3. 权限模型演进
Android 权限模型并非一开始就具备“运行时申请”的能力。早期 Android 采用安装时权限模型:应用在 AndroidManifest.xml 中声明所需权限,用户在安装界面一次性确认,安装完成后应用即可在后台自由使用这些权限。这种模型的问题在于,用户面对一长串权限列表时往往缺乏上下文,只能“全盘接受或放弃安装”,权限粒度粗、撤销困难,也难以解释权限的实际使用时机。
Android 6.0(API 23)引入运行时权限,把危险权限从“安装即授予”改为“使用时申请、用户逐项决定”。此后 Android 7 至 14 持续细化:权限分组、后台限制、一次性授权、自动重置、精确/模糊位置、通知权限、媒体细分权限、前台服务类型等逐步加入。理解这条演进线,是掌握 Android 权限模型与系统安全工程的基础。
一、核心原理:安装时权限与运行时权限的本质差异
安装时权限的核心是“声明即授予”。应用在 Manifest 中写入 <uses-permission android:name="android.permission.CAMERA"/>,系统在安装阶段把权限与包名绑定,之后应用调用 Camera API 时不再询问用户。它的安全假设是:用户在安装时已经理解并同意全部权限。但现实中,用户很难判断“手电筒应用为什么需要读取联系人”,因此该模型逐渐失效。
运行时权限的核心是“按需申请、逐项决策”。危险权限被归入权限组,例如 CAMERA、RECORD_AUDIO、ACCESS_FINE_LOCATION 等。应用在真正使用功能前调用 requestPermissions(),系统弹出对话框,用户可选择“允许”或“拒绝”。Android 6.0 至 10 期间,同一权限组内一个权限被授予,组内其他权限可能被系统视为已授权;Android 11 之后,这种组内联动被进一步弱化,授权粒度更细。
关键结论:运行时权限不是“把安装弹窗挪到运行期”,而是把权限决策从一次性、不可解释的安装确认,变成可解释、可撤销、可审计的运行时契约。
二、方法流程:运行时权限的标准申请路径
在 Android 6.0 及以上,申请危险权限通常遵循“检查—解释—申请—处理结果”的闭环。以下代码展示了相机权限的典型申请流程:
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.CAMERA)) {
// 向用户解释为什么需要相机权限
showCameraRationale();
} else {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CAMERA},
REQ_CAMERA);
}
} else {
openCamera();
}
@Override
public void onRequestPermissionsResult(int requestCode,
String[] permissions, int[] grantResults) {
if (requestCode == REQ_CAMERA) {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
openCamera();
} else {
// 用户拒绝:降级功能或引导到设置页
showPermissionDeniedTip();
}
}
}
这段代码体现了运行时权限的三个关键判断:是否已授权、是否需要解释、用户拒绝后如何降级。尤其要注意 shouldShowRequestPermissionRationale() 的返回值:首次申请前通常为 false;用户拒绝过一次后为 true;用户勾选“不再询问”后再次为 false。应用不能假设用户一定会同意,也不能在拒绝后反复弹窗骚扰。
三、Android 6.0 至 14 的关键变化清单
| 版本 | 权限模型变化 | 工程影响 |
|---|---|---|
| Android 6.0(API 23) | 引入运行时权限,危险权限需在使用时申请 | 必须处理授权回调与拒绝分支 |
| Android 7.0(API 24) | 禁止 file:// 跨应用共享,推荐 FileProvider | 文件共享需配置 content URI 与临时授权 |
| Android 8.0(API 26) | 权限组内授权联动,后台执行限制加强 | 后台服务需改为前台服务或 JobScheduler |
| Android 9(API 28) | 后台传感器限制,前台服务需通知 | 传感器与前台服务需声明类型并展示通知 |
| Android 10(API 29) | 后台位置需单独申请;长期未用权限自动重置 | 位置权限拆分为“仅前台/始终允许” |
| Android 11(API 30) | 一次性授权;自动重置增强;后台位置二次确认 | 需适配“仅此次允许”与再次申请逻辑 |
| Android 12(API 31) | 精确/近似位置可选;蓝牙权限细分;麦克风/摄像头指示器 | 位置精度降级需可接受;蓝牙需新权限 |
| Android 13(API 33) | 通知权限独立;媒体权限拆分为图片/视频/音频 | 通知需运行时申请;媒体读取按类型申请 |
| Android 14(API 34) | 前台服务类型强制声明;媒体权限进一步细分 | 前台服务需匹配类型并说明用途 |
四、案例与场景:位置权限的演进最能说明问题
以地图导航类应用为例。Android 6.0 至 9 期间,应用申请 ACCESS_FINE_LOCATION 后即可在前台和后台获取位置。Android 10 起,位置权限被拆分为“仅前台”和“始终允许”,应用若要在后台持续定位,必须单独申请后台位置权限,并且系统会审查使用场景。Android 11 进一步要求后台位置不能与前台位置同时申请,用户可能先给“仅此次允许”,应用需要在下次启动时重新判断。Android 12 又加入“精确/近似”开关,用户可只给模糊位置,应用必须能接受精度下降。
这个案例说明:权限模型演进的方向不是“增加弹窗数量”,而是把权限决策拆解到更贴近使用场景的粒度。开发者需要把“权限被拒绝”视为正常状态,而不是异常状态。
常见误区:把 targetSdkVersion 停留在低版本以规避运行时权限。这种做法在新系统上会触发兼容模式,且应用无法上架 Google Play;更重要的是,它会让应用错过自动重置、一次性授权等安全能力,反而增加长期维护成本。
五、复盘:权限模型演进带来的工程要求
- 权限最小化:只申请业务真正需要的权限,能用系统选择器(如照片选择器)就不申请宽泛的存储权限。
- 场景化申请:在用户触发相关功能时申请,而不是启动时一次性申请全部权限。
- 拒绝可降级:用户拒绝后应提供替代路径或说明,而不是阻塞整个应用。
- 持续适配:每次提升
targetSdkVersion都要重新检查权限行为变化,尤其是位置、通知、媒体和前台服务。 - 可审计:记录权限申请与授权结果,便于排查线上“功能不可用”问题。