Android 权限模型:从懵懂到通透

权限,是SDK和宿主应用之间最微妙的边界。说白了,它决定了你的SDK能碰什么、不能碰什么。我这些年处理过的隐私合规问题,十有八九都跟权限有关。

这一章,我们就把Android权限体系彻底聊透。你不仅要会写代码,还得明白为什么这么写,以及在隐私合规的大背景下,哪些红线不能碰。

3.1 旧权限体系与新的变化

Android 6.0(API 23)是个分水岭。在那之前,权限在安装时一次性授予,用户要么全同意,要么不安装。听起来省事,但问题很大——很多App要了跟功能无关的权限,用户毫无办法。

6.0之后,Google推出了运行时权限模型。应用可以在运行时动态请求权限,用户也能随时在设置里撤销。嗯,这才是用户该有的控制权。

对比维度 旧权限体系(API < 23) 运行时权限体系(API >= 23)
授权时机 安装时一次性授予 运行时动态请求
用户控制 几乎无法撤回 可随时在设置中关闭
权限分组 所有权限同等对待 分普通权限和危险权限(需弹窗)
对SDK的影响 宿主授权后SDK直接使用 SDK需判断权限状态,协调宿主申请

我个人习惯在设计SDK时就明确区分:哪些功能必须依赖运行时权限,哪些可以降级。比如读取联系人属于危险权限,但如果我们只是想获取联系人个数来做统计,完全可以换一种方式。

3.2 运行时权限申请:不是一句 checkSelfPermission 那么简单

很多新手SDK开发者以为只要调用 checkSelfPermission 返回 GRANTED 就完事了。你想想看,如果宿主的业务逻辑绕过了权限检查,底层SDK直接去读数据,会发生什么?对,会直接抛出 SecurityException 崩溃。

所以,SDK内部必须做好防御。

一个典型的运行时权限申请流程是这样的:


// SDK内部:检查权限
if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS)
        != PackageManager.PERMISSION_GRANTED) {
    // 权限未授予,不能执行操作
    // 需要回调宿主的权限请求逻辑
    callback.onPermissionRequired(Manifest.permission.READ_CONTACTS);
    return;
}
// 权限已授予,执行数据读取
doReadContacts();

为什么要回调宿主?因为弹权限窗口必须在 Activity 里发起。SDK不能偷偷弹窗,这是合规的基本要求。我曾经见过一个SDK自己在 Service 里弹 Dialog,结果权限回调永远收不到,因为系统根本不响应非 Activity 上下文的权限请求。

正确的做法是:SDK定义一个接口,让宿主在 Activity 中实现权限请求逻辑。SDK只负责判断、通知、回调。

小小技巧:可以用一个抽象类让宿主继承,或者直接暴露一个权限检查回调的接口。我个人推荐接口方式,耦合度更低。而且在 onRequestPermissionsResult 里,记得把结果回传给你的SDK。

3.3 隐私合规要求:你以为授权了就万事大吉?

这年头,合规比功能实现更重要。Google Play、各大应用商店对权限的审核越来越严。我整理了几个常见的坑:

  • 权限申请目的说明:每次请求权限时,建议用弹窗或 Snackbar 提前说明为什么需要这个权限。别上来就弹系统权限窗口,用户一脸懵。
  • 最小权限原则:你的SDK真的需要读取通话记录吗?还是只要读取设备状态就够了?能用一个“普通权限”解决的问题,绝不碰“危险权限”。
  • 权限被拒后的降级策略:用户拒绝权限后,你的SDK不能直接崩溃。至少保证其他不需要该权限的功能正常运作。我遇到过很多SDK,因为一个权限被拒,整个模块都瘫痪了。
  • 动态权限与隐私政策的关联:如果你的SDK收集了位置信息,务必在隐私政策中说明。而且位置权限必须在用户使用相关功能时才请求,不能在启动时一次性全要。

曾经踩过的坑:我在做一款地图SDK时,默认在启动时请求了“粗略位置”和“精确定位”两个权限。结果被应用商店以“未充分说明目的”驳回。后来我把粗定位作为默认选项,精确定位仅在用户点击“获取精确位置”按钮时才请求,才通过审核。

3.4 知识体系一览

下面这张图,把Android权限模型的核心脉络画清楚了。你可以收藏起来,写代码时当参考。

Android 权限模型知识体系 旧权限体系 (API < 23) 安装时一次性授权 用户几乎无法撤回 SDK无需考虑运行时状态 运行时权限体系 (API >= 23) 运行时动态请求 用户可随时在设置中关闭 SDK必须判断权限状态 运行时权限申请核心流程 checkSelfPermission → 未授权时回调宿主 → 宿主弹窗 → 结果回调 隐私合规要求 申请前说明目的 → 遵循最小权限原则 → 提供降级策略 → 关联隐私政策 不能从Service弹权限窗口 必须由Activity发起 用户拒绝后不要崩溃 提供优雅的降级方案

3.5 实践中的避坑清单

最后,总结一个实战清单。每次接入或设计SDK权限时,对照检查一遍:

  • 权限声明:AndroidManifest 里只声明你真正需要的权限,不要多余声明。比如你的SDK只用到网络状态,就不要声明READ_PHONE_STATE。
  • 权限检查:每次调用危险权限相关API前,必须检查权限是否已被用户授权。检查时机最好在API调用入口处,而不是构造函数里。
  • 权限回调:设计好宿主回调接口,确保 onRequestPermissionsResult 能准确地回传给SDK。我见过一些SDK忘了处理结果,导致权限状态永远停留在“未响应”状态。
  • 权限被拒:用户拒绝后,不要反复弹窗。可以给用户一个引导入口,让用户去设置里手动开启。但注意不要过度诱导,免得被应用商店判定为违规。
  • 权限变更:Android 11 引入了“权限自动重置”机制,如果应用长期未使用某个权限,系统会自动重置。你的SDK需要能感知这种变化。

权限这件事,往小了说是代码逻辑,往大了说是用户信任。你把权限模型吃透了,隐私SDK的数据迁移之路就好走了一大半。