news 2026/9/30 3:37:58

Android 14 AOSP定制:去掉录屏确认弹窗的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Android 14 AOSP定制:去掉录屏确认弹窗的完整指南

做AOSP定制的兄弟应该都懂,系统录屏时的确认弹窗是个很烦人的东西。不管你是给教学平板做自动录屏,还是给企业设备做远程协助,只要产品需要无人值守地采集屏幕内容,这个弹窗就会一遍遍打断自动化流程。Android 14里这个情况还更特殊,因为隐私保护逻辑加了不少新约束。我在自己项目里刚好把“去掉录制屏幕弹窗”完整走了一遍,从原理到改源码再到编译踩坑,整理一版实操笔记出来,给有同样需求的朋友做个参考。

1. 先把需求拆清楚:这个弹窗到底是谁弹的

1.1 弹窗的“真身”不是录屏服务,而是权限申请页

不少第一次接触这个需求的人会先去MediaProjectionManager、MediaProjection这些类里面翻,但实际让你点“允许/取消”的确认框,跟录屏的数据流是两套东西。

应用层拿录屏能力走的是这套链路:应用调用MediaProjectionManager.createScreenCaptureIntent(),拿到一个系统级的 Intent,然后startActivityForResult()把这个 Intent 发出去。这时候系统会拉起一个专门的权限确认页,用户点“允许”,系统回调RESULT_OK和对应的 data Intent,应用再用这个结果去获取MediaProjection实例。

这个权限确认页在Android 14的AOSP里位于packages/apps/PermissionController,具体类是com.android.permissioncontroller.permission.ui.MediaProjectionPermissionActivity。它是独立于录屏服务的,去掉录屏弹窗最直接的思路,就是让这个 Activity 在收到请求时不再弹 UI,而是直接返回授权结果。

1.2 Android 14为什么把权限收得更紧

Android 14对MediaProjection的限制确实比之前的版本严。

首先从应用侧来说,Android 14要求调用createScreenCaptureIntent的应用必须在前台运行,不能像以前那样在后台偷偷拉起授权请求。其次,应用如果要持续投屏/录屏,需要在前台服务中声明mediaProjection类型,并且在前台服务启动时拿到对应的MediaProjection实例,否则会直接抛SecurityException。

这些约束会对“去掉弹窗”的改造产生连锁反应。比如你把弹窗绕过之后,如果目标应用没有正确申请前台服务类型,录屏会话依然可能启动失败;又比如系统在启动MediaProjection会话时,SystemUI 里会同步出现“正在录制”的红色提示条,这在特定定制场景下也需要隐藏。

所以完整的“去掉录屏弹窗”改造,包含两件事:一是取消权限确认环节,二是确保后续录屏会话能正常拉起和持续运行。两个环节缺一个,都会出现“弹窗没了但录不了”或“录了几秒自己断掉”的尴尬现象。

1.3 哪些场景需要动这个弹窗

我在实际接触的项目里,主要遇到三类需求:

第一类是自动化测试。跑UI自动化时,如果需要录制机内屏幕做取证,每次都要人工点允许,测试流程根本没法持续跑。第二类是政企/教育定制设备,管理员希望设备开机后自动录屏,或者让远程协助方直接看到被控端画面,不能每次连接都让用户做选择。第三类是录屏工具类App想给用户“零打扰”体验,直接在应用内完成所有授权逻辑,不让系统弹窗打断操作。

不论哪类场景,最终在系统层要做的事情都是一样的:让MediaProjectionPermissionActivity不出现,或者让它自动放行。但“自动放行”这个动作怎么做,有不同的技术路线,下面我逐个拆一下。

2. 去弹窗方案选型:三条路子怎么选

2.1 方案一:修改PermissionController,自动返回授权结果

这个方案改动范围最小,也最符合系统原本的设计语义。核心思路是让MediaProjectionPermissionActivity在onCreate阶段直接“代用户”做出允许选择,然后setResult+finish。

好处很明显:权限确认链路完整保留,系统回调逻辑不变,目标应用不需要感知到系统被改动过。坏处也很明显:所有应用都会“被自动授权”,等于把整个系统的录屏权限白名单彻底打开。在政企单应用设备上这没问题,但在面向大众用户的多应用设备上,任何应用都能无感录屏,隐私层面的风险太高。

所以这个方案适合“设备上只有受信应用”的封闭场景,不适合开放市场的消费级ROM。

2.2 方案二:MediaProjectionManagerService 服务端放行

这个方案不做UI层的自动点击或自动返回,而是直接改frameworks/base/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java,在服务端判断用户Uid、包名、调用场景,对命中白名单的调用直接授予投影权限,其他情况继续走原确认流程。

好处是可控性强:完全由白名单决定谁可以免确认录屏,而不是全局放开。坏处是改动点在系统服务层,相对底层,代码编译和排查门槛稍高,而且各家AOSP版本的MediaProjectionManagerService内部实现差异不小,从Android 13到14这段代码变化的幅度就挺明显,升级版本时补丁要跟着适配。

如果你做的是设备级ROM,又希望普通应用保持原来的弹窗授权机制,只给特定几个内置应用开免确认通道,方案二就是比较合理的路线。

2.3 方案三:AppOps 预置权限

还有一个稍微“野”一点的思路,就是绕开源码改动,在系统启动或以root身份执行时,直接对目标包名设置PROJECT_MEDIAAppOps 权限为允许:

adb shell appops set <package> PROJECT_MEDIA allow

如果你只是想在开发阶段快速验证,这条命令确实能免除一部分确认流程。但它的作用范围有限:AppOps 放行不等于MediaProjectionPermissionActivity完全不会出现——在部分Android 14版本上,权限确认 Activity 的启动判断不只依赖AppOps,还会叠加其他运行时状态,例如应用是否处于前台、是否已经拥有MediaProjectiontoken 等。它用来调试排查、临时验证是够用的,但作为量产ROM的正式方案不太够格。

2.4 我的选择:主改UI层授权 + 服务端白名单兜底

我在自己项目里采用的是“方案一 + 方案二”组合:先在MediaProjectionPermissionActivity层对指定包名自动放行,同时在MediaProjectionManagerService里加一个白名单判断,让系统内部调用路径不需要再依赖Activity回调结果。

这样做的原因是,Android 14上的投屏/录屏入口不止createScreenCaptureIntent一条:带MediaProjection的VirtualDisplay创建还会涉及MediaProjectionManager.createVirtualDisplay,而有些系统内置应用会走系统权限绕过用户确认。如果只在UI层改,部分系统内部入口仍然会被拦;如果只在服务端改,开放源码的权限确认Activity又可能在一些边界情况下被拉起。两层一起改,覆盖面最全,也最稳。

下表是三条方案的对比,方便你根据自己项目形态做选型:

方案改动位置控制粒度风险适用场景
PermissionController自动授权应用层UI全局或包名判断全部应用可无感录屏封闭设备、单应用场景
MediaProjectionManagerService白名单系统服务可按Uid/包名精确控制改动底层,需适配版本差异多应用设备、定向放行
AppOps预置权限系统设置项按包名不彻底,部分版本失效开发调试、快速验证

3. AOSP实操:在PermissionController里直接放行

3.1 准备工作和源码定位

动手之前,先确认你的AOSP源码环境是完整的,最好已经成功编译过一遍基础镜像。没编译过的源码树,直接改完再编译会遇到一堆环境问题,很难判断到底是代码问题还是构建问题。

确认环境没问题后,我先定位MediaProjectionPermissionActivity:

find packages/apps/PermissionController -name "*MediaProjection*"

Android 14的路径一般是:

packages/apps/PermissionController/src/com/android/permissioncontroller/permission/ui/MediaProjectionPermissionActivity.kt

这个文件从Java改成了Kotlin实现,结构跟老版本差异不小。展开之后最核心的就是onCreate里的授权链路。如果你只是想要一个快速验证版,可以直接在onCreate里把授权结果设置好:

override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (intent?.action == MediaProjectionManager.ACTION_MEDIA_PROJECTION_PERMISSION) { val resultData = intent.getParcelableExtra<Intent>( MediaProjectionManager.EXTRA_MEDIA_PROJECTION_PERMISSION_DATA ) val resultIntent = Intent() resultIntent.putExtra( MediaProjectionManager.EXTRA_MEDIA_PROJECTION_PERMISSION_DATA, resultData ) setResult(RESULT_OK, resultIntent) finish() return } // 原有流程 bindAndStartService() ... }

注意上面这版改动是“对所有应用全局放行”,只适合封闭场景验证,量产用的版本一定要加包名或签名校验。我实际用的版本会再套一层签名白名单判断,命中白名单才自动放行,否则继续走弹窗。

3.2 别忽略权限回调的附加数据

改完setResult之后,有一个必须确认的细节:回调给上层应用的resultIntent里,那个额外的EXTRA_MEDIA_PROJECTION_PERMISSION_DATA不能丢。这个 data Intent 是MediaProjectionManagerService后来用来恢复会话状态的凭据,少了它,应用拿到了RESULT_OK但后续调用getMediaProjection时依然可能失败。

有些朋友改完代码发现“弹窗是没了,但应用一直拿不到投影会话”,很大原因就是这里只顾着setResult没把附加数据原样传回去。在系统源码里,这段数据其实是MediaProjectionPermissionActivity在正常用户点击允许后,从MediaProjectionManagerService那里拿到的结果。我直接把intent里的getParcelableExtra原样回填,保证了链路一致。

3.3 服务端白名单兜底改法

接着处理MediaProjectionManagerService的白名单兜底。这个类的位置在:

frameworks/base/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java

它的核心逻辑是维护一个MediaProjection会话集合,授权来源会走到内部的状态判断。我加的改动是在授权判断入口处,先检查调用者包名是否命中预置名单:

private boolean isWhitelistedProjectionApp(int callingUid, String packageName) { final String[] whitelist = getResources().getStringArray( R.array.config_mediaProjectionAutoGrantPackages); if (whitelist == null || whitelist.length == 0) { return false; } for (String pkg : whitelist) { if (packageName != null && packageName.equals(pkg)) { return true; } } return false; }

然后在服务内部创建/校验 MediaProjection 权限的地方,命中白名单时直接放行,否则走原有的权限确认逻辑。

当然,不同AOSP版本这段代码的位置差异较大。Android 14 release分支里,授权相关的入口和AppOps检查、foreground检查耦合在一起,我建议不要硬套网上的旧补丁,而是先花点时间把当前版本的调用链看清楚,再把白名单判断插到最外层入口。你可以在改之前先编译一遍基线代码,确认哪些文件、哪些方法会被调用到,避免盲改。

3.4 编译刷机验证的流程

改完代码后,需要分别编译 PermissionController 和 framework:

source build/envsetup.sh lunch <your_product>-userdebug m packages/apps/PermissionController m services

只编模块然后adb sync到设备上,速度会快很多。但如果改动涉及 framework 服务,一般需要连带重启 system_server,或者直接整机刷入。更稳的做法是编完所有相关镜像后,用fastboot flashall刷机验证,避免模块间版本不一致导致诡异问题。

刷完机后验证的点有两个:第一,目标应用调用createScreenCaptureIntent的时候,不再出现任何中间确认页,直接进入录屏状态;第二,录屏数据也正常出来了,不是黑屏或花屏。只验证弹窗消失但不验证录屏结果,等于没测。

4. 编译报错实录:out/soong/build.ninja 这类坑怎么填

4.1 一个典型的构建失败现场

改造Android 14源码时,我遇到过好几次类似这样的构建失败输出:

FAILED: out/soong/build.ninja cd "$(dirname "out/soong/build.ninja")" ... soong bootstrap failed with: exit status 1

看到FAILED: out/soong/build.ninja,有些朋友第一反应是去修 ninja 文件,其实方向就错了。out/soong/build.ninja是 Soong 构建系统根据你当前环境、产品配置、源码变更自动生成的总构建描述文件。它本身不是手写的,而是由soong_build生成的,真正失败的点通常在“生成这个 ninja 文件”之前的分析或配置阶段。

这个时候重点看两类日志:一类是out/soong/soong.log,另一类是out/soong/soong_ui.log。如果是环境问题,日志里一般会直接报 Python 依赖缺失、磁盘空间不足、Java 版本不符;如果是源码本身的问题,比如模块依赖写错、MK/BP文件语法错误,日志会把具体涉及的Android.bp或Android.mk指向出来。

4.2 常见的根因和处理方法

我结合自己这次踩坑的经验,整理一下最常见的几个根因:

第一,目录残留脏数据。上一次编译在不同产品配置或不同分支下留下了旧产物,再切回当前配置时,Soong 的状态文件和产物缓存不一致,导致重新生成 build.ninja 失败。处理方式比较直接:

rm -rf out/soong

重新构建。这个操作会丢掉 Soong 的增量缓存,让系统重新做完整的环境探测和产物分析。如果还不行,就把整个out目录清掉重来。代价是编译时间变长,但能解决绝大部分状态错乱问题。

第二,并发量大导致资源耗尽。m -j64这种高并发在内存不够的机器上,经常出现构建进程被杀、soong_build 中途退出的情况。想判断是不是这个问题,看soong.log尾部有没有内存分配失败的痕迹。遇到的话,调低并发数,比如m -j16或者干脆让系统自己分配m。

第三,代码里的语法错误被 Soong 规则检测到,但报错信息被 build.ninja 的失败日志掩盖了。这时看soong.log里最早出现的报错,定位到具体Android.bp文件的那一行。这种问题需要冷静顺着日志往前找,不要在out/soong/build.ninja附近反复纠结。

4.3 模块编译和全量编译的选择建议

我自己的习惯是,改动小模块时先用模块编译验证:

m packages/apps/PermissionController

模块编译能过,再考虑整机刷入。如果模块编译阶段就遇到out/soong/build.ninja类错误,大概率不是这个模块本身的问题,而是项目整体构建状态的问题。这时候检查环境变量是否污染、有没有多开终端同时编同一棵源码树。

另外提醒一句:尽量不要在同一棵源码树里反复切换 lunch 目标。AOSP的增量系统对“换目标”的支持并没有想象的那么健壮,很多离奇的build.ninja报错都是切换目标导致的。有条件的话,不同产品目标用不同的out目录,或者编完一个目标后彻底清理再编下一个。这个习惯能帮你少踩很多坑。

5. 常见问题与避坑手册:弹窗没了只是开始

5.1 弹窗是没了,但录出来是黑屏

这个现象在Android 14上非常典型。原因多半不在权限确认环节,而是MediaProjection的会话没有真正绑定到有效的VirtualDisplay。应用拿到授权结果后,需要调用getMediaProjection获取实例,再配合VirtualDisplay把屏幕内容投递到应用侧。

如果在服务端我做白名单放行时,没有同步处理好 token 的注册和会话恢复,就会出现“授权状态ok但数据管道没建立”的假成功。排查时先确认应用侧日志里onStart回调是否触发,再确认VirtualDisplay的Display参数是否有效。不要一上来就怀疑系统改动,先把标准录屏流程用 ADB 脚本跑一遍,能录再回头查定制逻辑。

5.2 目标应用设了前台服务,却依然被拒

Android 14的mediaProjection前台服务类型有个特殊之处:系统要求应用启动前台服务时,必须已经持有有效的MediaProjection实例,否则会抛SecurityException。这意味着“先启动前台服务再申请录屏”的老写法在Android 14上可能不工作,得先申请录屏能力、拿到实例再启动前台服务。

这个时序问题在定制ROM关闭弹窗后会变得更隐蔽,因为应用开发者还以为系统弹窗是因为自己的代码写得不对。我在项目里专门写了一份适配说明,提醒预置应用使用如下顺序:先声明foregroundServiceType="mediaProjection",在应用进入前台后发起录屏请求,拿到MediaProjection之后立即启动前台服务。把这个时序理顺了,弹窗消失之后录屏才能稳定运行。

5.3 录屏过程的通知和状态栏提示如何隐藏

系统级录屏激活后,用户会在状态栏看到一个红色的“正在录制”指示,通知栏也会出现对应通知。在一些无人值守设备上,这种视觉提示会暴露录屏行为,也需要一并处理。

这个提示由 SystemUI 里的媒体投影通知逻辑控制。如果只是在 PermissionController 层放行,通知还是会正常出现。想隐藏的话,需要到 SystemUI 源码里找到MediaProjection或ScreenCapture相关的通知创建处,把对应系统通知屏蔽,同时保留应用自身录屏状态。这里建议不要直接把所有通知都禁掉,因为录屏通知在某些场景下跟用户交互有关联,比如停止录屏的操作入口。完全屏蔽后,你会失去一个快捷停录途径,务必要在应用层提供等效的停止入口。

5.4 隐私安全的权衡

最后再说一个容易被忽略的点。去掉录屏弹窗本质是在削弱系统级隐私屏障,这在开发机和封闭设备上没问题,但如果产品要对外发布,你就得想清楚如何应对合规和用户隐私诉求。

我自己的处理思路是:保留服务端白名单,默认不全局放行;所有预置录屏应用必须声明明确的录屏用途;设备提供“录屏状态常驻通知”的全局开关,企业管理员可以决定是否关闭提示。这套组合既满足了无人值守录屏的业务需求,又不至于把系统的隐私保护完全打开。毕竟“技术上能不能做”是一回事,“产品上应不应该全开”是另一回事。

改完这轮MediaProjection相关代码,我自己最大的感受就是:别把“去弹窗”想成一个点,它是一整条链路上的状态同步问题。从权限申请、服务端授权、会话创建到SystemUI提示,每个环节都像串联的灯,只关掉其中一盏,另外几盏还会在某个时刻突然亮起来。先把整条链路跑通、理解透,再动手改,会省下好几轮测试时间。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 3:37:57

YOLOv11多尺度包裹识别与姿态估计在物流分拣线的实战指南

简介&#xff1a;这份34页PDF技术文档聚焦物流分拣系统的智能化升级&#xff0c;面向从事计算机视觉、物流自动化或目标检测开发的工程师与研究者&#xff0c;可作为YOLOv11落地的系统参考。内容从传统分拣系统的准确性与效率瓶颈切入&#xff0c;详细讲解YOLOv11的骨干网络、颈…

作者头像 李华
网站建设 2026/9/30 3:37:17

宝塔面板+Docker部署青龙面板:完整流程与避坑指南

青龙面板这类定时任务管理工具&#xff0c;这两年玩的人特别多。我见过不少人第一次部署时&#xff0c;直接在服务器上装Node环境、clone代码、手动改端口、用screen挂着进程&#xff0c;一套操作下来&#xff0c;依赖版本冲突、进程守护、开机自启这些问题轮番折腾&#xff0c…

作者头像 李华
网站建设 2026/9/30 3:36:41

MySQL调优最重要的两个参数:缓冲池与刷盘策略

1. 先说结论&#xff1a;为什么我把“最重要”框死在两个参数上前阵子帮朋友接手一台“已经调优过”的MySQL服务器。配置文件翻开来&#xff0c;洋洋洒洒改了三十多个参数&#xff0c;从max_connections到tmp_table_size&#xff0c;从query_cache_type到key_buffer_size&#…

作者头像 李华
网站建设 2026/9/30 3:36:09

IEEE 802.1Qca-2015解析:TSN显式路径控制、带宽预留与冗余保护

简介&#xff1a;IEEE 802.1Qca-2015 是 IEEE 802.1Q-2014 的修订版&#xff0c;全称即“局域网与城域网——桥与桥接网络 第24号修正案&#xff1a;路径控制与预留”&#xff0c;为以太网桥接网络增加显式路径控制、带宽预留与冗余保护能力&#xff0c;是 TSN 时间敏感网络协议…

作者头像 李华
网站建设 2026/9/30 3:36:08

TMDS编码算法解析:FPGA实现RGB转HDMI的关键技术

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 3:36:05

Python代码质量守门员:Pylint与Flake8静态检查实战指南

1. 为什么需要静态检查&#xff1a;两个工具帮我守住了代码底线1.1 先看一个让人头大的代码评审现场我参与过不少Python项目的评审&#xff0c;最怕的就是那种“变量乱起名、函数几百行、import堆在文件中间”的代码。改起来要命&#xff0c;review起来更是一肚子火。可问题在于…

作者头像 李华