☰
Android 14去录屏确认弹窗:MediaProjection授权链路定制实践
2026/9/30 13:02:13 网站建设 项目流程

最近在搞 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 14(API 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_CCACHE=1,并且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下某些工具文件丢失或损坏。最常见的诱因是上一次编译被 Ctrl+C 中断,或者某次操作把out/soong下的一部分目录删了,导致soong_build找不到依赖。

我的排查顺序是这样的:

  1. 先确认out/soong/build.ninja文件在不在,如果不在,说明上次生成没完成,直接删除整个out/soong重新来。
  2. 清理残留后重新初始化环境:rm -rf out/soong,然后重新执行source build/envsetup.sh和lunch,再make。这一步能解决八成问题。
  3. 如果还不行,检查磁盘空间:df -h看剩余空间,建议别低于 100G;同时留意 inode 是否耗尽。
  4. 谨慎考虑删除整个 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 产生版本不一致的假象。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询