3. 权限模型演进

Android 权限模型并非一开始就具备“运行时申请”的能力。早期 Android 采用安装时权限模型:应用在 AndroidManifest.xml 中声明所需权限,用户在安装界面一次性确认,安装完成后应用即可在后台自由使用这些权限。这种模型的问题在于,用户面对一长串权限列表时往往缺乏上下文,只能“全盘接受或放弃安装”,权限粒度粗、撤销困难,也难以解释权限的实际使用时机。

Android 6.0(API 23)引入运行时权限,把危险权限从“安装即授予”改为“使用时申请、用户逐项决定”。此后 Android 7 至 14 持续细化:权限分组、后台限制、一次性授权、自动重置、精确/模糊位置、通知权限、媒体细分权限、前台服务类型等逐步加入。理解这条演进线,是掌握 Android 权限模型与系统安全工程的基础。

Android 权限模型演进主线(6.0 → 14) 从安装时授权 → 运行时授权 → 精细化、可撤销、可审计 6.0 运行时权限 危险权限逐项申请 7.0 FileProvider 收紧 file:// 共享 8.0 权限组与后台限制 组内授权联动 9.0 后台传感器限制 前台服务需通知 10 后台位置 + 自动重置 仅前台/始终允许 11 一次性授权 仅此次允许 12 蓝牙/精确位置 近似位置可选 安装时权限(≤5.1) Manifest 声明 → 安装确认 授予后长期有效 用户难以逐项撤销 运行时权限(6.0+) 使用时 requestPermissions 用户可拒绝、可撤销 需处理拒绝与“不再询问” 精细化权限(10+) 一次性、仅前台、自动重置 通知、媒体、蓝牙细分 后台访问持续收紧 关键变化速览 • 6.0:危险权限运行时申请,权限组内授权联动 • 10:后台位置需单独申请;长期未用权限自动重置 • 11:一次性授权;12:精确/近似位置、蓝牙细分;13:通知权限;14:前台服务类型与媒体权限细分

一、核心原理:安装时权限与运行时权限的本质差异

安装时权限的核心是“声明即授予”。应用在 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 都要重新检查权限行为变化,尤其是位置、通知、媒体和前台服务。
  • 可审计:记录权限申请与授权结果,便于排查线上“功能不可用”问题。