Android 自定义权限

一、 Android 自定义权限核心机制解释

关键点:自定义权限不是运行时动态申请的权限,是安装期解析、一次性固化到应用包信息里的系统元数据,不是服务端装完之后可以 "事后补发"。

1、权限是谁注册到系统的

xml

xml 复制代码
<!-- 服务端App Manifest -->
<permission android:name="com.cars.xxxcenter.china.PERMISSION_STORAGE"
    android:protectionLevel="normal" />

只有定义这个<permission>的服务端 APK 安装的时候 ,Android PackageManager 才会把这条权限:com.cars.xxxcenter.china.PERMISSION_STORAGE注册到系统全局权限数据库。

  • 服务端没装 → 系统完全不知道这个权限字符串是什么,数据库里没有这条记录。
  • 权限名字字符串完全一样也没用,系统数据库没有注册条目,它就是未知权限

2、客户端<uses‑permission>做了什么

客户端 manifest:

xml

ini 复制代码
<uses‑permission android:name="com.cars.xxxcenter.china.PERMISSION_STORAGE" />

uses‑permission 只在客户端自己安装那一刻生效

  1. 安装客户端时,PackageManager 扫描客户端 manifest;

  2. 去系统权限数据库查找这个权限名字;

    • ✅ 如果此时服务端已经装好,权限已注册:就把该权限授予客户端,记录到客户端的权限集合;
    • ❌ 如果此时服务端还没装,数据库查不到该权限 → 标记为 unknown permission,直接丢弃,不会保存这条权限申请
  3. 安装完成之后,PackageManager 不会再回头重新扫描一遍客户端的 uses‑permission

⚠️ 重点:后续你再把服务端装上去,系统不会遍历所有已安装 App,补授予之前被忽略掉的 uses‑permission。客户端已经安装完毕,它的权限快照已经固化。

3、现象对应你的故障

  1. 先装 demo (客户端):

    • 此时服务端未安装,权限未注册;
    • demo 安装阶段uses‑permission遇到未知权限,被静默丢弃;logcat 输出Unknown permission: xxx
    • demo 的权限列表里,从来就没有拿到过这个自定义权限
  2. 后装服务端:

    • 服务端安装,把PERMISSION_STORAGE注册进系统;
    • 但是 demo 不会被重新处理,不会自动补发这个权限
  3. 运行时调用 checkCallingPermission():去查调用方 (demo) 已授予的权限集合,里面根本没有该权限,直接返回PERMISSION_DENIED,ContentProvider 返回 403。

✅ 修复方式:服务端先安装,再安装客户端 demo ;如果顺序错了,必须卸载重装客户端,触发重新解析uses‑permission

4、为什么 normal 级别也不能运行时动态申请?

  • normal/signature自定义权限,不属于危险权限
  • requestPermissions()只针对dangerous权限;
  • 自定义 normal 权限没有弹窗申请这一套逻辑 ,全部是安装时一次性授予,运行时无法主动申请补发。

这是 Android 非常经典的坑:跨 App 自定义权限强依赖安装顺序,很多车载项目都会踩。

5、可选的规避方案(不想严格控制安装顺序)

  1. 签名权限 signature:两个 App 使用同一个系统签名,不需要依赖这套自定义权限校验,直接信任调用方签名;车载系统开发最常用。
  2. 不用自定义权限,改用 UID 校验:getCallingUid()判断调用方的 uid、包名,自己做白名单校验,完全绕开自定义权限安装顺序问题。
  3. 启动时客户端检测:客户端运行时检测自己是否拥有该自定义权限,如果没有,提示用户 "请先安装服务端并重装本应用"。

一句话总结

自定义权限是服务端安装时注册到系统数据库 ;客户端的 uses‑permission 只在客户端自己安装那一刻查询一次数据库。客户端先装的时候权限还没注册,申请直接丢掉;就算后面装服务端,旧客户端不会被重新处理,权限不会自动补上。

二、如果A(客户端)和B(服务端)是系统应用,那么还有安装顺序限制吗?

A (客户端)、B (服务端) 都是系统应用(预置在 system/system_ext/product/vendor 分区,ROM 烧录进去)
分两种场景:整机开机 PMS 扫描预置 APKadb push 动态更新系统 app,行为完全不一样。

场景 1:整机 ROM 烧录,开机扫描全部预置系统 App(车机量产 ROM 最常见)

开机时PackageManagerService一次性扫描全部系统分区下所有 APK,不是按普通 install 安装流程走 稀土掘金。PMS 会先把所有<permission>全部收集注册进权限数据库,再处理所有 App 的<uses‑permission>。✅ 此时不受安装顺序困扰

  1. B(服务端,定义<permission>)、A(客户端,<uses‑permission>)都是镜像预置;
  2. PMS 第一轮遍历全部系统 APK,把 B 声明的PERMISSION_STORAGE注册;
  3. 第二轮处理 A 的 uses‑permission,权限已经存在,正常授予 A;
  4. checkCallingPermission()正常返回成功,不会出现Unknown permission

量产车机镜像,两个 app 都预置到镜像,不存在 "谁先装谁后装" 的坑

场景 2:adb push 更新系统 App(调试阶段,非常高频踩坑)

你调试车机经常做的:adb remount && adb push B.apk /system/priv-app/xxx,这是动态更新已经开机跑起来的设备 ,不是整机开机扫描。这里行为和普通第三方 app 几乎一致,依然存在顺序坑

  1. 设备已经开机运行,PMS 已经完成 systemReady;

  2. 如果设备上先 push 安装 A (客户端) ,此时 B 还没 push 上去;

    • A 的 uses‑permission 遇到未知权限,被静默丢弃;
  3. 后续再 push B (服务端),B 把自定义权限注册进系统;

  4. ⚠️ PMS 不会回头重新扫描 A 的 manifest,不会补发权限给 A

  5. A 依旧拿不到该自定义权限,checkCallingPermission返回 DENIED,报 403。

👉 调试阶段 push 更新系统 app,依然要遵守:先 push B(定义权限的服务端),再 push A(客户端) ;如果顺序搞反,必须 卸载 A,再 push A,触发重新解析 uses‑permission。

关键区分总结表

表格

场景 是否受自定义权限顺序坑影响 说明
整机烧录 ROM,两个 app 都是镜像预置 ❌ 不受 PMS 开机一次性扫描全部系统 APK,先收集全部 permission 再授权 uses‑permission
adb push 动态更新系统 App(调试) ✅ 仍然受 等价运行时安装,遵循普通 app 安装逻辑,顺序错权限丢失,需要重装客户端
data/app 下普通第三方 App ✅ 受 最经典坑,必须先装定义权限的服务端

补充两个车载项目高频踩坑点

  1. 跨分区预置 B 在system_ext,A 在product:整机烧录场景没问题;PMS 开机扫描所有分区的 manifest,权限可以跨分区注册使用。
  2. protectionLevel=signature 特殊行为 如果权限是signature两个 app 相同签名,整机烧录没问题;调试 push 动态更新,同样遵守上面规则:A 先 push、B 后 push,A 依然拿不到权限,需要重装 A稀土掘金。
  3. 不要误以为 "系统 app 就可以绕开自定义权限安装顺序",只对整机开机扫描生效;调试 adb push 更新系统 app,坑完全保留,这是车载调试非常容易踩的。

工程上的稳妥方案(车机项目建议)

  1. 量产镜像:两个 app 全部预置,无顺序问题;
  2. 调试脚本:写 shell 脚本,先 push 服务端 B,再 push 客户端 A
  3. 兜底:代码增加自检逻辑,客户端 A 启动检测是否拥有该自定义权限,缺失则打印明确 log,方便定位调试问题;
  4. 备选方案:放弃自定义权限,校验调用方 UID / 包名白名单,彻底规避整套机制坑。

对应你之前的 xxx项目:烧录整机镜像没问题;但你日常 adb push 替换 apk 调试,依然会踩这个权限丢失 bug。

三、queries的作用?

xml 复制代码
<queries> 
    <package android:name="com.cars.xxxcenter.china" /> 
</queries>

1、<queries>到底做什么

Android 11+((API30+)) 开始引入包可见性:

默认情况下,你的 App 看不到设备上绝大多数第三方 / 系统 App。

写上:

xml

xml 复制代码
<queries><package android:name="com.cars.xxxcenter.china"/></queries>

✅ 允许你的客户端 App:

  1. PackageManager.getPackageInfo() / resolveContentProvider() 查到 com.volvocars.datacenter.china 这个包;
  2. 可以查询到它的 ContentProvider 信息(authority)。

它不能:

  • 不会让不存在的 Provider 变成存在;

  • 不会帮你申请自定义权限;

  • 不能防止 Unknown authority 崩溃。

相关推荐
FungLeo5 小时前
Flutter 超长 StatefulWidget 拆分术:part of + extension on State 实战
android·flutter·dart
小王C语言6 小时前
MySQL 数据类型:数值类型、字符串类型、日期和时间类型、enum 和 set、find_in_set 查询
android·数据库·mysql
我命由我123456 小时前
Android 控件 - CardView(快速实现圆角)
android·java·java-ee·kotlin·android studio·android-studio·android runtime
码云数智-园园6 小时前
Android 全方位性能优化合集
android·性能优化
hunterandroid7 小时前
[Android 从零到一] Compose 动画实战:从 AnimatedVisibility 到复杂转场与手势联动
android
Knight_AL7 小时前
Lombok @Builder 踩坑:build() 前后对象类型不一样
android·java·开发语言
大黄说说8 小时前
Android 本地存储深度对比:SharedPreferences、MMKV、Room 数据库怎么选?
android·数据库
阿巴斯甜9 小时前
adb push .../AdapterService.apk 和 .../区别:
android
恋猫de小郭9 小时前
Flutter 的另外一种形态?社区 DartNative 要来了。
android·前端·flutter