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:权限的唯一标识。注意命名规范,一般用包名倒序 + 权限名,避免冲突。
  • labeldescription:给用户看的。label 要简短,description 要说明这个权限用来干什么。我记得 Google Play 审核时,如果 description 写得太模糊,可能会被拒。
  • protectionLevel:权限保护级别。这是重点,下面单独讲。

权限保护级别详解

保护级别决定了系统如何处理这个权限。我整理了一张表,方便你对照:

保护级别 含义 是否需要用户同意 典型场景
normal 低风险权限,不会直接泄露用户隐私 否,安装时自动授予 控制 App 是否显示通知
dangerous 高风险权限,可能涉及用户隐私数据 是,运行时弹窗请求 读取联系人、访问相册
signature 只有相同签名的 App 才能获取 否,系统自动判断 自家 App 之间的通信
signatureOrSystem 系统应用或相同签名的 App 可获取 系统预装 App 的扩展功能

重点提醒dangerous 级别在 Android 6.0 以上需要运行时请求,而 normalsignature 不需要。很多新手在这里踩坑——声明了 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

权限保护级别的选择策略

怎么选?我一般按这个思路来:

  1. 只给自家 App 用signature。省心又安全。
  2. 给第三方 App 用,但风险低normal。比如控制是否显示某个功能按钮。
  3. 给第三方 App 用,涉及用户数据dangerous。必须做运行时适配。
  4. 系统级功能signatureOrSystem。但普通开发者基本用不到。

你可能会问:如果我用 normal 级别,但实际暴露了敏感数据,会怎样?答案是:系统不会拦截,但 Google Play 审核会直接拒绝你的 App。所以别耍小聪明。

避坑指南:我曾经踩过的雷

我曾经在一个跨进程通信的项目里,定义了三个自定义权限,分别控制读、写、执行。结果因为权限名太相似,调用方搞混了,一直报权限拒绝。排查了两天才发现——权限名写错了一个字母。

所以我的建议是:

  • 权限名用全大写 + 下划线,比如 READ_DATAWRITE_DATA,一目了然。
  • 在代码里把权限名定义成常量,不要硬编码字符串。
  • 如果权限很多,写一个单独的 PermissionContract.java 类来管理。

警告:Android 11(API 30)开始,系统对权限的可见性做了限制。如果你的 App 目标 API 30 以上,调用方可能无法通过 PackageManager.queryContentProviders() 找到你的 Provider。这时候需要调用方在 Manifest 里用 <queries> 声明对你们 App 的查询需求。

知识体系总览

下面这张图,我把自定义权限的核心流程和关键决策点画了出来,方便你整体把握:

自定义权限知识体系 声明 <permission> 选择 protectionLevel normal / signature dangerous 安装时自动授予 运行时弹窗请求 关键决策:是否涉及用户隐私?是 → dangerous;否 → normal/signature 自家 App 通信优先用 signature,省去运行时适配

总结一下

自定义权限其实不复杂,核心就三步:声明权限、选对保护级别、做好运行时适配。但细节决定成败——权限名别写错、label 做好多语言、dangerous 级别别忘了请求。做到这几点,你的自定义权限就能稳稳地跑起来。

嗯,关于自定义权限就聊这么多。如果你在实际项目中遇到什么奇葩问题,欢迎来公众号找我聊聊。

一句话记住:自定义权限是你的 App 的「门禁卡」,signature 是自家钥匙,dangerous 是访客登记,别搞混了。


公众号:蓝海资料掘金营,微信deep3321