1. Android UID 的本质与作用
当你安装一个安卓应用时,系统会默默给它分配一个身份证号码——这就是UID(User ID)。它不像进程ID(PID)那样随时变化,只要应用不卸载重装,这个数字就会一直跟随它。想象一下公寓楼的门禁系统:UID就是每户人家的门卡权限,决定了你能进哪些区域、用哪些设施。
内核层通过UID实现沙盒隔离机制的核心原理:
- 文件权限控制:应用数据目录(如
/data/data/pkgname)的归属权限设置为u0_a123格式 - 进程隔离:Binder通信时会校验调用方UID
- SELinux策略:
seapp_contexts中基于UID配置安全上下文
通过adb shell ps看到的UID格式解析示例:
u0_a123 4567 789 /com.example.appu0:主用户(User 0)a123:应用ID=123(真实UID=10000+123=10123)
2. UID的生成规则揭秘
在android_filesystem_config.h中定义了系统预留UID:
#define AID_SYSTEM 1000 // 系统服务 #define AID_SHELL 2000 // adb shell #define AID_APP 10000 // 普通应用起始UID应用安装时的UID分配流程:
- 解析APK:读取
AndroidManifest.xml中的sharedUserId声明 - 查询数据库:检查
/data/system/packages.xml是否已存在相同共享UID - 计算最终UID:
- 普通应用:
10000 + appId - 多用户环境:
userId * 100000 + (10000 + appId)
- 普通应用:
关键源码路径:
PackageManagerService.java:处理安装逻辑Settings.java:维护packages.xml数据库UserHandle.java:UID计算工具类
3. 系统文件中的UID踪迹
通过以下文件可以追踪UID绑定关系:
| 文件路径 | 作用 | 示例内容 |
|---|---|---|
| /data/system/packages.xml | 记录所有包的UID和权限 | <package name="com.test" uid="10123"> |
| /data/system/packages.list | 简化版的包列表 | com.test 10123 0 /data/data/com.test |
| /proc/[pid]/status | 查看运行中进程的UID | Uid: 10123 10123 10123 10123 |
查看某个应用UID的三种方法:
# 通过包名查询 adb shell dumpsys package com.example.app | grep userId # 通过进程ID反查 adb shell cat /proc/1234/status | grep Uid # 列出所有应用UID映射 adb shell pm list packages -U4. 开发中的UID实战技巧
获取当前应用UID的代码示例:
// 通过ApplicationInfo获取 int uid = getApplicationInfo().uid; // 通过进程API获取 int uid = android.os.Process.myUid(); // Binder调用方UID检测 int callingUid = Binder.getCallingUid(); if (callingUid != Process.SYSTEM_UID) { throw new SecurityException("Only system can call this"); }多用户环境下的特殊处理:
fun getRealUid(context: Context): Int { val userId = UserHandle.getUserId(Process.myUid()) val appId = UserHandle.getAppId(Process.myUid()) return if (userId != UserHandle.USER_SYSTEM) { userId * 100000 + appId } else { appId } }调试时常用的ADB命令:
# 查看指定包名的UID adb shell pm dump com.example.app | grep "userId=" # 监控Binder调用UID adb shell su root cat /sys/kernel/debug/tracing/trace_pipe | grep uid5. UID与权限系统的联动
权限检查的核心链路:
checkPermission()调用PackageManager.checkUidPermission()- 查询
/data/system/packages.xml中的<perms>节点 - 对比
<item name="android.permission.CAMERA" granted="true"/>
特殊场景处理:
- 共享UID应用:需要相同签名和
sharedUserId声明 - 隔离服务:使用
android:isolatedProcess="true"会分配9xxxx范围的UID - WebView沙盒:固定使用
1053作为WebView zygote的UID
一个典型的权限拒绝日志分析:
W/PackageManager: Permission denied: uid=10123 requires android.permission.READ_CONTACTS6. 内核层的UID映射
Linux内核通过以下机制实现UID控制:
- 进程凭证:
struct cred中的uid/gid字段 - 文件系统:
stat()返回的inode属性包含UID - Binder驱动:在
binder_transaction()中校验sender_euid
关键内核代码路径:
kernel/cred.c:处理凭证管理drivers/android/binder.c:实现跨进程通信校验fs/proc/base.c:生成/proc/[pid]/status信息
通过strace观察权限检查过程:
strace -f -e open,access,stat adb shell am start-activity com.example.app7. 疑难问题排查指南
常见问题现象与解决方案:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 文件权限拒绝 | 应用目录归属错误 | chown u0_a123:u0_a123 /data/data/pkg |
| Binder调用失败 | 服务端未校验调用方UID | 添加Binder.getCallingUid()检查 |
| SELinux拒绝访问 | 安全上下文不匹配 | 调整seapp_contexts配置 |
| 多用户环境下功能异常 | 未处理跨用户UID转换 | 使用UserHandle.getUid()转换 |
我在调试跨进程文件共享时踩过的坑:当两个应用声明相同sharedUserId时,它们的/data/data/目录会合并。有次忘记在Manifest中声明android:sharedUserId,导致权限配置全部失效,最终通过分析packages.xml才发现配置遗漏。