权限管理:Android权限模型演进、最小权限原则、运行时权限处理、自定义权限
聊到Android安全,权限管理绝对是个绕不开的话题。我做了这么多年安全开发,见过太多因为权限没处理好而翻车的案例。说白了,权限就是应用和系统之间的一道门——门开多大,什么时候开,开完要不要关上,这些都是学问。
Android权限模型的演进
Android的权限模型不是一天建成的。它经历了几个重要阶段,每个阶段都是为了解决上一阶段遗留的问题。
| 版本 | 权限模型 | 核心特点 |
|---|---|---|
| Android 1.0 - 5.1 | 安装时授权 | 安装时一次性授予所有权限,无法撤销 |
| Android 6.0 - 9.0 | 运行时权限 | 用户可在使用时授权,可随时撤销 |
| Android 10+ | 分区存储 + 权限细化 | 后台位置、媒体文件等权限进一步拆分 |
| Android 11+ | 一次性权限 + 自动重置 | 新增「仅本次允许」,长时间未使用自动重置 |
我记得刚入行那会儿,Android 4.4 还是主流。那时候权限管理特别粗暴——你装一个手电筒应用,它要读取你的联系人,你敢不给?不给就别想装。这种「全有或全无」的模式,说白了就是把用户架在火上烤。
到了 Android 6.0,Google 终于想明白了。运行时权限模型的出现,让用户可以在使用过程中决定是否授权。我个人觉得,这是Android安全史上最重要的转折点之一。
核心变化:从「安装时信任」转向「使用时信任」。用户不再需要提前承诺所有权限,而是按需授予。
最小权限原则
这个原则听起来简单,做起来真不容易。什么叫最小权限?就是你的应用只需要拿到完成功能所必需的最少权限,多一个都不要。
我曾经接手过一个项目,代码里直接声明了二十多个权限。一问开发人员,很多权限根本没用上。为什么?因为早期开发时图省事,把权限声明一股脑全写上去了。这种做法在Google Play审核时会被直接拒掉。
实践中有几个要点:
- 只声明你真正需要的权限——别想着「万一以后用得上」
- 能用普通权限就别用危险权限——比如用Intent调起相机而不是直接申请CAMERA权限
- 权限使用要有明确场景——用户问「为什么需要这个权限」,你得能说清楚
我的习惯:每次提交代码前,我都会再过一遍AndroidManifest.xml里的权限声明。问自己三个问题:这个权限真的需要吗?有没有替代方案?用户能理解为什么需要吗?
运行时权限处理
运行时权限是Android 6.0引入的核心机制。处理不好,轻则功能异常,重则直接闪退。我见过太多因为权限被拒绝后没有做兼容处理而导致崩溃的案例。
先看一个标准的权限请求流程:
// 检查权限是否已授予
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
// 权限未授予,判断是否需要显示解释说明
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.CAMERA)) {
// 用户之前拒绝过,解释为什么需要这个权限
showPermissionRationaleDialog();
} else {
// 直接请求权限
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CAMERA},
REQUEST_CAMERA_PERMISSION);
}
} else {
// 权限已授予,直接执行操作
openCamera();
}
这段代码看起来简单,但坑都在细节里。我来说几个容易踩的坑:
避坑指南:
- 不要假设用户一定会同意——每次使用前都要检查权限状态
- 用户勾选「不再询问」后——shouldShowRequestPermissionRationale会返回false,这时候需要引导用户去设置里手动开启
- 一次性权限(Android 11+)——用户选择「仅本次允许」后,应用下次启动时权限会被重置,必须重新请求
还有一个容易被忽略的点:权限回调的处理。很多开发者只在onRequestPermissionsResult里处理授权成功的情况,却忘了处理拒绝和永久拒绝的情况。
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CAMERA_PERMISSION) {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 授权成功
openCamera();
} else {
// 授权失败,需要区分是「拒绝」还是「永久拒绝」
if (!ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.CAMERA)) {
// 用户勾选了「不再询问」,引导去设置
showSettingsNavigation();
} else {
// 用户只是拒绝了一次,可以再次解释
showPermissionExplanation();
}
}
}
}
自定义权限
自定义权限是Android提供的一种机制,让你的应用可以定义自己的权限,供其他应用调用时使用。说白了,就是给你的组件加一把锁,只有持有对应钥匙的应用才能打开。
什么时候需要自定义权限?我举几个实际场景:
- 你的应用暴露了一个ContentProvider,只想让信任的应用访问
- 你的Service需要被特定应用调用,而不是谁都能调
- 你开发了一个SDK,需要限制调用方的身份
定义自定义权限的步骤:
<!-- 1. 在AndroidManifest.xml中声明权限 -->
<permission
android:name="com.example.myapp.permission.MY_CUSTOM_PERMISSION"
android:label="@string/permission_label"
android:description="@string/permission_description"
android:protectionLevel="signature" />
<!-- 2. 在组件上使用该权限 -->
<provider
android:name=".MyContentProvider"
android:authorities="com.example.myapp.provider"
android:permission="com.example.myapp.permission.MY_CUSTOM_PERMISSION" />
这里有个关键点:protectionLevel的选择。我见过不少开发者随便写个normal级别,那基本等于没设防。常用的级别有:
| 级别 | 含义 | 使用场景 |
|---|---|---|
| normal | 系统自动授予,无需用户确认 | 低风险操作,如通知权限 |
| dangerous | 需要用户运行时确认 | 访问敏感数据或操作 |
| signature | 只有相同签名的应用才能获取 | 自家应用之间的通信 |
| signatureOrSystem | 系统应用或相同签名的应用 | 系统级功能 |
我的建议:如果是自家应用之间的通信,优先用signature级别。这样既安全又省事——用户不需要额外授权,因为签名验证已经保证了调用方的身份。
嗯,这里还要注意一个细节:自定义权限的命名要遵循反向域名格式,避免和其他应用的权限冲突。我曾经遇到过两个第三方SDK用了相同的权限名,结果在同一个应用里直接冲突了,排查了半天才发现是权限名重复的问题。
知识体系总览
下面这张图把权限管理的核心脉络梳理了一下,你可以对照着回顾:
权限管理这件事,说难不难,说简单也不简单。关键是要养成好习惯——每次加权限前多想一步,每次处理权限回调时多写一个分支。嗯,这些细节积累起来,就是专业和业余的区别。
公众号:蓝海资料掘金营,微信deep3321