19、自定义权限:声明自定义权限、权限保护级别、运行时权限适配
权限这个东西,在 Android 里一直是个绕不开的话题。尤其是从 6.0 开始,运行时权限的引入让整个权限体系发生了质变。今天我们就来聊聊自定义权限——说白了,就是让你的 App 也能定义自己的权限规则,让别人访问你的组件时,得先过你这一关。
为什么需要自定义权限?
你可能会有疑问:系统权限已经那么多了,为什么还要自己定义?
我举个例子。假设你做了一个记账 App,里面有个「导出账单」的功能。你希望只有你自己写的另一个「家庭财务管理」App 才能调用这个导出接口,其他第三方 App 一概拒绝。这时候,系统权限帮不了你——它不知道什么是「账单导出」。你需要自己定义一个权限,比如 com.example.finance.permission.EXPORT_BILL,然后把这个权限绑定到你的 ContentProvider 或 Service 上。
说白了,自定义权限就是让你的 App 拥有「门禁系统」的自主权。
声明自定义权限
声明权限很简单,在 AndroidManifest.xml 里用 <permission> 标签即可。我习惯把它放在 <application> 外面,这样更清晰。
<permission
android:name="com.example.myapp.permission.READ_DATA"
android:label="@string/perm_read_data_label"
android:description="@string/perm_read_data_desc"
android:protectionLevel="dangerous" />
这里有三个关键属性:
- name:权限的唯一标识。注意命名规范,一般用包名倒序 + 权限名,避免冲突。
- label 和 description:给用户看的。label 要简短,description 要说明这个权限用来干什么。我记得 Google Play 审核时,如果 description 写得太模糊,可能会被拒。
- protectionLevel:权限保护级别。这是重点,下面单独讲。
权限保护级别详解
保护级别决定了系统如何处理这个权限。我整理了一张表,方便你对照:
| 保护级别 | 含义 | 是否需要用户同意 | 典型场景 |
|---|---|---|---|
normal |
低风险权限,不会直接泄露用户隐私 | 否,安装时自动授予 | 控制 App 是否显示通知 |
dangerous |
高风险权限,可能涉及用户隐私数据 | 是,运行时弹窗请求 | 读取联系人、访问相册 |
signature |
只有相同签名的 App 才能获取 | 否,系统自动判断 | 自家 App 之间的通信 |
signatureOrSystem |
系统应用或相同签名的 App 可获取 | 否 | 系统预装 App 的扩展功能 |
重点提醒:dangerous 级别在 Android 6.0 以上需要运行时请求,而 normal 和 signature 不需要。很多新手在这里踩坑——声明了 dangerous 权限,却忘了做运行时适配,结果一调用就崩溃。
运行时权限适配
嗯,这里要重点说说。自定义权限的运行时适配,和系统权限的流程完全一样。你想想看,用户安装你的 App 时,系统会提示「这个 App 需要访问某些权限」,但 只有 dangerous 级别的权限才会弹窗。
我曾经在一个项目里,给 ContentProvider 加了一个自定义权限,保护级别设成了 dangerous。结果测试时发现,另一个 App 调用这个 Provider 时直接抛了 SecurityException。排查了半天才发现——调用方没有在代码里请求权限。
正确的做法是这样的:
// 调用方 App 中,先检查权限
if (ContextCompat.checkSelfPermission(this,
"com.example.myapp.permission.READ_DATA")
!= PackageManager.PERMISSION_GRANTED) {
// 如果没有权限,就请求
ActivityCompat.requestPermissions(this,
new String[]{"com.example.myapp.permission.READ_DATA"},
REQUEST_CODE);
} else {
// 已有权限,直接调用
getContentResolver().query(uri, null, null, null, null);
}
这里有个坑:自定义权限的弹窗文案。系统弹窗里显示的 label 和 description,就是你之前在 <permission> 里定义的。如果 label 写的是英文,而用户手机是中文系统,那弹窗就会显示英文。我建议 label 和 description 都用 string 资源,做好多语言适配。
小技巧:如果你的自定义权限只给自家 App 用,强烈建议用 signature 级别。这样既不需要运行时请求,又能保证安全性。我自己的项目里,90% 的自定义权限都用 signature。
权限保护级别的选择策略
怎么选?我一般按这个思路来:
- 只给自家 App 用 →
signature。省心又安全。 - 给第三方 App 用,但风险低 →
normal。比如控制是否显示某个功能按钮。 - 给第三方 App 用,涉及用户数据 →
dangerous。必须做运行时适配。 - 系统级功能 →
signatureOrSystem。但普通开发者基本用不到。
你可能会问:如果我用 normal 级别,但实际暴露了敏感数据,会怎样?答案是:系统不会拦截,但 Google Play 审核会直接拒绝你的 App。所以别耍小聪明。
避坑指南:我曾经踩过的雷
我曾经在一个跨进程通信的项目里,定义了三个自定义权限,分别控制读、写、执行。结果因为权限名太相似,调用方搞混了,一直报权限拒绝。排查了两天才发现——权限名写错了一个字母。
所以我的建议是:
- 权限名用全大写 + 下划线,比如
READ_DATA、WRITE_DATA,一目了然。 - 在代码里把权限名定义成常量,不要硬编码字符串。
- 如果权限很多,写一个单独的
PermissionContract.java类来管理。
警告:Android 11(API 30)开始,系统对权限的可见性做了限制。如果你的 App 目标 API 30 以上,调用方可能无法通过 PackageManager.queryContentProviders() 找到你的 Provider。这时候需要调用方在 Manifest 里用 <queries> 声明对你们 App 的查询需求。
知识体系总览
下面这张图,我把自定义权限的核心流程和关键决策点画了出来,方便你整体把握:
总结一下
自定义权限其实不复杂,核心就三步:声明权限、选对保护级别、做好运行时适配。但细节决定成败——权限名别写错、label 做好多语言、dangerous 级别别忘了请求。做到这几点,你的自定义权限就能稳稳地跑起来。
嗯,关于自定义权限就聊这么多。如果你在实际项目中遇到什么奇葩问题,欢迎来公众号找我聊聊。
一句话记住:自定义权限是你的 App 的「门禁卡」,signature 是自家钥匙,dangerous 是访客登记,别搞混了。
公众号:蓝海资料掘金营,微信deep3321