ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Android 13/14/15默认授权指南:从pm grant到系统源码级方案

Android 13/14/15默认授权指南:从pm grant到系统源码级方案 做安卓开发和系统定制的兄弟这两年应该都体会过一种焦虑用户手上的ROM版本越来越新权限弹窗却越来越难缠。尤其是Android 13/14/15连续三代大版本更新把“运行时权限”机制收得越来越紧以前那种在代码里直接requestPermissions()然后用户无脑点允许的日子基本已经过去了。你在调试设备、做自动化测试、或者定制企业级设备的时候会频繁遇到一个需求——系统安装完应用之后某些关键权限能不能直接默认授权别让用户手动去点。先说清楚这个需求到底出现在哪些场景。做自动化测试的人最清楚跑monkey或者UiAutomator脚本的时候如果被测应用非要弹窗要定位、要存储、要通知权限脚本直接卡死没人坐在屏幕前帮你点“允许”。做系统定制的小伙伴更懂整机出货给行业客户客户要求预置的APP装上就直接能用不可能让终端操作工去设置里翻权限开关。还有做设备管理方案的朋友希望应用工厂出厂就拥有无障碍、电池白名单之类的特殊能力。我今天把我在Android 13、14、15上折腾出来的默认授权方案从简单的adb命令到系统源码级修改一次性梳理清楚你根据自己手上的设备和权限级别选能用的那套走。1. 权限机制变化为什么Android 13之后默认授权变成刚需1.1 从M到15运行时权限的“收紧”路径Android的运行时权限机制从6.0API 23开始成型但它不是一成不变的这十年的演变路径其实非常清晰——权限粒度越来越细授权的控制权越来越从开发者手里被收走。Android 12引入了大致位置权限ACCESS_COARSE_LOCATION和ACCESS_FINE_LOCATION二选一Android 13直接上线通知权限POST_NOTIFICATIONS、附近的WIFI设备权限NEARBY_WIFI_DEVICES和媒体权限细分READ_MEDIA_IMAGES/VIDEO/AUDIOAndroid 14又加了“仅访问部分照片”这种新的选择式授权模式Android 15则进一步把存储权限做成了更严格的分类管理。这个收紧路径对默认授权的影响是巨大的。举个最直接的例子Android 13之前你只要声明了READ_EXTERNAL_STORAGE权限在TargetSDK较低的情况下App装上去就能直接读SD卡但到了Android 13以上系统强制要求你去请求READ_MEDIA_IMAGES这类细化媒体权限而且这些权限默认都是拒绝状态没有任何例外。如果你在搞AOSP定制还在沿用老一套的“把权限加到AndroidManifest里就完事”你会发现装上之后依然无法读取用户相册这就是权限机制升级带来的坑。1.2 三类“默认授权”真实场景分析我实际接触过的大量“默认授权”需求归纳起来就三种形态你先对号入座后面跟着选方案。第一类是常规运行时权限的自动化授权比如存储、定位、相机、麦克风、通讯录。这类权限本身在系统里就是典型的dangerous permission危险权限漏洞在于可以直接用pm grant命令给也可以用系统源码里的DefaultPermissionGrantPolicy批量给。常见于测试机、演示机、行业PDA。第二类是特殊权限的预授权这部分麻烦不少。无障碍Accessibility、通知POST_NOTIFICATIONS、闹钟SCHEDULE_EXACT_ALARM、电池优化白名单REQUEST_IGNORE_BATTERY_OPTIMIZATIONS都属于特殊权限或者系统管控很严的权限。它们不是走普通授权通道而是走AppOpsManager或者系统设置表。你要是没搞清楚机制就会卡在“能grant但开关还是灰的”这种尴尬状态。第三类是企业设备管理场景下的策略授权这个相对高级一点适合做MDM移动设备管理方案。Device Owner应用可以通过DevicePolicyManager的权限策略接口直接对第三方App做批量授权甚至可以禁用用户对某些权限的修改权限。这个方案不需要系统签名但需要设备进入Device Owner模式也有一点限制后面我会说到。2. 最常用也最快的方案adb/pm命令直接授权2.1 pm grant的基础用法与限制边界先说大家最耳熟能详的adb shell pm grant。这个命令的本质是调用PackageManagerService的grantRuntimePermission接口把某个dangerous permission直接授予指定应用。语法非常简单# 授予应用存储权限 adb shell pm grant com.your.app android.permission.READ_EXTERNAL_STORAGE adb shell pm grant com.your.app android.permission.WRITE_EXTERNAL_STORAGE # 授予应用定位权限 adb shell pm grant com.your.app android.permission.ACCESS_FINE_LOCATION adb shell pm grant com.your.app android.permission.ACCESS_COARSE_LOCATION # 授予相机权限 adb shell pm grant com.your.app android.permission.CAMERA注意一个关键前提这个命令只能对dangerous permission生效。你在Manifest里声明的普通权限normal和签名权限signature调用这个命令会直接报Operation not allowed: package ...错误。而且目标应用的targetSdkVersion一般没有硬性限制但应用必须要在Manifest里声明这个权限否则系统会报“Permission ... is not a changeable permission type”。实测下来Android 13设备上直接用这个命令授予通知权限、粗略位置权限都是完全OK的。有个点要注意pm grant授予的权限不是永久锁死的如果用户去了系统设置页面手动把该权限关掉那授权状态就会被重置掉。所以它更适合“一次性部署”不适合做长期固化的系统方案。2.2 查看权限状态与撤销权限的配套命令配合检查权限状态我们要会用pm check-permission和dumpsys package。前者是快速判断应用是否持有某个权限后者能看到全部授权细节。# 查看某个权限是否已授予 adb shell pm check-permission com.your.app android.permission.ACCESS_FINE_LOCATION # 查看目标应用的所有权限授权信息 adb shell dumpsys package com.your.app | grep -A5 runtime permissions还有一个场景是你要批量给多个权限或者应用升级之后权限被系统重置回去了那就需要写个循环脚本。比如在测试机上给被测应用一次性授予十几个权限adb shell pm grant com.your.app android.permission.ACCESS_FINE_LOCATION adb shell pm grant com.your.app android.permission.ACCESS_COARSE_LOCATION adb shell pm grant com.your.app android.permission.CAMERA adb shell pm grant com.your.app android.permission.RECORD_AUDIO adb shell pm grant com.your.app android.permission.READ_PHONE_STATE adb shell pm grant com.your.app android.permission.READ_CONTACTS adb shell pm grant com.your.app android.permission.WRITE_CONTACTS在Android 14/15上这种命令的兼容性比想象中好但遇到部分权限已经拆成了READ_MEDIA_VISUAL_USER_SELECTED这种情况你需要先把新权限名授一遍老权限名系统会自动废弃。我踩过的坑是Android 14上读相册的权限从READ_EXTERNAL_STORAGE换成了READ_MEDIA_IMAGES结果脚本里还是老的权限名grant的时候系统直接安静地回一个SecurityException我排查了好久才反应过来是权限名本身变了不是设备的问题。2.3 一个特殊的坏消息Android 14/15对部分权限的grant限制这里我要重点提醒一个坑。不要以为所有危险权限都能靠pm grant搞定。在Android 14API 34和Android 15API 35上Google对一些敏感权限做了更严格的限制典型的例子是电话、短信、通话记录这几个权限。当你的应用targetSdkVersion升到34之后系统不再允许通过pm grant直接授予这些权限即使你声明了也没用。这是权限策略层面的修改SDK开发者根本没地方绕。如果必须要在新系统上给这类权限你只有几条路可以走要么把应用变成系统预置App放到/system/priv-app要么让自己成为默认拨号/短信应用通过Role机制要么就只能退回targetSdkVersion 33以下的旧版策略但Google Play商店有强制要求上架应用必须升到指定SDK版本这是行业大趋势躲不掉。所以我的建议是做常规运行时权限的自动化授权优先考虑非敏感权限一旦涉及短信/电话这类敏感权限直接走系统签名或角色机制才靠谱。3. 系统签名与预置App让权限跟随安装自动授予3.1 为什么系统App能自动获得危险权限很多做ROM定制的朋友都遇到过这个现象把一个App塞进/system/priv-app之后它声明的危险权限竟然不需要弹窗就直接生效了。这里面的机制不是系统App天然免疫而是PackageManagerService在安装和开机扫描阶段会根据一个特殊白名单自动帮它完成授权。这个白名单文件就是**privapp-permissions-platform.xml**。系统在启动预置App时会读取这个文件如果App声明的权限出现在白名单里且App是priv-app级别放在/system/priv-app目录就会在安装时直接授予该权限。如果白名单里没有写而且App又是privileged权限级别系统还会在日志里打警告告诉你缺少哪些权限的声明。所以做系统定制的时候你不一定要改代码写好这个XML文件就可以了。3.2 实操打包App进system镜像的完整步骤假设你要把一个名为com.example.secureapp的应用预置到Android 13/14/15的ROM里让它自动获得定位和相机权限操作路径大概分这几步。第一步将APK编译成系统应用并创建对应的privapp-permissions白名单!-- 文件位置frameworks/base/etc/privapp-permissions-platform.xml -- permissions privapp-permissions packagecom.example.secureapp permission nameandroid.permission.ACCESS_FINE_LOCATION/ permission nameandroid.permission.ACCESS_COARSE_LOCATION/ permission nameandroid.permission.CAMERA/ permission nameandroid.permission.READ_MEDIA_IMAGES/ /privapp-permissions /permissions第二步把APK放进/system/priv-app目录然后重新编译整个系统镜像。不要只塞APK不处理白名单否则某些权限在Android 14上会直接报错甚至导致系统UI崩溃。第三步开机之后可以验证一下一般语法是adb shell dumpsys package com.example.secureapp | grep -A10 runtime permissions如果你看到权限状态是grantedtrue就说明默认授权生效了。这种方式的优点是一劳永逸权限跟随系统安装自动授予而且用户即使去设置里手动关闭系统重启之后只要没做持久化还是可能给授权回来取决于具体ROM策略。缺点是必须得有系统签名能力还要重新整包编译系统适合有源码权限的ROM团队。3.3 签名权限与系统权限的区别这里要把“系统App自动授权”和“签名权限”分开聊很多人会被绕晕。签名权限就是Manifest里protectionLevelsignature的权限比如android.permission.INSTALL_PACKAGES、android.permission.MOUNT_FORMAT_FILESYSTEMS等。这类权限的授予规则很简单应用签名必须和系统签名一致系统才给授。你用pm grant去授这类权限也是不行的因为它不是dangerous permission命令直接拒绝。而系统App自动授权本质上是利用了privapp-permissions白名单机制它覆盖的是dangerous permission。也就是说即便应用不是系统签名只要它是priv-app并且写进了privapp-permissions-platform.xml危险权限它就能自动获得。但签名权限是另一套逻辑签名相同就是有签名不相同就是没有白名单机制对签名权限无效。所以你在定制ROM的时候要注意如果预置应用申请的是普通危险权限靠白名单就够了如果申请的是签名级权限那你必须让应用使用平台签名platform key来签名APK否则装进去也用不了。我见过不少团队打包APK时忘了换签名结果应用塞进系统里功能权限却缺这个少那个折腾了几天才发现是签名问题。4. 源码级默认授权修改DefaultPermissionGrantPolicy4.1 系统重生的默认授权引擎如果你有AOSP源码并且希望所有用户从设置里都能看到权限已默认允许而不是依赖privapp-permissions那一套白名单机制那就要动DefaultPermissionGrantPolicy这个类了。这个类是系统在首次启动、新用户创建、或者安装新应用时决定要不要默认授予危险权限的核心策略类。它的位置在源码里通常在这条路径下frameworks/base/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java关键方法叫grantDefaultPermissions系统会遍历所有已安装应用然后根据应用的包名、是否系统应用、是否预置应用等条件去调用内部方法给它授权。你可以在里面新增自己的授权规则比如// 伪代码示意仅展示结构 private void grantDefaultPermissionsForSystemPackage(PackageInfo pkg) { // 如果是我们的定制应用直接授予全部需要的权限 if (pkg.packageName.equals(com.example.secureapp)) { SetString permissions new ArraySet(); permissions.add(Manifest.permission.ACCESS_FINE_LOCATION); permissions.add(Manifest.permission.CAMERA); grantRuntimePermissions(pkg, permissions, true, userId); } // 其他原有逻辑... }这里的最后一个布尔参数如果传true表示即使应用已经在前台请求过权限也会被系统覆盖掉强制置为授权状态如果传false则只在应用尚未请求过权限时才生效。这个参数的选择非常关键。4.2 修一条UserInfo不是改完代码就完事我早期搞源码授权的时候犯过一个低级错误代码写对了编译也通过了刷机之后权限却还是没生效。查来查去最后发现问题出在权限数据只在首次开机时初始化。DefaultPermissionGrantPolicy的授权动作主要发生在“新用户创建”或“开机启动扫描预置应用”的时机如果你刷的是旧系统然后升级到新系统或者升级版本过程中数据分区没清系统不会重新去跑一遍授权逻辑。所以验证这个修改时第一件事就是清除数据后重新开机或者干脆整包清理后再测试adb shell pm update-lib -f # 这个不重要这里的坑主要在于不一定生效 # 正确姿势是恢复出厂设置或清除数据分区让系统重建用户环境 adb shell am force-stop com.example.secureapp adb shell pm clear com.example.secureapp adb reboot如果你是在刷机后的第一启动之前就把代码改了那每次都能触发完整逻辑。但如果你是OTA升级上去的就要特别注意因为目标应用可能已经存在DefaultPermissionGrantPolicy不会回头去修复已有应用的权限状态。解决办法要么是清除应用数据要么是强制触发一次grantDefaultPermissions逻辑后者比较麻烦建议直接用脚本把权限重新pm grant一遍。4.3 AOSP方案在Android 14/15上的适配坑Android 14、15分别对DefaultPermissionGrantPolicy做过一些内部结构调整比如把部分授权动作移到PermissionController模块中导致你改frameworks/base里的默认策略时发现有些代码路径已经不再被调用了。所以在Android 14/15上做源码级授权一定要先确认权限的最终决定权落在哪里。比如Android 14之后READ_MEDIA_VISUAL_USER_SELECTED这种“仅部分访问”的权限底层会走AppOpsManager的决策路径你单纯在DefaultPermissionGrantPolicy里授予权限可能不够还得配合设置默认的AppOpMode。再比如Android 15对通知权限的默认授权策略也有微调部分OEM在首发版本里甚至会把通知权限默认设为拒绝你要想在源码层面绕过去建议直接查一下你手上这个AOSP版本里NotificationManagerService的默认策略有没有变化。所以我的经验是做源码定制前先花半天读一下对应版本的权限相关代码别照搬旧版本的patch否则改了个寂寞。5. 特殊权限授权无障碍、通知、闹钟、电池优化5.1 无障碍权限没有pm grant但有settings命令无障碍权限BIND_ACCESSIBILITY_SERVICE不是dangerous权限pm grant压根不认它。在Android 13/14/15上无障碍服务的开启原理是系统把已启用的无障碍服务列表保存在Settings.Secure的enabled_accessibility_services字段中。所以要在默认授权阶段直接开启某个无障碍服务可以通过修改这个系统设置项实现。我写过一个自动化使能无障碍的脚本核心命令是下面这几条# 先设置无障碍服务组件名多个服务用冒号隔开 adb shell settings put secure enabled_accessibility_services com.example.app/com.example.app.MyAccessibilityService # 然后开启无障碍总开关 adb shell settings put secure accessibility_enabled 1注意如果你用的是Android 14以上系统某些OEM的ROM会在这个基础上加一层校验命令设置之后还要确认自己的服务是否真的被系统绑定。如果服务没起来大概率是组件路径写错了或者是服务声明里的android:accessibilityFlags有问题用adb shell dumpsys accessibility看实时状态会非常直观。5.2 通知权限的“假授权”陷阱通知权限是个很有意思的东西。POST_NOTIFICATIONS是Android 13新引入的权限虽然它会在设置界面显示成“通知”开关但它本质上还是可以被pm grant授予的运行时权限。可实际用起来很多人遇到了“授权了但通知还是不弹”的情况这里面的猫腻在于通知渠道NotificationChannel的优先级也会影响通知是否展示。比如你通过pm grant给应用授予了通知权限但如果应用自己创建的通知渠道被设置成了低优先级IMPORTANCE_MIN系统还是会默认不展示通知或者只在通知栏折叠区显示。这个跟权限授权是两码事排查的时候要先看一眼dumpsys notification里目标服务的channel状态别把所有锅都甩给权限模块。还有一个Android 14/15的新行为部分OEM在你第一次授权通知权限之后会弹出“通知是否显示在锁屏”之类的二次确认。这种属于厂商ROM层的自定义逻辑不是AOSP原生行为你要是遇到这种设备自动授权完之后还得额外设置一次锁屏通知显示权限具体字段通常是Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS或厂商自己的Settings.System字段。5.3 闹钟与电池优化也别死磕pm grantSCHEDULE_EXACT_ALARM在Android 12是特殊权限以前有人用pm grant授过但具体到不同版本行为不一致。更稳妥的做法是通过appops直接改默认值# 让应用在后台不限制 adb shell appops set com.example.app OP_RUN_ANY_IN_BACKGROUND allow # 允许精确闹钟 adb shell appops set com.example.app SCHEDULE_EXACT_ALARM allow # 忽略电池优化部分版本不适用但值得一试 adb shell appops set com.example.app OP_IGNORE_BATTERY_OPTIMIZATION allow这里要敲黑板的是appops的名字在不同Android版本上有变化比如Android 14上部分AppOps名称变成了android:schedule_exact_alarm这种带命名空间的形式。你可以先执行adb shell appops get com.example.app看当前所有操作模式再决定设置哪个字段。实测下来这一套命令在很多设备上是有效的但不保证100%通用毕竟OEM会魔改AppOps的字段集。5.4 特殊权限的自动授权“关闭”问题还有一个小众需求跟“默认授权”正好相反——有些ROM会把大量权限默认授予预置应用导致应用权力过大不符合安全规范。这种时候我们谈的已经不是“怎么授权”而是“怎么关掉默认授权”。解决办法有两个方向。第一个方向是收紧privapp-permissions白名单只保留真正必要的权限条目多余权限不写进XML系统自然不会自动授予。第二个方向是修改DefaultPermissionGrantPolicy在默认授权逻辑里跳过不需要授权的包名。这个操作要谨慎因为某些系统App比如SystemUI、Settings缺了权限会直接导致系统功能异常动之前先在测试机上跑一轮完整回归。6. 企业级设备管理方案Device Owner与权限策略6.1 DevicePolicyManager的权限自动授予能力如果你做的是B端设备而且不想编系统、不需要系统签名那推荐的方案是走**Device Owner设备所有者**模式。设备进入Device Owner模式之后可以通过DevicePolicyManager的setPermissionGrantState接口直接设置第三方应用的权限状态。// Java侧调用示意 if (mDevicePolicyManager.setPermissionGrantState( mAdminComponent, com.example.targetapp, Manifest.permission.CAMERA, DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED)) { // 授权成功 }这个接口能帮你把dangerous permission设为三种状态PERMISSION_GRANT_STATE_DEFAULT默认跟随用户正常流程、PERMISSION_GRANT_STATE_GRANTED强制允许、PERMISSION_GRANT_STATE_DENIED强制拒绝。实测在Android 13/14/15上这套API依然是稳定的只是对不同权限的支持度存在差异比如一些特殊权限通知、无障碍就不支持直接用这个方法得配合setUserControlDisabled或设置设备管理策略才能完全控制。6.2 自动授权策略与用户控制权权衡这个方案有个好消息是Device Owner可以禁用用户对某个权限的修改。通过setUninstallBlocked和setUserControlDisabled配合你可以让目标应用在权限设置页面显示为灰色不可点。这种锁定能力在行业设备上非常刚需比如扫码枪、POS机、医疗手持设备管理员不希望现场人员随意关闭定位或相机权限那就可以直接锁死。但有一个权衡要注意用户手动关闭权限后Device Owner策略并不会立刻覆盖。系统会在onUserAction的时候决定是否重置授权状态所以如果你的策略要保证“用户关了就强制开启”需要监听权限变化并重新调用setPermissionGrantState。这种写法需要一个常驻的前台服务或者一个设备管理广播接收器来触发而且要注意在Android 14里后台启动限制更严了别写了一个永远跑不起来的服务。6.3 推荐的企业部署组合拳我在真实项目里最常用的一套企业部署组合拳是将设备进入Device Owner模式可用adb shell dpm set-device-owner com.my.admin/.AdminReceiver或通过NFC/二维码配置。在Device Owner App里维护一个“预授权权限清单”自动遍历目标应用逐项调用setPermissionGrantState设置为GRANTED。对无障碍、通知等特殊权限额外用settings put secure写入设备有root或system权限时或者提供辅助页面让实施人员一次性配置。对目标应用设置setUserControlDisabled避免现场人员改配置。这套方案最大的优势是可以在不改系统源码、不使用平台签名的前提下实现类似定制ROM的默认授权体验而且后续升级迭代灵活性更高。缺点嘛就是必须在设备进入Device Owner之后才能自动化处理对初始部署的流程设计有一定要求。7. 常见问题排查与避坑技巧7.1 高频报错速查表现象可能原因解决方案Operation not allowed: ...权限不是危险权限或者目标应用未声明换用pm grant可授的权限名确认Manifest声明SecurityException: Permission ... is not a changeable permission type权限类型不匹配或Android 14限制敏感权限检查是否为短信/电话类权限换系统签名方案授权成功但功能仍异常权限粒度已拆分如READ_EXTERNAL_STORAGE被拆成媒体细分权限授予READ_MEDIA_IMAGES等或检查AppOps状态设置里权限是灰的系统禁用了运行时权限控制或Device Owner策略影响查看dumpsys package定位是哪个策略锁住的无障碍服务不生效组件名错误或服务声明问题用dumpsys accessibility查实际状态pm grant后重启失效非系统应用权限状态被系统重置改用系统预置或Device Owner方案7.2 一个必须避开的坑targetSdkVersion的“强制升级”陷阱很多人在Android 14/15上做默认授权遇到最多的坑是应用targetSdkVersion不匹配。举个例子你的测试应用targetSdkVersion还是29Android 14设备上系统已经默认不给你旧存储权限的授权通道了你pm grant一个WRITE_EXTERNAL_STORAGE命令倒是执行成功但应用实际访问公共目录时系统还是会拒绝因为系统层的存储策略已经切换了。处理办法是要么升级应用的targetSdkVersion到33/34以上切换成新权限模型要么干脆在源码或pm grant里把新权限一起授了。在做自动化测试时我会写一个针对不同系统版本分别授权的脚本判断设备SDK版本然后授予对应版本的权限名。例如# 判断Android版本 if [ $(adb shell getprop ro.build.version.sdk) -ge 33 ]; then adb shell pm grant com.example.app android.permission.READ_MEDIA_IMAGES adb shell pm grant com.example.app android.permission.READ_MEDIA_VIDEO else adb shell pm grant com.example.app android.permission.READ_EXTERNAL_STORAGE fi7.3 实战记录帮客户批量部署50台Android 14扫码枪最后分享一个实际项目帮客户部署一批Android 14的扫码枪。他们的应用需要相机、精准定位、通知、悬浮窗四个权限而且现场员工绝对不允许去设置里关闭。排查思路很简单第一步先用pm grant把相机、定位授了第二步用settings put secure把通知权限默认打开因为这台设备锁定在单一App模式下Kiosk模式第三步悬浮窗权限比较麻烦它属于特殊权限pm grant无效最后是靠Device Owner的setPermissionGrantState配合setUserControlDisabled控制住了。整个过程一天搞定后续也没出过权限丢失的问题。这个案例说明一个普适的经验默认授权没有银弹一定要分权限类型、分系统版本组合使用不同方案。先确认设备权限级别再从最快速的pm grant试起不行就上系统源码级还不满足就Device Owner顶上。搞清楚每一步背后系统在查什么你就能稳准狠地解决默认授权问题。我个人实际操作里还有一个小心得做完默认授权之后别急着发版先在开发者选项里开启“不保留Activity”和“后台进程限制”模拟一下极端场景再验证应用权限是否还能正常用。权限授权只是第一步真正扛得住用户折腾、扛得住系统回收的权限持久化才是稳妥方案该有的样子。
返回列表