ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

VirtualApp悬浮窗权限适配终极指南:多开场景下从Android 6到14的避坑路线图

VirtualApp悬浮窗权限适配终极指南:多开场景下从Android 6到14的避坑路线图 VirtualApp悬浮窗权限适配终极指南多开场景下从Android 6到14的避坑路线图【免费下载链接】VirtualAppVirtual Engine for Android(Support 14.0 in business version)项目地址: https://gitcode.com/GitHub_Trending/vi/VirtualAppVirtualApp是一款开源的Android虚拟引擎沙盒它的核心能力是让同一台设备上并行运行多个应用实例也就是常说的应用多开、游戏双开。而悬浮窗权限适配恰恰是让多开功能真正活起来最棘手的一环——Android系统从6.0到14.0对悬浮窗的限制几乎每个大版本都在加码。这篇文章不打算按版本号给你念流水账而是从你大概率踩过的三个坑出发讲清楚VirtualApp是怎么在沙盒里完成悬浮窗权限适配的以及你该抄哪些作业。先把三个日常翻车现场摆上台面先对号入座下面这三种情况你大概率至少中过一个悬浮窗直接不显示——沙盒里的App明明设置了显示在其他应用上层结果悬浮球死活弹不出来。授权后毫无反应——Settings里开关都打开了回到App依然提示未授予悬浮窗权限。一进后台就被杀——悬浮窗还在但多开的应用进程被系统回收连保活都保不住。这三个问题的根源都指向同一个事实悬浮窗权限SYSTEM_ALERT_WINDOW从来不是申请一次就完事的普通权限。它在Android系统里属于特殊权限走的是AppOps应用操作权限体系而VirtualApp的沙盒结构让这个体系变得格外复杂。接下来我们一步步拆解你会发现这三个坑其实是同一个问题的三个侧面。先记住一个关键结论沙盒里权限归宿主管在深入代码之前先理解VirtualApp的权限归属模型。你可以把系统权限机制想象成大楼的安检制度普通应用是访客进楼要过安检动态权限弹窗悬浮窗权限是门禁卡访客无法自己办理必须由大楼管理处系统设置审批VirtualApp则像一个内部中介它让你沙盒里的每个App都能借用宿主的身份刷卡进楼。这个借用身份的设计就是整个悬浮窗权限适配的基石。看下面的架构图VA Framework层负责应用层的Hook和管理悬浮窗权限相关的所有拦截动作都发生在这里为什么必须借用身份因为系统的权限检查是按uid和包名一对一核验的。沙盒内的AppVApp Client进程拥有自己的包名但系统里根本没有这个包名的权限记录。VirtualApp的做法是让系统以为检查的还是宿主应用。多进程的协作关系可以看这张图先记住这个结论后面所有代码你都能看懂沙盒App的悬浮窗权限是否通过取决于宿主App是否被授予了该权限以及VirtualApp是否成功把检查对象替换成了宿主。痛点一授权后无响应问题出在AppOps检查拦截这是最隐蔽的坑。很多开发者把Settings.canDrawOverlays()和ACTION_MANAGE_OVERLAY_PERMISSION玩得滚瓜烂熟但放到VirtualApp沙盒里这套流程经常失灵——因为系统检查的AppOps记录里根本没有沙盒App的名字。做什么在Binder层面偷换检查参数VirtualApp在lib模块的AndroidManifest.xml中声明了权限uses-permission android:nameandroid.permission.SYSTEM_ALERT_WINDOW /光声明还不够。沙盒里的App发起checkOperation、noteOperation这类AppOps查询时请求会经过一个名为AppOpsManagerStub的Hook源码在com.lody.virtual.client.hook.proxies.appops包下。它的核心逻辑是把参数里的包名替换成宿主包名、uid替换成宿主真实uidpublic class AppOpsManagerStub extends BinderInvocationProxy { Override protected void onBindMethods() { addMethodProxy(new BaseMethodProxy(checkOperation, 1, 2)); addMethodProxy(new BaseMethodProxy(noteOperation, 1, 2)); addMethodProxy(new BaseMethodProxy(startOperation, 2, 3)); addMethodProxy(new BaseMethodProxy(finishOperation, 2, 3)); } private class BaseMethodProxy extends StaticMethodProxy { Override public boolean beforeCall(Object who, Method method, Object... args) { // 把包名替换为宿主包名uid替换为宿主uid if (pkgIndex ! -1 args[pkgIndex] instanceof String) { args[pkgIndex] getHostPkg(); } if (uidIndex ! -1 args[uidIndex] instanceof Integer) { args[uidIndex] getRealUid(); } return true; } } }为什么系统只认识宿主不认识沙盒App系统收到宿主包名宿主uid的查询后会去AppOps表里查宿主的记录。宿主装在你的手机上时你已经手动授权过悬浮窗所以查询结果自然返回已允许。沙盒App拿到的就是一个被洗白过的答案——授权后无响应的问题本质上不是授权流程的锅而是权限查询对象对不上号。结果如何你的App只需按正常流程走理解这一点后你的适配代码反而可以很朴素按标准流程检查即可if (Build.VERSION.SDK_INT Build.VERSION_CODES.M) { if (!Settings.canDrawOverlays(context)) { Intent intent new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse(package: context.getPackageName())); activity.startActivityForResult(intent, REQUEST_OVERLAY); } }注意事项这段代码适用于宿主自身的权限检查Android 11API 30及以上推荐改用canDrawOverlays()方法在沙盒App内部判断时最终结论由上述Stub的Hook结果决定。痛点二悬浮窗不显示窗口类型和SDK版本绑定权限通过了悬浮球还是不出现这通常不是权限问题而是窗口类型WindowType没跟上系统版本。做什么按版本切换WindowManager.LayoutParams类型Android 8.0API 26开始系统强制要求TYPE_APPLICATION_OVERLAY旧的TYPE_PHONE会被直接忽略甚至抛异常。这也是为什么老代码在新手机上静默失败WindowManager.LayoutParams params new WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, Build.VERSION.SDK_INT Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_PHONE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); windowManager.addView(floatView, params);为什么不同版本对窗口层级的安检规则不同可以这样理解旧版本像老式商场商户随便在过道摆摊TYPE_PHONEAndroid 8.0之后商场换了管理制度所有摊位必须到指定楼层TYPE_APPLICATION_OVERLAY经营。你如果还用旧的方式摆摊保安WindowManager直接当没看见——不报错就是不显示。结果如何新版本必须用新窗口类型从Android 8.0开始只用TYPE_APPLICATION_OVERLAY这一条路。如果你的业务同时覆盖老设备保留版本判断是必须的。这也解释了为什么网上很多悬浮窗适配代码都长着相似的三元表达式——它不是模板是系统规则逼出来的。痛点三一进后台就被杀前台服务与版本限制的组合拳多开App通常希望悬浮球常驻但Android 10API 29开始系统对后台弹窗做出了明确限制应用在后台时不允许弹出悬浮窗。这直接导致切回桌面→悬浮球消失的经典问题。做什么前台服务保活 前后台状态感知常规解法是把悬浮窗管理交给一个前台服务startForeground同时监听前后台切换在前台时恢复显示// Android 12API 31及以上前台服务还需要声明类型 if (Build.VERSION.SDK_INT Build.VERSION_CODES.S) { Notification notification buildNotification(); startForeground(NOTIFICATION_ID, notification); }配合前后台感知Override public void onPause() { super.onPause(); if (isAppInBackground()) { hideFloatWindow(); } }为什么这是系统资源回收制度与保活需求的博弈把前台服务想象成长租房合同签了合同startForeground系统就不会随意把你扫地出门。但Android 12之后合同还要写明用途前台服务类型否则依然会被拒。与此同时Android 10的后台限制又要求你在后台别搞事——所以最佳实践是后台隐藏、前台恢复而不是硬顶着系统的规则。结果如何悬浮窗生命周期与应用状态保持同步把创建、显示、隐藏收敛到一个FloatWindowService里用onDestroy统一回收视图避免内存泄漏。这一步做完悬浮窗的存活率基本就稳了。一张表看清不同Android版本的适配关注点Android版本API级别权限申请方式窗口类型后台/前台服务要点6.0–7.123–25动态申请SYSTEM_ALERT_WINDOW归为特殊权限走系统设置TYPE_PHONE可用无强制限制保活压力小8.0–9.026–28同上AppOps体系细化必须TYPE_APPLICATION_OVERLAY通知渠道NotificationChannel强制化1029同上TYPE_APPLICATION_OVERLAY后台禁止弹窗需前后台状态感知1130同上canDrawOverlays()替代部分旧APITYPE_APPLICATION_OVERLAY软件包可见性QUERY_ALL_PACKAGES影响多开列表12–1331–33同上TYPE_APPLICATION_OVERLAY前台服务必须声明类型通知管理更严1434同上窗口位置/大小控制更精细TYPE_APPLICATION_OVERLAY需根据WindowInsets调整悬浮窗边界这张表怎么用不要背代码背关注点——每个版本升级先看它动的是权限检查还是窗口展示还是进程存活这三条线中的哪一条适配就有方向了。跨版本适配的底层真相条件注入的Hook架构你可能会好奇VirtualApp自己是怎么做到跨版本都稳定的答案藏在InvocationStubManager里——它按SDK版本条件性地注入不同的系统服务Hook类似按楼层装不同型号的安检门if (Build.VERSION.SDK_INT KITKAT) { addInjector(new AppOpsManagerStub()); // 悬浮窗权限的AppOps劫持 addInjector(new AlarmManagerStub()); } if (Build.VERSION.SDK_INT M) { addInjector(new FingerprintManagerStub()); } if (Build.VERSION.SDK_INT N) { addInjector(new WifiScannerStub()); }VClientImpl里更是到处是Build.VERSION.SDK_INT分支从磁盘缓存目录到Renderer初始化每个版本都有专属处理路径。这给我们的启发是与其写一个塞满if-else的巨型类不如像VirtualApp一样按系统服务为单位拆分Hook再用版本判断统一装配。你的悬浮窗适配代码也应当遵循同样的可维护性思路——把权限检查窗口创建服务保活拆成独立模块。上线前的最终核查清单按照下面的清单逐项打勾能帮你规避90%的悬浮窗权限适配事故宿主AndroidManifest.xml已声明SYSTEM_ALERT_WINDOW权限Android 8.0 分支使用了TYPE_APPLICATION_OVERLAY窗口类型沙盒内App的权限检查能被AppOps Hook正确拦截包名/uid替换生效Android 10 已处理后台弹窗限制后台隐藏/前台恢复Android 12 前台服务已声明类型并绑定通知Android 14 已按WindowInsets调整悬浮窗边界避免遮挡系统栏多进程场景下权限状态可同步VA Server侧维护共享状态总结与行动建议回过头看VirtualApp的悬浮窗权限适配其实是一套三层联动宿主声明权限、Hook层偷换权限查询身份、窗口层按版本切换类型。三个翻车现场分别对应这三层中的一个环节排查时按权限→窗口→进程的顺序走基本不会漏。下一步建议你这样做把上面的核查清单落到你的多开Demo里重点验证Android 10和Android 12两个分水岭版本阅读VirtualApp源码中com.lody.virtual.client.hook.proxies包下的AppOps相关Hook理解它的拦截边界再决定要不要扩展自己的权限劫持逻辑关注Android 15及后续版本对前台服务类型的进一步收紧这类改动通常会影响悬浮窗常驻方案提前在官方文档与社区讨论中跟进。悬浮窗权限适配没有一劳永逸的银弹但把机制想透、把版本关注点列成表你就能在每个新版本到来时第一时间判断出该动哪一行代码。【免费下载链接】VirtualAppVirtual Engine for Android(Support 14.0 in business version)项目地址: https://gitcode.com/GitHub_Trending/vi/VirtualApp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表