ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Android 14去录屏确认弹窗:MediaProjection授权链路定制实践

Android 14去录屏确认弹窗:MediaProjection授权链路定制实践 最近在搞 Android 14 系统定制的时候碰到一个看着简单、实际能让人折腾两天的小需求去掉录屏确认弹窗。这个需求在行业终端、远程巡检、自动化测试这类场景里非常常见只要系统弹窗不消失自动化流程就会卡住。更麻烦的是 Android 14 把 MediaProjection 的授权链路又收紧了一轮老版本里直接改个返回值或者隐藏视图的做法基本失效了得顺着权限申请、服务注册、投影创建这条完整链路往下捋。这篇文章把我实测验证过的两种改法、关键 patch、编译踩坑记录都整理了一遍适合正在做 AOSP 定制、系统录屏方案或者想彻底搞懂 MediaProjection 授权机制的朋友直接参考。1. 别急着改代码先把弹窗链路理清楚1.1 一次录屏请求在 Android 14 里的完整路径应用层发起录屏时通常的调用长这样MediaProjectionManager mpm (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE); Intent permissionIntent mpm.createScreenCaptureIntent(); mMediaProjectionLauncher.launch(permissionIntent);createScreenCaptureIntent()返回的 Intent 实际指向系统内置的MediaProjectionPermissionActivity这个 Activity 才是弹窗本体。它会在界面上弹出 AlertDialog标题大概是“开始录制或转换屏幕内容”下面有“立即开始”和“取消”两个按钮。用户点击“立即开始”之后Activity 才会通过系统内部接口创建一个真正的IMediaProjectionBinder 对象打包进 result Intent 返回给应用。应用拿到 resultCode 和 data 后再通过MediaProjectionManager.getMediaProjection()获取MediaProjection实例最后调用createVirtualDisplay()把屏幕内容投递到 Surface 上。这条链路里有三个关键节点MediaProjectionManagerService负责服务端注册和权限状态维护MediaProjectionPermissionActivity负责弹窗展示与授权结果组装MediaProjection负责持有一整套录屏运行状态。很多去弹窗方案只盯着第二个节点改却忘了第一个节点还在做权限校验结果就是弹窗虽然没了应用却拿不到合法 token照样黑屏或者闪退。所以动手前一定要把三个节点的关系确认清楚再决定从哪里切入。1.2 Android 14 比老版本多了哪些“拦路虎”Android 13 及以前的版本MediaProjectionPermissionActivity的逻辑相对简单用户点“允许”就算完。但 Android 14API 34开始录屏弹窗不再是单一确认框它把音频采集来源的选择也塞了进来。现在的授权界面会要求用户选择捕获哪个音频来源比如“整个设备的音频”“某个应用的音频”或者干脆不录声音。这些选项最终会组织成一个MediaProjectionConfig对象传到服务端直接影响后续的音频捕获配置。另一个变化是前台服务类型的要求。Android 14 对录屏类服务的前台服务类型限制更严格如果应用没有正确声明mediaProjection类型的前台服务系统会在开始录屏后不久强制断开MediaProjection表现出来就是录到一半自动停止、回调里onStop反复触发。这个看似和弹窗无关但在去弹窗方案里属于典型的“隐藏的雷”你跳过弹窗后一切看起来正常跑一会儿就断排查半天才发现是前台服务类型的问题。所以在设计去弹窗方案时不能只想着“别弹窗”还要顺手把服务端校验逻辑和音频配置一起解决。这也是为什么我说这个需求“看着简单实际上是个链路改造”。2. 去掉弹窗的两种主流方案与取舍2.1 方案一在 MediaProjectionPermissionActivity 里直接授权最直接的做法是修改MediaProjectionPermissionActivity的onCreate把弹窗逻辑跳过直接构造授权结果并finish()。它的好处是改动集中只影响一个文件编译风险小而且应用侧的行为和原来几乎一模一样仍然是“发 Intent、等 resultCode”只是系统自动帮你点了“允许”。这个方案适合大部分 ROM 定制场景尤其是希望保留 MediaProjection 授权流程、只是去掉用户交互的情况。我在某行业终端上验证过去掉弹窗之后第三方录屏应用 SDK 的授权回调流程完全没变集成方只改了启动方式兼容性非常好。不过它有一个短板如果应用在启动录屏前额外检查了“弹窗是否可见”或者依赖系统返回的某些附加信息那单纯改 PermissionActivity 可能不够需要配合服务端策略一起调整。2.2 方案二在 MediaProjectionManagerService 层绕过权限校验方案二的思路是把决策点从 Activity 上移到服务端MediaProjectionManagerService在收到投影注册请求时如果命中自定义策略比如指定包名、指定 uid 或指定时间窗口就跳过弹窗创建流程直接构造投影对象并通过回调返回。这样应用层连弹窗 Activity 都不需要经过链路更“干净”。但方案二的问题也很明显服务端的投影注册流程是跨进程 Binder 调用加策略时要考虑调用方 token 的合法性问题绕错了轻则导致应用拿到的投影对象无效重则让整个 MediaProjection 服务异常。在 AOSP 上实现这个方案时你需要同时维护createProjectionInternal、回调注册、token 校验这几块逻辑工作量比方案一大出一截。除非你对MediaProjectionManagerService的内部实现已经比较熟悉否则我建议先用方案一落地再考虑服务端策略优化。2.3 我的选型建议如果你只是想要“去掉弹窗”选方案一如果你要做“只允许我的应用静默录屏、其它应用照旧弹窗”可以优先考虑方案二但记得留好白名单。不同 Android 14 分支之间MediaProjectionManagerService的代码也可能有差异方案二的迁移成本会明显高于方案一。对比项方案一方案二改动范围单个 ActivityService Activity应用兼容性高中实现难度低高适用场景全局去弹窗白名单静默录屏迁移成本低中高实际项目里我基本都是先用方案一完成核心需求把功能跑通再在方案一之上叠加白名单判断。这样风险最小出问题也容易回溯。3. 动手改 AOSP 源码关键代码与配置细节3.1 修改 MediaProjectionPermissionActivity 的完整 Patch首先定位文件frameworks/base/services/core/java/com/android/server/media/MediaProjectionPermissionActivity.java在onCreate里等MediaProjectionManager初始化完成后把所有弹窗和权限检查逻辑跳过直接构造授权结果。参考 patch 如下不同 AOSP 版本的 API 签名可能略有差异以你正在编译的源码为准Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); mUid getIntent().getIntExtra(MediaProjectionManager.EXTRA_UID, -1); mPackageName getIntent().getStringExtra(MediaProjectionManager.EXTRA_PACKAGE_NAME); mAppToken getIntent().getParcelableExtra( MediaProjectionManager.EXTRA_APP_TOKEN, IMediaProjection.class); if (mPackageName null || mAppToken null || mUid 0) { setResult(RESULT_CANCELED); finish(); return; } // 去弹窗核心跳过 AlertDialog直接创建投影对象并返回 mProjectionManager (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE); IMediaProjection projection mProjectionManager.createProjection(mPackageName, mUid, mAppToken); Intent resultData new Intent(); resultData.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION, projection.asBinder()); setResult(RESULT_OK, resultData); finish(); }这段代码里mProjectionManager.createProjection()属于系统内部接口普通 SDK 里看不到但在 AOSP 源码编译时可以正常调用。如果编译时报方法不存在多半是版本差异去MediaProjectionManager.java里搜一下createProjection的当前签名把参数改成对应形式即可。我当初第一次改的时候偷懒直接在onCreate里setResult(RESULT_OK)然后finish()结果应用确实不弹窗了但拿不到 MediaProjection 对象后面创建 VirtualDisplay 时直接 NullPointerException。所以这一步一定不能省必须把合法的 projection binder 塞回 data 里。3.2 处理 Android 14 新增的音频选项配置Android 14 的弹窗里之所以多了音频选择是因为服务端需要MediaProjectionConfig来决定音频捕获配置。你如果只按上面 3.1 的 patch 改会发现很多应用录出来只有画面没有声音或者音频来源不对。这是因为默认 config 的音频捕获配置是空的。如果要保证录屏有声音可以在创建投影时传入一个MediaProjectionConfig配置音频捕获规则。参考写法如下MediaProjectionConfig config new MediaProjectionConfig.Builder() .setAudioPlaybackCaptureConfig( new AudioPlaybackCaptureConfiguration.Builder(getMediaProjection()) .addMatchingUsage(AudioAttributes.USAGE_MEDIA) .addMatchingUsage(AudioAttributes.USAGE_GAME) .build()) .build(); IMediaProjection projection mProjectionManager.createProjection(mPackageName, mUid, mAppToken, config);注意这里有个“先有鸡还是先有蛋”的问题AudioPlaybackCaptureConfiguration.Builder需要先有一个 MediaProjection 实例才能构造而我们恰恰是要创建投影对象。实际操作里可以先创建一个不带 config 的 projection用它构造音频配置再通过服务端接口更新配置或者直接改MediaProjectionManagerService在创建投影时自动填充一个默认的AudioPlaybackCaptureConfiguration。我实测中更推荐后者因为改动更集中也避免多个应用各自处理音频配置。在MediaProjectionManagerService.createProjectionInternal()里找到构建 MediaProjection 实例的位置给audioPlaybackCaptureConfig字段一个默认值只捕获USAGE_MEDIA和USAGE_GAME。这样能覆盖绝大多数游戏、视频、音乐同步录音的需求也不容易引入麦克风回环问题。3.3 SystemUI 侧注意点与 SELinux 补充去弹窗改动主要落在系统服务代码里但 Android 14 的 SystemUI 还负责录屏指示点和媒体投屏状态显示。弹窗去掉之后SystemUI 可能仍然会在状态栏显示录制指示点或者误判系统正在投屏。如果产品上也希望隐藏状态栏录屏图标可以到MediaProjectionIndicator里把对应状态回调改成空实现或者在注册监听时过滤掉指定包名。另外某些 Android 14 厂商分支启用了额外的 SELinux 策略MediaProjectionPermissionActivity直接调用内部接口创建投影时可能会遇到权限拒绝日志一般是avc: denied类型。你需要在对应的.te文件里补 allow 规则。不是每台设备都会遇到但我在某个厂商分支上确实碰过所以提一句遇到日志再针对性处理。4. 编译验证与集成实操4.1 编译相关模块与环境建议改动MediaProjectionPermissionActivity属于系统服务代码最省时间的编译方式是单独编译 services 模块。在 AOSP 根目录执行source build/envsetup.sh lunch 你的产品-userdebug make services -j$(nproc)编译完成后产物会输出到out/target/product/产品/system/framework/services.jar可以单独 push 到设备/system/framework下并重启 SystemServer 验证不用每次都整包刷机。正式验收当然建议直接做整包编译make -j$(nproc)环境方面Android 14 比老版本吃资源建议至少 32GB 内存、200GB 空闲磁盘。ccache 开大一点能明显加快重复编译我的习惯是设置USE_CCACHE1并且ccache -M 100G多轮编译之间能省不少时间。4.2 常见编译报错 failed: out/soong/build.ninja 的排查最近不少人在编译 Android 14 时都报过这个错报错大概长这样FAILED: out/soong/build.ninja cd $(dirname out/host/linux-x86/bin/soong_build) ...从报错内容看是soong_build在尝试重新生成build.ninja时失败了它依赖的out/host下某些工具文件丢失或损坏。最常见的诱因是上一次编译被 CtrlC 中断或者某次操作把out/soong下的一部分目录删了导致soong_build找不到依赖。我的排查顺序是这样的先确认out/soong/build.ninja文件在不在如果不在说明上次生成没完成直接删除整个out/soong重新来。清理残留后重新初始化环境rm -rf out/soong然后重新执行source build/envsetup.sh和lunch再make。这一步能解决八成问题。如果还不行检查磁盘空间df -h看剩余空间建议别低于 100G同时留意 inode 是否耗尽。谨慎考虑删除整个 out 目录因为里面编译好的 target 产物也会一并清掉重新全量编译非常耗时。除非out/soong反复出问题否则不建议动不动就全员清空。另外有一个很重要的习惯不要在源码目录下用 sudo 执行编译命令。我之前有同事用sudo make后out 下部分文件属主变成 root后续普通用户编译时soong_build无法写入最终报错就是这种样子。遇到这种情况用sudo chown -R 用户名:组名 out恢复属主比清空 out 快得多。4.3 烧录后验证点清单编译成功并烧录到设备之后建议按下面的清单逐项过一遍确认去弹窗方案真的完整落地了录屏应用能否正常启动并收到RESULT_OK。执行adb shell dumpsys media_projection确认投影已经注册包名和 uid 正确。录屏画面是否正常显示旋转屏幕方向后画面是否依然流畅。音频是否正常特别是视频应用里的媒体声音。锁屏再解锁已有的投影是否还保持在线。杀掉录屏应用后服务端状态是否释放干净能否马上重新发起录屏。其中dumpsys media_projection是最关键的命令它能把服务端登记的投影对象、uid、包名、回调数量都打印出来。很多问题靠日志看不出名堂一跑 dumpsys 就清楚了。5. 常见问题与避坑指南5.1 问题速查表现象常见原因解决办法弹窗没了但录屏黑屏返回的 projection binder 无效确认 onCreate 中调用了 createProjection并把合法 binder 塞进 resultData画面正常但没有声音音频捕获配置缺失在服务端给 MediaProjection 设置默认 AudioPlaybackCaptureConfiguration录到一半自动断开前台服务类型不符合 Android 14 要求应用侧声明 mediaProjection 类型的前台服务编译报 out/soong/build.ninja 失败out 目录损坏或磁盘空间不足按 4.2 的顺序清 soong 缓存、检查磁盘SELinux 拒绝日志缺少对应 allow 规则根据 avc: denied 日志补 .te 文件表格之外有个容易忽略的点如果你的方案还涉及 SystemUI 状态栏录屏图标记得把MediaProjectionIndicator的更新逻辑过滤掉否则用户会看到“明明没有弹窗也没有主动录屏状态栏却一直有个红点”的怪现象。5.2 几条独家心得第一改完 PermissionActivity 之后最好顺手把MediaProjectionManagerService里的权限校验也看一遍。有些 Android 14 分支有额外的敏感内容检查比如系统检测到当前屏幕上正在输入密码会强制终止录屏。这类策略不是弹窗自带的而是服务端主动推下来的不处理的话就会出现“某些页面录不了”的情况。第二dumpsys media_projection是排查问题的利器。我定位好多问题都是先看它的输出再对比应用侧拿到的 resultData 里 binder 是否一致基本几分钟就能锁定是 Activity 侧的问题还是服务端的问题。这个命令比反复抓 logcat 高效得多。第三去弹窗改动和默认音频配置建议放在同一个 commit 里。分开提交容易让后人以及自己在排查问题时忽略“音频配置依赖投影对象”这种关联关系。我实际维护代码时吃过这个亏后来就把两者绑在一起做并在代码注释里写清楚原因。最后再分享一个实际项目里很实用的技巧如果只是想快速验证去弹窗效果不一定每次都要整包 make。单独编译services.jar之后用adb shell stop adb shell start快速重启框架比整机重启省时间。我在 Android 14 上试过多次这个流程对 framework 改动验证很稳。当然最终验收还是建议刷整包避免手工 push 产生版本不一致的假象。
返回列表