ARTICLE DETAIL

资讯详情

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

Android 12到15权限模型变化与默认授权实战方案

Android 12到15权限模型变化与默认授权实战方案 前阵子帮客户提一台基于Android 13的行业平板做系统预装适配需求单上写着开机后自动授予扫码App定位、相机、存储权限不能让现场人员再手动去设置里点。这批设备要出货一千多台如果每一台都靠人工点弹窗光是权限配置就能耗掉两个人力一周。刚接到需求时我以为无非是写个开机广播调grantRuntimePermission真正做完才发现Android 13之后权限模型已经变了好几轮同样是默认授权应用权限五个版本就有五种不同脾气。这篇文章我把从Android 12到Android 15的实际适配过程、代码方案、踩坑记录一起整理出来包含两条核心路线一条是应用层授权器适合预置App已经打进镜像、不想改系统源码的场景另一条是AOSP源码层方案适合ROM厂商或深度定制项目。另外会把通知权限、特殊访问权限、AppOps这一类grantRuntimePermission搞不定的“漏网之鱼”单独拉出来说清楚。目标读者是ROM开发、系统集成、行业设备定制工程师以及被权限适配折磨过但找不到系统化资料的Android Framework入门者。1. 默认授权到底解决什么问题先看三种实际场景1.1 行业终端预装几百台设备不能靠手点我接触最多的是扫码枪、工业PDA、车载中控、医疗手持终端这类设备。它们有一个共性系统里预装的业务App就是设备的核心功能用户不是普通消费者而是仓库工人、司机、护士。这些操作人员没有精力去理解“后台定位权限”是什么意思也不可能在每次系统更新后重新点一遍授权弹窗。Android 6.0引入运行时权限后所有危险权限都需要App自己申请。Android 13又加了一个POST_NOTIFICATIONS通知权限等于把授权弹窗数量又往上抬了一截。一台设备正常使用至少需要授权定位、相机、存储、电话、通知这五类每次弹窗还要寻找“仅使用期间允许”和“始终允许”的区别对现场部署来说基本是灾难。所以行业终端只要出货量上了百台默认授权就不是“优化项”而是“必须项”。这里的核心诉求不是替用户做安全决策而是把部署门槛降到最低保证业务应用开箱即用。1.2 企业定制ROM与自动化测试企业定制ROM是第二个高频场景。比如考勤一体机、电子班牌、门禁终端往往需要禁用用户手动撤销某些权限否则业务就可能被误操作打断。这类场景不光要“默认授权”还需要“锁定权限状态”保证在任何情况下权限不被篡改。自动化测试对默认授权的需求更直接。App的权限弹窗是UI自动化最头疼的干扰源弹窗一出现用例就找不到控件。如果测试过程中每个机型都要手动处理权限CI流水线根本跑不动。所以测试工程团队通常会准备一批“已默认授权”的镜像把权限状态固化到系统里让用例在干净且确定的权限环境下执行。这三种场景的共同点都是同一个把权限授予从“交互式的人工点击”变成“无感的代码或配置”。区别只是触发时机不同——是安装时授予还是开机后授予还是首次启动前授予。1.3 三条技术路线怎么选预授权、白名单、自动化辅助我在项目里把默认授权方案分成三条路线先看一张对比表再逐个展开方案实现层级改动量适用场景局限应用层授权器grantRuntimePermissionApp层调用系统接口小只需预置一个系统签名App预装App已固定、不方便改ROM需要系统签名部分权限无法覆盖多用户场景要额外处理AOSP系统配置DefaultPermissionGrantPolicy / privapp-permissionsFramework层中需要编译系统ROM定制、设备厂商出镜像需要系统源码环境调试周期长ADB命令行辅助pm grant / appops set工具层零代码开发调试、小批量样机只适合测试量产不可复制实际项目中我通常建议“应用层授权器 AOSP白名单”组合使用框架层把系统签名App的权限路径打开应用层负责在开机后针对具体业务包执行授权。只依赖其中任何一条都会遇到死角这一点后面章节会反复提到。2. Android 13/14/15的权限模型变化老方案失效的根源2.1 Android 13新增权限带来的第一个坎Android 13API 33的权限变化不是小修小补而是新增了一批“半运行时”权限其中对默认授权影响最大的三个是POST_NOTIFICATIONS通知推送权限所有App弹通知前需要申请。这个权限和普通危险权限不同它更接近“开关型权限”。NEARBY_WIFI_DEVICES附近Wi-Fi设备权限替代旧的定位相关Wi-Fi扫描权限。App若未声明usesPermissionFlagsneverForLocation系统会要求同时具备定位权限这在预装授权时很容易漏。BODY_SENSORS_BACKGROUND后台身体传感器权限普通App基本用不上但有些健康类预装App会踩到。为什么说老方案失效因为在Android 12及之前很多人的做法是定期遍历权限列表并调用grantRuntimePermission这个方法对普通危险权限是有效的但到了Android 13POST_NOTIFICATIONS并不是纯粹的dangerous permission它在PermissionManager里的处理分支更接近“特殊权限”直接用grantRuntimePermission要么不生效要么授权了但通知仍然被系统拦截。我实测过多次授权结果显示grantedtrue但状态栏根本没有通知就是因为没有同步处理AppOps里的通知开关。另一个容易忽略的点是targetSdk版本。Android 13上如果预装App的targetSdk低于33系统会默认给一部分权限做兼容处理但一旦把targetSdk升到34或35这些兼容行为全部消失原来“好像能自动授权”的假象就会现出原形。2.2 Android 14哪些“默认值”被改了Android 14API 34没有像13那样新增一堆权限名但它把很多默认行为从“宽松”拧到了“严格”对默认授权场景影响非常大。首先对于targetSdk 34及以上的应用系统对后台行为、媒体权限部分访问、前台服务类型都加了新限制。比如读取媒体文件时部分访问权限READ_MEDIA_VISUAL_USER_SELECTED允许用户只授权部分照片这个“选中部分”的状态是普通的grantRuntimePermission无法预设的。其次Android 14加强了对外部存储、闹钟、电话相关权限的管理。SCHEDULE_EXACT_ALARM这类闹钟权限在Android 14上普通方式几乎无法静默授予即便系统应用也需要走AlarmManager.canScheduleExactAlarms()的检查流程。还有一个直接坑到我的点Android 14上如果授权器App和预装业务App不是同一个签名某些系统服务会在内部校验签名归属导致授权结果被悄悄忽略logcat里没有任何异常。这个在低版本上是不会发生的排查起来相当费时间。2.3 Android 15的进一步限制与兼容性清单Android 15API 35的权限相关变化更多是“收口”。它对前台服务类型做了更严格的管理对BOOT_COMPLETED后拉起服务的限制也更明显。因为默认授权器多半要在开机后做异步操作一旦涉及startForegroundService就必须严格遵循5秒内调用startForeground的约束否则直接抛ForegroundServiceStartNotAllowedException。另外Android 15对“所有文件访问”权限、通知权限、媒体权限的审核更细未声明合理用途时MANAGE_EXTERNAL_STORAGE这类权限即使设置了AppOps也可能在重启后被系统自动回收。我把几个版本对授权接口的影响做成清单方便收藏权限/能力Android 13Android 14Android 15grantRuntimePermission 普通危险权限可用可用注意targetSdk可用POST_NOTIFICATIONS 通知权限需配合AppOps仍需配合AppOps仍需配合AppOpsNEARBY_WIFI_DEVICES需声明usesPermissionFlags行为延续行为延续SCHEDULE_EXACT_ALARM需要单独处理更严格更严格MANAGE_EXTERNAL_STORAGE可用AppOps高版本可能被回收审核更严BOOT_COMPLETED后启动前台服务正常需指定类型类型限制更严多用户工作资料授权需遍历UserHandle需考虑资料状态行为延续这个表格不是官方文档的复述而是我在不同版本实机上反复验证后的结论。适配新版本Android时建议先把这张表过一遍能省下不少排查问题的时间。3. 核心方案一应用层默认授权器的完整实现3.1 授权原理从PackageManager到runtime-permissions.xml在写代码之前先把底层机制讲透。Android把权限分成安装时权限和运行时权限两类。安装时权限normal/install-time权限在App安装时由系统自动授予用户无感运行时权限dangerous权限则需要运行时用户确认。我们说的默认授权本质上是在运行时权限的流程上做文章绕过用户确认环节由系统级代码直接把权限写入授权状态。这个状态最终落在/data/system/users/{userId}/runtime-permissions.xml文件里每个包一行记录着权限名和granted标志。系统提供的写入接口就是PackageManager.grantRuntimePermission(String packageName, String permissionName, UserHandle user)。这个方法不是谁都能调的调用者必须满足以下条件之一持有android.permission.GRANT_RUNTIME_PERMISSIONS签名级权限与目标App使用相同签名系统Uid。换句话说普通App根本没法调用但系统预置App放在/system/priv-app目录下天然具备这个资格。这个限制是默认授权器必须预置为系统应用的根本原因。3.2 开机广播自动授权代码与配置下面直接上一个我在项目里验证过的完整实现。核心思路是开机广播触发授权服务服务遍历预设的包名和权限列表逐个调用grantRuntimePermission。先写广播接收器public class DefaultPermissionReceiver extends BroadcastReceiver { private static final String TAG DefaultPermissionReceiver; Override public void onReceive(Context context, Intent intent) { if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction()) || Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(intent.getAction())) { Intent service new Intent(context, DefaultPermissionService.class); try { ContextCompat.startForegroundService(context, service); } catch (Exception e) { Log.e(TAG, startForegroundService failed, e); } } } }再写授权服务核心逻辑public class DefaultPermissionService extends Service { private static final String TAG DefaultPermissionService; private static final String[] TARGET_PACKAGES { com.example.scanner, com.example.terminal }; private static final String[] TARGET_PERMISSIONS { Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO, Manifest.permission.POST_NOTIFICATIONS }; Override public void onCreate() { super.onCreate(); startForeground(1001, createNotification()); } Override public int onStartCommand(Intent intent, int flags, int startId) { grantAllPermissions(); stopForeground(STOP_FOREGROUND_REMOVE); stopSelf(); return START_NOT_STICKY; } private void grantAllPermissions() { PackageManager pm getPackageManager(); for (String pkg : TARGET_PACKAGES) { if (!isPackageInstalled(pm, pkg)) { Log.w(TAG, package not installed: pkg); continue; } for (String permission : TARGET_PERMISSIONS) { if (pm.checkPermission(permission, pkg) ! PackageManager.PERMISSION_GRANTED) { try { pm.grantRuntimePermission(pkg, permission, Process.myUserHandle()); Log.i(TAG, granted permission - pkg); } catch (Exception e) { Log.e(TAG, grant failed permission - pkg, e); } } } } } private boolean isPackageInstalled(PackageManager pm, String pkg) { try { pm.getPackageInfo(pkg, PackageManager.PACKAGE_INFO_BASIC); return true; } catch (PackageManager.NameNotFoundException e) { return false; } } private Notification createNotification() { NotificationChannel channel new NotificationChannel( permission_grant, Permission Grant, NotificationManager.IMPORTANCE_LOW); NotificationManager nm getSystemService(NotificationManager.class); nm.createNotificationChannel(channel); return new Notification.Builder(this, permission_grant) .setContentTitle(正在初始化权限) .setSmallIcon(android.R.drawable.ic_popup_sync) .build(); } }AndroidManifest.xml关键部分uses-permission android:nameandroid.permission.GRANT_RUNTIME_PERMISSIONS / uses-permission android:nameandroid.permission.RECEIVE_BOOT_COMPLETED / uses-permission android:nameandroid.permission.POST_NOTIFICATIONS / application android:directBootAwaretrue android:persistenttrue receiver android:name.DefaultPermissionReceiver android:directBootAwaretrue android:exportedtrue intent-filter action android:nameandroid.intent.action.BOOT_COMPLETED / action android:nameandroid.intent.action.LOCKED_BOOT_COMPLETED / /intent-filter /receiver service android:name.DefaultPermissionService android:exportedfalse android:foregroundServiceTypespecialUse android:directBootAwaretrue / /application注意几个关键点授权器App必须预置为priv-app否则GRANT_RUNTIME_PERMISSIONS权限根本拿不到。Android 14/15上启动前台服务必须在5秒内调用startForeground上面代码里我在onCreate中立刻启动了这是经过实测靠得住的写法。android:persistenttrue只能用于系统应用作用是防止进程被杀但重度依赖这个标志在较新Android上会有内存限制问题建议核心逻辑跑完就stopSelf。directBootAware的作用是让应用在用户解锁前也能接收到LOCKED_BOOT_COMPLETED广播但直接访问部分系统服务会受限这里我只用它兜底开机早期场景。3.3 时机、重试与状态持久化三个工程化细节第一时机问题。BOOT_COMPLETED并不是一个“所有系统服务都就绪”的时机它只保证系统启动到一定阶段。我在实际设备上发现个别情况下PMS还在扫描新安装包此时调用grantRuntimePermission有可能因为包还没扫描完而报找不到目标包。更稳妥的做法是在onStartCommand里做一个“延迟重试”机制比如5秒后重试一次最多重试3次。第二状态持久化。Android的运行时权限状态默认是持久化的正常重启不会丢。但如果预置App做了更新APK替换部分权限状态会被系统清掉触发重新授权。所以不能只在量产时跑一次最好在ACTION_PACKAGE_REPLACED时也触发重授权。注册广播时注意Android 8之后的隐式广播限制广播接收器必须显式声明包名或者做成显式Intent启动。第三多用户场景。上面代码用的Process.myUserHandle()只能给当前用户授权行业设备一般没有多用户但企业定制ROM可能会开访客模式。正确做法是遍历UserManager.getUserProfiles()或ActivityManager.getUsers()对每个用户都执行一次授权。要注意工作资料用户下很多权限的授予行为与主用户不同需要额外处理。第四幂等性。授权操作要设计成“每次开机都执行但不会造成副作用”。我见过有同事在授权逻辑里不加检查每次开机都把权限重新grant一遍导致个别预装App因为权限状态反复变化出现崩溃。建议先判断checkPermission结果再决定是否授予像上面代码里写的那样。4. 核心方案二走AOSP官方路线用配置项和系统组件预授权4.1 DefaultPermissionGrantPolicy系统默认授权的正统机制如果设备由你们团队自己出ROM我更推荐在系统源码层面做默认授权而不是靠一个应用层授权器在开机后“亡羊补牢”。AOSP里有一个专门负责默认授权的类叫DefaultPermissionGrantPolicy路径在frameworks/base/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java它的职责是PMS在系统准备阶段把预设的系统默认应用比如Settings、SystemUI、拨号器的权限按配置直接授予不需要用户参与。对ROM人员来说有两个可以动手的地方一是修改frameworks/base/packages/SettingsProvider/res/values/defaults.xml中的config_defaultGrantedPermissions数组把需要预授权的权限加进去二是在DefaultPermissionGrantPolicy中添加针对特定包的授权逻辑。我实际用过第二种方式在grantDefaultPermissions方法里追加一个自定义方法private void grantDefaultPermissionsToScannerApp(int userId) { final String packageName com.example.scanner; final SetString permissions new ArraySet(); permissions.add(Manifest.permission.ACCESS_FINE_LOCATION); permissions.add(Manifest.permission.CAMERA); permissions.add(Manifest.permission.RECORD_AUDIO); grantRuntimePermissions(packageName, permissions, true, userId); }然后在grantDefaultPermissions末尾调用它grantDefaultPermissionsToScannerApp(userId);这种方式比应用层授权器更早执行发生在PackageManagerService的systemReady阶段基本不会出现“包还没扫描完”的竞态问题。而且授权逻辑是系统代码普通App无法篡改安全性更高。当然代价也很明显需要完整编译系统镜像调试周期长。而且每次升级Android大版本都要重新适配源码。4.2 privapp-permissions 白名单的配置方法Android 9之后priv-app的权限管理出现了一个容易被忽略的硬约束所有priv-app不能隐式声明和持有权限必须在/etc/permissions/privapp-permissions-*.xml中显式声明。举个例子授权器App需要WRITE_SECURE_SETTINGS或GRANT_RUNTIME_PERMISSIONS如果只把APK丢进/system/priv-app不写XML白名单这些权限仍然拿不到。正确的配置文件长这样permissions privapp-permissions packagecom.example.permissionhelper permission nameandroid.permission.GRANT_RUNTIME_PERMISSIONS/ permission nameandroid.permission.WRITE_SECURE_SETTINGS/ permission nameandroid.permission.REQUEST_INSTALL_PACKAGES/ /privapp-permissions /permissions放到/system/etc/permissions/目录下赋予系统权限重启后生效。要注意文件名不能和其它权限文件重名推荐用privapp-permissions-你的模块名.xml命名。这个文件不是只给默认授权器用的凡是预置进priv-app目录的App只要用到签名级或系统级权限都要在这里声明。很多同事第一次适配Android 10以上设备时都会在这个环节栽跟头App明明在priv-app目录里权限却死活没有。4.3 何时需要修改PermissionController如果目标是“安装任何App时都自动授予所有请求的权限”那应用层授权器和DefaultPermissionGrantPolicy都不够彻底因为它们在App安装后的某个节点才介入可能错过了App启动初期的权限判断。这种情况下最干净的做法是修改PermissionController。PermissionController是Android 6.0之后专门负责权限弹窗和授权管理的系统App预装App在请求权限时最终都会走它的GrantPermissionsActivity。如果希望完全不弹窗可以修改PermissionController里GrantPermissionsViewHandler的默认逻辑把所有请求结果改成GRANTED或者直接让系统服务层跳过弹窗判断。但这个方案代价很大需要替换系统的PermissionController APK涉及SELinux策略、签名、升级兼容等一系列问题只推荐给有完整ROM开发能力的团队。对于大多数项目我认为并没有必要动到PermissionController把前面三层方案吃透就足够覆盖需求了。5. 兜底与特殊权限通知、特殊访问、AppOps的处理5.1 POST_NOTIFICATIONS这类“非危险权限”怎么默认开通很多人在Android 13上最抓狂的就是明明grantRuntimePermission已经对POST_NOTIFICATIONS返回成功但App的通知就是不显示。原因是系统对通知的最终裁决权在AppOps层而不是普通的runtime permission状态。Android 13之后通知权限的实际开关由OP_POST_NOTIFICATION这个AppOps操作决定。即使权限被grant了只要AppOps里的这个操作不是MODE_ALLOWED系统依然会拦掉通知。在代码里设置AppOps的方式有两种。一种是通过NotificationManager的接口NotificationManager nm getSystemService(NotificationManager.class); nm.setNotificationsEnabledForPackage(pkg, uid, true);另一种直接操作AppOpsManagerAppOpsManager appOps getSystemService(AppOpsManager.class); appOps.setMode(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg, AppOpsManager.MODE_ALLOWED);不过要注意AppOpsManager.setMode需要系统权限普通App同样调用不了。而且Android 13之后对通知AppOps的校验更严格如果目标App的targetSdk是33以上可能还需要同步设置setNotificationsEnabledForPackage才能完全生效。在开发调试阶段也可以用ADB命令直接验证adb shell appops set com.example.scanner POST_NOTIFICATION allow5.2 “特殊访问权限”的默认开启特殊访问权限和普通运行时权限完全不是一套机制用grantRuntimePermission对付它们基本无效。有几个常见的特殊权限需要单独处理WRITE_SETTINGS修改系统设置系统签名App可以直接通过Settings.Global.putInt()写入或者使用Settings.System.putString()。但普通App必须引导用户到“修改系统设置”页面手动开启。对默认授权来说要让系统应用持有WRITE_SECURE_SETTINGS权限后直接写入然后在AppOps里把OP_WRITE_SETTINGS设为allow。SYSTEM_ALERT_WINDOW悬浮窗设置AppOpsOP_SYSTEM_ALERT_WINDOW为MODE_ALLOWED。MANAGE_EXTERNAL_STORAGE所有文件访问Android 11之后普通应用无法通过grantRuntimePermission获取必须跳转到Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION。系统应用可以通过AppOps把OP_MANAGE_EXTERNAL_STORAGE设为allow但不同厂商ROM上的行为差异很大我遇到过几台设备需要同时写Environment.isExternalStorageManager()的缓存才能让App立刻感知到授权变化。REQUEST_INSTALL_PACKAGES安装未知应用通过AppOps设置OP_REQUEST_INSTALL_PACKAGES为allow但对部分厂商ROM还需要配合Settings.Global的未知来源开关。这些特殊权限的默认开启本质上是“系统绕过用户界面直接替用户做了决策”所以风险也最高。建议只在行业设备或企业定制ROM上使用普通消费者设备乱开这些口子基本等于给恶意软件留后门。5.3 AppOps 层的补充操作AppOps是Android 4.4引入的细粒度权限控制机制它不直接对应权限而是对应“操作”。比如OP_CAMERA、OP_RECORD_AUDIO、OP_NOTIFICATION。运行时权限授予后系统通常会同步设置对应AppOps但反过来并不成立AppOps被允许权限可能还没授予。有些系统组件只查AppOps不查权限所以默认授权时只做权限grant还不够需要根据目标App实际用到的操作码在AppOps层也做一遍预置。下面是我整理的常用操作码对照权限AppOps操作码适用场景POST_NOTIFICATIONSOP_POST_NOTIFICATION通知开关SYSTEM_ALERT_WINDOWOP_SYSTEM_ALERT_WINDOW悬浮窗WRITE_SETTINGSOP_WRITE_SETTINGS修改系统设置MANAGE_EXTERNAL_STORAGEOP_MANAGE_EXTERNAL_STORAGE所有文件访问REQUEST_INSTALL_PACKAGESOP_REQUEST_INSTALL_PACKAGES安装未知应用CAMERAOP_CAMERA相机一般权限grant后自动设置RECORD_AUDIOOP_RECORD_AUDIO麦克风一般权限grant后自动设置工程上建议写一个通用的setAppOpsAllowed方法把包名、Uid、操作码作为参数传入统一处理。注意在Android 12及以上AppOpsManager的unsafeCheckOpNoThrow返回值可能是空字符串需要用MODE_ALLOWED或MODE_DEFAULT做兜底判断。6. 验证、排错与安全边界别把设备搞成筛子6.1 验证授权是否生效命令行与日志写完了授权逻辑不能光看Logcat就说“成了”。我习惯用以下命令在真机上验证查看某个包所有被授予的权限adb shell dumpsys package com.example.scanner | grep -E grantedtrue|runtime查看某个权限的授权情况adb shell dumpsys permission android.permission.ACCESS_FINE_LOCATION在开发调试阶段手动授予/撤销adb shell pm grant com.example.scanner android.permission.ACCESS_FINE_LOCATION adb shell pm revoke com.example.scanner android.permission.ACCESS_FINE_LOCATION查看通知、特殊权限的AppOps状态adb shell appops get com.example.scanner授权后重启设备再查一遍确认runtime-permissions.xml有没有回滚。这一步尤其重要因为我发现某些厂商ROM会在重启时清理“非交互授予”的权限不加验证根本发现不了。6.2 常见的六个坑与排查思路我在多个项目里反复踩过这些坑列成清单排查优先级从高到低授权后重启就丢权限。优先检查授权器是否有directBootAware如果是解锁前就执行授权可能会写入Direct Boot模式下的用户空间用户解锁后这些写入不生效。解决方法是等用户解锁后重新授权或者同时监听ACTION_USER_UNLOCKED。权限显示granted但App仍然弹窗。这说明App侧的权限缓存没感知到变化或者它申请的是特殊权限。先检查AppOps状态再看App是否在自己的代码里调用了requestPermissions有些框架会在请求时覆盖系统状态。授权时报SecurityException。原因基本都是调用者没有系统签名或没有GRANT_RUNTIME_PERMISSIONS权限。把APK放到/system/priv-app并写privapp-permissions后重新编译镜像不要试图绕过这个限制。通知权限根本拿不到。跳过grantRuntimePermission直接通过NotificationManager或AppOps设置。BOOT_COMPLETED广播没触发。检查应用是否被用户强制停止force-stop后收不到广播检查应用有没有被设备厂商列入自启动白名单。行业设备的厂商ROM特别喜欢搞自己的后台管理需要在设置里把授权器App加入“自启动”或“不受限制”列表。在多用户或工作资料里授权失败。需要遍历所有用户重新执行授权脚本不能用单用户API凑合。6.3 安全与合规的经验默认授权是一把双刃剑做的时候稍微不留神设备就会变成筛子。我第一次做这类适配时图省事把预装扫码App的所有危险权限全开了结果那台设备在后续测试中装了一个带恶意模块的测试包后台直接开始读通讯录和位置。虽然是测试环境但也足够让人冒冷汗。现在的项目里我强制自己遵守几条原则最小授权只开业务实际用到的权限不图省事全量放开。扫码App没必要拿通讯录权限就不要出现在授权列表里。白名单审批每一款预装App的授权清单都要经过业务方签字确认不默认开放“以后可能用到”的权限。权限复核出厂前用脚本跑一遍dumpsys package把每个预装App的权限状态导出来做差异比对只要多了任何一条不在白名单里的权限立刻阻断出货。系统签名隔离授权器App和目标App如果要预置到priv-app务必使用设备厂商自己的签名体系不要用公开的共享签名否则一个应用的漏洞可能影响整个系统的权限边界。另外每次升级Android大版本都要把前面那张兼容性表重新过一遍。不要假设“代码没动就还生效”——Android的权限模块每个版本都在内部重构同样的接口换个版本可能就从“能跑”变成“跑了个寂寞”。6.4 一个快速定位权限配置问题的排查链路最后放一段我在现场常用的排查顺序三十秒内定位大多数权限配置问题。第一步确认包是否预置为priv-app。执行adb shell pm path com.example.scanner看路径是/system/priv-app/还是/system/app/或/data/app/。如果不在priv-app目录谈后面的系统级权限都是白搭。第二步确认privapp-permissions白名单有没有生效。执行adb shell dumpsys package com.example.scanner | grep -E grantedtrue|requested permissions如果声明了GRANT_RUNTIME_PERMISSIONS但grantedfalse说明白名单没配对或没塞进/system/etc/permissions/。第三步确认授权器有没有真的被触发。执行adb logcat -s DefaultPermissionService看有没有日志输出。第四步确认授权完成后的实时权限状态。执行adb shell dumpsys package com.example.scanner | grep grantedtrue把结果和预期授权表比对。第五步重启后再次执行第四步。如果状态变了再去看是不是Direct Boot模式或多用户写入问题。这套链路我基本不烧脑按顺序走一遍就能定位问题已经成了团队里的标准排查流程。做默认授权这几年最大的体会是它远远不只是“写个广播调个接口”那么表层。Android的权限系统像一套层层嵌套的阀门权限申请是一个入口真正放不放行还要看AppOps、权限策略、SELinux、厂商定制这些后续关卡。哪怕是同一个Android版本不同厂商ROM的默认策略也能差出一大截。所以做这块项目文档和脚本一定要纳入版本管理每次换设备平台、升系统版本第一件事就是把授权清单重新跑一遍不要相信“上个项目没问题”的经验。如果设备量不大其实完全可以把授权配置抽成一份JSON或CSV用配置驱动代码不同项目换一张表就能复用省掉大量重复开发。
返回列表