一、 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 只在客户端自己安装那一刻生效:
-
安装客户端时,PackageManager 扫描客户端 manifest;
-
去系统权限数据库查找这个权限名字;
- ✅ 如果此时服务端已经装好,权限已注册:就把该权限授予客户端,记录到客户端的权限集合;
- ❌ 如果此时服务端还没装,数据库查不到该权限 → 标记为 unknown permission,直接丢弃,不会保存这条权限申请;
-
安装完成之后,PackageManager 不会再回头重新扫描一遍客户端的 uses‑permission。
⚠️ 重点:后续你再把服务端装上去,系统不会遍历所有已安装 App,补授予之前被忽略掉的 uses‑permission。客户端已经安装完毕,它的权限快照已经固化。
3、现象对应你的故障
-
先装 demo (客户端):
- 此时服务端未安装,权限未注册;
- demo 安装阶段
uses‑permission遇到未知权限,被静默丢弃;logcat 输出Unknown permission: xxx; - demo 的权限列表里,从来就没有拿到过这个自定义权限。
-
后装服务端:
- 服务端安装,把
PERMISSION_STORAGE注册进系统; - 但是 demo 不会被重新处理,不会自动补发这个权限;
- 服务端安装,把
-
运行时调用
checkCallingPermission():去查调用方 (demo) 已授予的权限集合,里面根本没有该权限,直接返回PERMISSION_DENIED,ContentProvider 返回 403。
✅ 修复方式:服务端先安装,再安装客户端 demo ;如果顺序错了,必须卸载重装客户端,触发重新解析uses‑permission。
4、为什么 normal 级别也不能运行时动态申请?
normal/signature自定义权限,不属于危险权限;requestPermissions()只针对dangerous权限;- 自定义 normal 权限没有弹窗申请这一套逻辑 ,全部是安装时一次性授予,运行时无法主动申请补发。
这是 Android 非常经典的坑:跨 App 自定义权限强依赖安装顺序,很多车载项目都会踩。
5、可选的规避方案(不想严格控制安装顺序)
- 签名权限
signature:两个 App 使用同一个系统签名,不需要依赖这套自定义权限校验,直接信任调用方签名;车载系统开发最常用。 - 不用自定义权限,改用 UID 校验:
getCallingUid()判断调用方的 uid、包名,自己做白名单校验,完全绕开自定义权限安装顺序问题。 - 启动时客户端检测:客户端运行时检测自己是否拥有该自定义权限,如果没有,提示用户 "请先安装服务端并重装本应用"。
一句话总结
自定义权限是服务端安装时注册到系统数据库 ;客户端的 uses‑permission 只在客户端自己安装那一刻查询一次数据库。客户端先装的时候权限还没注册,申请直接丢掉;就算后面装服务端,旧客户端不会被重新处理,权限不会自动补上。
二、如果A(客户端)和B(服务端)是系统应用,那么还有安装顺序限制吗?
A (客户端)、B (服务端) 都是系统应用(预置在 system/system_ext/product/vendor 分区,ROM 烧录进去)
分两种场景:整机开机 PMS 扫描预置 APK 和 adb push 动态更新系统 app,行为完全不一样。
场景 1:整机 ROM 烧录,开机扫描全部预置系统 App(车机量产 ROM 最常见)
开机时PackageManagerService一次性扫描全部系统分区下所有 APK,不是按普通 install 安装流程走 稀土掘金。PMS 会先把所有<permission>全部收集注册进权限数据库,再处理所有 App 的<uses‑permission>。✅ 此时不受安装顺序困扰:
- B(服务端,定义
<permission>)、A(客户端,<uses‑permission>)都是镜像预置; - PMS 第一轮遍历全部系统 APK,把 B 声明的
PERMISSION_STORAGE注册; - 第二轮处理 A 的 uses‑permission,权限已经存在,正常授予 A;
checkCallingPermission()正常返回成功,不会出现Unknown permission。
量产车机镜像,两个 app 都预置到镜像,不存在 "谁先装谁后装" 的坑。
场景 2:adb push 更新系统 App(调试阶段,非常高频踩坑)
你调试车机经常做的:
adb remount && adb push B.apk /system/priv-app/xxx,这是动态更新已经开机跑起来的设备 ,不是整机开机扫描。这里行为和普通第三方 app 几乎一致,依然存在顺序坑:
-
设备已经开机运行,PMS 已经完成 systemReady;
-
如果设备上先 push 安装 A (客户端) ,此时 B 还没 push 上去;
- A 的 uses‑permission 遇到未知权限,被静默丢弃;
-
后续再 push B (服务端),B 把自定义权限注册进系统;
-
⚠️ PMS 不会回头重新扫描 A 的 manifest,不会补发权限给 A;
-
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 | ✅ 受 | 最经典坑,必须先装定义权限的服务端 |
补充两个车载项目高频踩坑点
- 跨分区预置 B 在
system_ext,A 在product:整机烧录场景没问题;PMS 开机扫描所有分区的 manifest,权限可以跨分区注册使用。 - protectionLevel=signature 特殊行为 如果权限是
signature,两个 app 相同签名,整机烧录没问题;调试 push 动态更新,同样遵守上面规则:A 先 push、B 后 push,A 依然拿不到权限,需要重装 A稀土掘金。 - 不要误以为 "系统 app 就可以绕开自定义权限安装顺序",只对整机开机扫描生效;调试 adb push 更新系统 app,坑完全保留,这是车载调试非常容易踩的。
工程上的稳妥方案(车机项目建议)
- 量产镜像:两个 app 全部预置,无顺序问题;
- 调试脚本:写 shell 脚本,先 push 服务端 B,再 push 客户端 A;
- 兜底:代码增加自检逻辑,客户端 A 启动检测是否拥有该自定义权限,缺失则打印明确 log,方便定位调试问题;
- 备选方案:放弃自定义权限,校验调用方 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:
PackageManager.getPackageInfo()/resolveContentProvider()查到com.volvocars.datacenter.china这个包;- 可以查询到它的 ContentProvider 信息(authority)。
它不能:
-
不会让不存在的 Provider 变成存在;
-
不会帮你申请自定义权限;
-
不能防止
Unknown authority崩溃。