Android 14去掉录屏确认弹窗:反射、Device Owner与AOSP源码修改全解

发布时间:2026/10/5 8:02:14
Android 14去掉录屏确认弹窗:反射、Device Owner与AOSP源码修改全解 每次录屏、投屏、做自动化测试和远程协助最烦人的就是那个必须手动点一下的“开始录制或投射”弹窗。Android 14 上这个确认框依然在而且由于前台服务新规的存在处理起来比 Android 13 更麻烦。这篇文章我想把“去掉录制屏幕弹窗”这件事彻底聊透给出三条实际可落地的路径反射调隐藏 API、Android 14 新增的 Device Owner 白名单机制、以及直接改 AOSP 源码。每条路径我会说清楚原理、前提条件和实操细节方便你根据自己的身份普通应用开发者、系统应用维护者、ROM 定制者选一条真正能走通的方案。1. 授权弹窗是怎么出现的一条藏在 Android 14 改动里的链路先说清楚这个弹窗到底从哪来否则后面各种绕过方案都会变成无根之木。当你调用MediaProjectionManager.createScreenCaptureIntent()并把它交给startActivityForResult()之后系统并不会直接把屏幕内容交给你而是会启动 SystemUI 里的一个授权页面MediaProjectionPermissionActivity。这个页面展示了“开始录制或投射”“取消”两个按钮用户点允许之后系统才通过resultCode Intent把真正的投影对象IMediaProjection的 Binder 传回应用应用再调用getMediaProjection(resultCode, data)把它包装成MediaProjection实例后续才能创建VirtualDisplay去采集画面。这个流程从 Android 5 开始基本没变过但 Android 14 做了两件让开发者头大的事。第一个变化是MediaProjection的使用被强制绑定了前台服务。如果你的 ApptargetSdk是 34 或更高必须在 Manifest 里声明android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION并且在实际采集过程中启动一个foregroundServiceType为mediaProjection的前台服务。否则系统会直接抛SecurityException连createVirtualDisplay都到不了。很多人只关注怎么去弹窗忘记了这个前提结果反射拿到的投影对象一调用就崩。第二个变化是系统开始更严格地校验投影请求的调用方身份。以前某些私有 API 的反射方案在 Android 13 上还能碰运气到了 Android 14 上服务端权限校验路径被重新梳理过如果你没有系统签名或系统 UID直接在客户端反射调用底层接口很容易被SecurityException拦下。所以去弹窗这件事的难点不在于“让对话框不显示”而在于“让 MediaProjectionManagerService 认为这次请求已经获得了用户授权”。弹窗只是一个表象真正的开关在系统服务那一层。理解了这一点再看下面三条路径思路就清晰了让当前 App 拥有足够权限直接走系统预授权通道利用 Android 14 给 Device Owner 开的后门让系统自动判定你为可信任方从 AOSP 源码层面把弹窗逻辑从流程里摘掉。我在这篇文章里默认你已经能正常跑通录屏功能只是想去掉弹窗。如果你还不会最基础的MediaProjection VirtualDisplay录屏流程建议先对照官方 Demo 跑一遍再看后面的内容。2. 反射路线在 Android 14 上直接调 createProjection 绕过授权反射路线是所有方案里见效最快、适用范围最广、但“翻车”概率也最高的路线。它的核心思路是绕过MediaProjectionPermissionActivity这一层 UI 确认直接调用系统服务里的IMediaProjectionManager.createProjection()并传入isPermanentGrant true让系统认为这是永久授权不需要再弹窗询问用户。2.1 前置条件普通应用真的绕不过去先说一个必须面对的现实MediaProjectionManagerService 的createProjection方法内部有权限校验通常要求调用方持有android.permission.MANAGE_MEDIA_PROJECTIONsignature|privileged 级别或者具备 shell/root 身份。普通应用即便通过反射强行调到这个方法也会在服务端被权限检查拦下根本走不到“授权”那一步。因此反射方案的最基本前提是应用必须有系统签名平台签名不是任意一个你自签的 release 签名应用必须被安装到系统分区或者由厂商在 ROM 里预置为特权应用Manifest 里需要声明android.permission.CAPTURE_VIDEO_OUTPUT这是另一个签名级权限用来证明你有资格采集屏幕输出。如果你只是给普通用户设备上装一个第三方 App那反射这条路从一开始就走不通。别浪费时间直接看后面的 Device Owner 方案或者 AOSP 方案。2.2 Android 14 上可用的反射调用代码当你的应用满足系统签名这个前提之后剩下的就是纯反射技术活了。代码分三步走拿到IMediaProjectionManager服务代理、调用createProjection、构造MediaProjection实例。第一步通过隐藏服务media_projection获取IMediaProjectionManagerClass? serviceManagerClass Class.forName(android.os.ServiceManager); Method getServiceMethod serviceManagerClass.getMethod(getService, String.class); IBinder binder (IBinder) getServiceMethod.invoke(null, media_projection); Class? iManagerStubClass Class.forName(android.media.projection.IMediaProjectionManager$Stub); Method asInterfaceMethod iManagerStubClass.getMethod(asInterface, IBinder.class); Object iMediaProjectionManager asInterfaceMethod.invoke(null, binder);第二步调用createProjection。这里有个坑不同 Android 版本的 AIDL 方法签名不一样早期版本是(IBinder token, String packageName, int type, boolean isPermanentGrant)Android 11 之后陆续加入了IMediaProjectionCallback参数Android 14 上我见过的是(IBinder token, String packageName, int type, boolean isPermanentGrant, IMediaProjectionCallback callback)部分大版本还夹带了int uid参数。所以最稳妥的写法是遍历方法列表按方法名匹配然后根据参数个数动态补参Object iMediaProjection null; for (Method method : iMediaProjectionManager.getClass().getMethods()) { if (!createProjection.equals(method.getName())) { continue; } Class?[] parameterTypes method.getParameterTypes(); if (parameterTypes.length 4) { iMediaProjection method.invoke(iMediaProjectionManager, new Binder(), getPackageName(), 1, true); break; } else if (parameterTypes.length 5 IMediaProjectionCallback.class.isAssignableFrom(parameterTypes[4])) { Object callbackProxy Proxy.newProxyInstance( getClass().getClassLoader(), new Class[]{IMediaProjectionCallback.class}, (proxy, m, args) - null); iMediaProjection method.invoke(iMediaProjectionManager, new Binder(), getPackageName(), 1, true, callbackProxy); break; } }这里type传1表示MediaProjectionType.SCREEN_CAPTUREisPermanentGrant传true正是去弹窗的关键。IMediaProjectionCallback是一个 AIDL 接口客户端没有现成的实现类只能用 Java 动态代理生成一个空实现因为投影停止回调对我们来说不需要做额外处理。第三步把拿到的IMediaProjection包装成公开 API 里的MediaProjection对象。MediaProjection的构造器是非公开的需要反射构造Class? iProjectionClass Class.forName(android.media.projection.IMediaProjection); Class? projectionClass Class.forName(android.media.projection.MediaProjection); Constructor? constructor projectionClass.getDeclaredConstructor( Context.class, iProjectionClass); constructor.setAccessible(true); MediaProjection mediaProjection (MediaProjection) constructor.newInstance( this, iMediaProjection);拿到mediaProjection之后后续的createVirtualDisplay用法就和正常授权流程完全一样。2.3 Android 14 上反射方案最容易踩的两个坑先说前台服务。反射拿到的投影对象和正常授权拿到的MediaProjection在系统眼里没有区别所以 Android 14 的前台服务要求一样卡你。你必须在开始采集之前启动一个foregroundServiceTypemediaProjection的前台服务否则createVirtualDisplay一样会崩。Manifest 配置uses-permission android:nameandroid.permission.FOREGROUND_SERVICE / uses-permission android:nameandroid.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION / service android:name.RecordingService android:foregroundServiceTypemediaProjection android:exportedfalse /启动前台服务时也要显式传服务类型if (Build.VERSION.SDK_INT Build.VERSION_CODES.Q) { startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION); }第二个坑是createProjection的包名和 UID 不匹配。反射时如果你传入的packageName和当前调用方 UID 不一致系统服务在后续校验时会拒绝创建投影。所以在第二步最好直接从getPackageName()取真实包名不要硬编码字符串尤其不要抄网上的旧代码把包名写成别的。实测下来这套反射方案在 Android 14 的 AOSP 原生机上是能稳定工作的系统签名的 App 通过它录屏用户完全感知不到弹窗。但如果你的设备是深度定制的国产 ROM比如某些厂商把media_projection服务包名改掉或者加了额外的厂商权限校验反射就需要额外适配。这属于黑盒问题只能靠dumpsys media_projection和logcat慢慢排。3. Device Owner 路线用 Android 14 的 USER_CONTENT_CAPTURE_WHITELISTED_DEVICE_OWNER 绕开确认如果你不想编系统签名也没法把应用塞进系统分区但你的目标设备是可控的、专用设备那么 Android 14 给 Device Owner 开的新后门是更“政治正确”的路线。3.1 这套机制到底做了什么Android 14API 34新增了权限android.permission.USER_CONTENT_CAPTURE_WHITELISTED_DEVICE_OWNER同时DevicePolicyManager新增了setUserContentCaptureEnabled(ComponentName admin, boolean enabled)方法。当一个应用是当前设备的 Device Owner并且持有这个白名单权限时它可以调用setUserContentCaptureEnabled(admin, false)关闭设备的内容捕获。这个机制的本意是给企业设备管理场景用的企业管理员不希望设备上的内容被随便投屏或录制所以可以在系统层面关掉内容捕获。但反过来系统在处理MediaProjection请求时会对满足条件的 Device Owner 应用网开一面直接视为已经授权。表现在用户侧就是MediaProjectionPermissionActivity不再弹出录制流程直接走通。需要注意的是这条路要求的是 Device Owner不是普通的 Device Admin。普通设备管理员权限不够必须通过adb shell dpm set-device-owner或 MDM 方式把应用设为设备所有者。一台设备同一时间只能有一个 Device Owner这也是这条路最大的限制。3.2 完整配置与代码示例先在 Manifest 里声明白名单权限和设备管理员接收器uses-permission android:nameandroid.permission.USER_CONTENT_CAPTURE_WHITELISTED_DEVICE_OWNER / uses-permission android:nameandroid.permission.BIND_DEVICE_ADMIN / receiver android:name.AdminReceiver android:permissionandroid.permission.BIND_DEVICE_ADMIN android:exportedtrue meta-data android:nameandroid.app.device_admin android:resourcexml/device_admin_receiver / intent-filter action android:nameandroid.app.action.DEVICE_ADMIN_ENABLED / /intent-filter /receiver设备管理员接收器的实现public class AdminReceiver extends DeviceAdminReceiver { Override public void onEnabled(Context context, Intent intent) { super.onEnabled(context, intent); ComponentName admin new ComponentName(context, AdminReceiver.class); DevicePolicyManager dpm (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); if (dpm.isDeviceOwnerApp(context.getPackageName())) { dpm.setUserContentCaptureEnabled(admin, false); } } Override public void onUserContentCaptureDisabled(Context context, Intent intent) { super.onUserContentCaptureDisabled(context, intent); // Android 14 新增回调内容捕获被关闭时触发 } }res/xml/device_admin_receiver.xml里不能留空至少要有device-admin xmlns:androidhttp://schemas.android.com/apk/res/android /激活 Device Owner这里要注意激活条件。ADB 激活前设备上不能有已登录的账号企业账户、Google 账户等都需要先移除也不能已经存在其他 Device Owner。执行下面命令后设备会有一个设置向导一样的闪烁确认属正常现象adb shell dpm set-device-owner com.example.screenrecord/.AdminReceiver激活完成后在录屏代码里其实不需要写任何特殊的反射逻辑。你仍然用正常的createScreenCaptureIntent()加startActivityForResult()但弹窗已经不会再出现了系统会直接在授权回调里返回RESULT_OK和有效的Intent data你正常调getMediaProjection(resultCode, data)就能拿到投影实例。3.3 实测注意事项和兜底策略我在 Android 14 的 Pixel 设备上实测这套方案时发现一个有意思的细节setUserContentCaptureEnabled(admin, false)必须在 Device Owner 激活之后调用而且最好放到onEnabled或者应用首次启动时主动执行一次不能只依赖 BOOT_COMPLETED 广播。另外如果你是走adb shell dpm set-device-owner激活的激活完成后建议先重启一次设备再测试录屏因为某些 Android 14 厂商 ROM 对设备所有者状态缓存有延迟不重启可能不生效。还有一点必须提醒如果系统没有正确下发授权结果onActivityResult可能返回RESULT_CANCELED或者data为 null。这种时候不要死磕 Device Owner 链路直接在客户端补上一套第 2 章的反射兜底逻辑用反射createProjection拿到投影实例然后正常录屏。我的实际做法是先尝试标准getMediaProjection如果失败再走反射二者共用同一个MediaProjection封装层上层录屏逻辑完全不用改。这条路线非常适合机器人测试、Kiosk 模式设备、内网监控录制这类“一台设备只跑一个专用应用”的场景。它的优点是干净、不需要系统签名、不依赖厂商 ROM缺点是只能有一个 Device Owner而且会成为设备管理方案的一部分卸载、更换应用都要先解除设备所有者状态部署门槛比普通 App 高不少。4. 编译期方案从 MediaProjectionManagerService 根上把弹窗吞掉如果你本来就是做 ROM 定制的开发者不想在运行期搞反射那套脆弱的逻辑那最好的方式是直接在 AOSP 源码层面改掉这个弹窗。这条路最稳定没有黑盒问题也不需要担心不同 Android 版本隐藏 API 签名变化因为你直接改的就是系统。4.1 弹窗相关的两个关键代码位置第一个位置是 SystemUI 里的MediaProjectionPermissionActivity。这个 Activity 的任务就是弹窗让用户确认它的位置在packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java不同版本目录可能略有变化。最粗暴的思路是它直接识别白名单包名判断是可信调用方就马上设置RESULT_OK并finish()用户根本看不到 UI。但问题在于这个 Activity 需要构造返回给调用方的Intent里面包含MediaProjection的 Binder。这个 Binder 是从系统服务那边拿的Activity 本身不自产。所以更科学的改法是第二个位置。第二个位置是frameworks/base/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java。这是系统侧真正负责创建和授权MediaProjection的服务。正常情况下从createProjection到授权成功中间会经过startConsentDialog或者类似逻辑把请求转给 SystemUI 去弹窗。系统应用shell、system uid 等因为持有的权限足够会走“免弹窗”分支。这里就有一个非常好的切入点给特定包名也开放“免弹窗”分支。4.2 推荐修改思路包名白名单 永久授权我的建议是不要在 SystemUI 的 Activity 层面绕因为那只是“让弹窗不显示”授权流程还是绕了一圈直接修改MediaProjectionManagerService的授权判断逻辑更彻底。大致思路是在createProjectionInternal或类似的授权入口处增加一个白名单判断private boolean isProjectionSilentlyGranted(String packageName, int uid) { String[] whitelist mContext.getResources() .getStringArray(R.array.config_mediaProjectionSilentGrantPackages); if (whitelist null || whitelist.length 0) { return false; } String target packageName : uid; for (String item : whitelist) { if (target.equals(item)) { return true; } } return false; }在方法里如果白名单命中就直接把这次的 projection 标记为已授权不再走用户确认if (isProjectionSilentlyGranted(packageName, uid)) { projection.setGranted(true); }白名单配置放到config.xml中string-array nameconfig_mediaProjectionSilentGrantPackages itemcom.example.screenrecord:10123/item /string-array这里的10123是应用 UID。用“包名:UID”组合是为了避免不同用户或多用户场景下白名单误伤你也可以只按包名匹配按你团队实际维护成本来。改完系统服务之后客户端 App 本身不需要任何反射直接走标准 API 就能静默开始录屏。和反射方案相比这种编译期方案不受隐藏 API 签名限制也不会因为你改了MediaProjection构造器而在某个大版本升级时突然失效。缺点是每次系统大版本升级都要重新移植 patch而且 AOSP 代码不同版本的命名和逻辑位置会有差异patch 不保证直接 apply。4.3 为什么 adb shell screenrecord 不弹窗一个现成的参照AOSP 里其实已经有一个“免弹窗”的例子adb shell screenrecord。它执行录屏的时候用户不会看到任何确认弹窗。原因是 screenrecord 运行在 shell UID 下而 shell 在 MediaProjectionManagerService 里有明确的特权分支canProjectFromShell这类检查会对 shell 直接放行。这个现成的逻辑可以作为你的参照点。如果你在做 ROM 定制最简单的思路不是自己新写一套白名单机制而是参考 shell 的放行分支把你自己的包名也加入可放行列表。唯一的风险是安全边界退化了一旦某个应用被打上系统签名并写进白名单它就能在用户完全不知情的情况下录制任意画面。所以这个方案只适合企业内部固件、定制设备不适合面向普通消费者的 ROM除非你有足够的合规和隐私设计兜底。5. 路径怎么选授权模型、前提条件与实测验证技巧三条路都介绍完了很多读者这时候反而会纠结到底该用哪条。我个人的判断标准很简单看你能掌控到什么层级。5.1 三条路径的横向对比路径适用身份关键前提稳定性安全代价反射调用隐藏 API系统签名应用 / 特权预装应用平台签名、CAPTURE_VIDEO_OUTPUT 权限、目标设备 ROM 未特殊加固中依赖 AIDL 签名绕过用户确认隐私风险高Device Owner 白名单普通 App 设备管理员Android 14 及以上、设备可激活 Device Owner、无其他设备所有者高官方机制设备被企业化接管需合规部署AOSP 源码修改ROM 定制团队能编译和刷入系统最高彻底从源头去掉需要自行维护 patch升级成本高如果你只是给公司内部做一个录屏工具我建议优先尝试 Device Owner 路线。它不需要系统签名不用碰反射那套脆弱的代码而且 Android 14 官方加了这个白名单机制说明这条路在后续版本大概率会继续被支持。反射方案作为兜底保留不要作为首选。如果你本身就是做系统应用的那么反射方案可以让你快速验证弹窗能不能去掉但生产环境里我更建议把这段反射逻辑封装成一个库并且在启动时先检查FOREGROUND_SERVICE_MEDIA_PROJECTION权限和前台服务状态因为 Android 14 上静态反射成功只代表你拿到了投影对象不代表你能正常采集画面。采集阶段崩溃的案例我看过太多大部分问题就在“前台服务没起来”或者“投影被系统回收了你没注册回调”。5.2 如何去验证弹窗真的被去掉了验证这件事别靠肉眼建议用两个方法。第一抓日志。弹窗相关 Activity 是MediaProjectionPermissionActivity如果方案生效logcat 里应该看不到它创建和启动的日志adb logcat -s MediaProjectionPermissionActivity MediaProjectionManagerService正常授权成功后你能看到MediaProjectionManagerService里出现projection granted之类的状态日志但看不到 PermissionActivity 的 onCreate 日志。第二使用 dumpsys 查看当前媒体投影状态adb shell dumpsys media_projection输出里会列出当前正在进行的投影、调用方包名、是否 granted、持续时长等信息。只要你能看到自己的包名处于活动投影状态就说明整个链路已经打通。这里分享一个我在实测中踩过的细节反射方案下dumpsys media_projection里可能显示两个投影条目一个来自反射创建的投影另一个来自某些系统组件自建的临时投影。不要慌以你自己的包名那条为准。如果发现投影反复被系统回收多半是IMediaProjectionCallback动态代理没有正确保持引用被 GC 了。解决方法是把回调代理对象保存为成员变量不要用局部变量。5.3 一条合规红线去掉弹窗必须付出额外代价最后聊一个技术之外但更重要的问题——合规。去掉录制屏幕弹窗本质上就是让用户不知道自己正在被录制。这个能力一旦被滥用会直接变成恶意偷拍、窃取密码、绕过金融风控的工具。所以不管选哪条路径我都强烈建议你在产品层做两件事在应用内部增加显眼且不可跳过的录制提示比如一直在通知栏显示“屏幕录制正在进行中”或者给画面叠加一个固定水印不要把用户蒙在鼓里。在接单或需求澄清阶段确认去掉弹窗这个需求来自谁、用在哪里。如果是你自己开发的测试工具问题不大如果是交付给客户的产品就要在技术方案文档里明确隐私风险并让客户承担相应的合规责任。我见过不少团队只关心技术上能不能绕过弹窗从不考虑用户感知最后应用被应用市场审核打回。技术上的“能”不代表产品上的“应该”这一点希望你能记住。回到实践层面我个人在 Android 14 上给内网自动化录制工具选的方案是“Device Owner 为主、反射兜底”。先在开发设备上跑通标准授权流程确认录屏链路正常再激活 Device Owner 验证弹窗消失最后再把反射逻辑写进初始化模块里。这套组合帮我挡住了很多厂商 ROM 的意外行为也省去了反复编译刷机的痛苦。如果你的设备是 Pixel 或者 AOSP 原生机反射方案就已经够用如果你要交付的是整机固件那就老老实实走源码修改把那点安全顾虑交给产品设计去解决吧。