要理解 Android 中的 Pid 和 Uid,我们先讲个生活故事 —— 把你的手机比作一座「智能商场」,每个应用都是商场里的「店铺」,而你是商场的「管理员」。接下来,我们用这个故事拆解 Pid 和 Uid 的本质,再结合代码和时序图讲透实现原理。
一、故事开篇:商场里的 “身份” 与 “活动”
假设你是「手机商场」的管理员,需要解决两个核心问题:
- 怎么区分不同店铺的身份? 比如 “外卖 A” 和 “外卖 B” 是两家不同的店,不能让 “外卖 A” 随便拿 “外卖 B” 的客户数据。这时候需要给每家店发一张唯一的身份证(Uid) ,凭身份证判断 “你是谁”,决定你能访问哪些资源(比如客户通讯录、位置信息)。
- 怎么管理店铺的临时活动? “外卖 A” 每天会开展 “接单”“推送消息”“更新数据” 等活动,每个活动需要单独的场地和人员。你需要给每个正在进行的活动发一个临时工号(Pid) ,凭工号判断 “哪个活动在运行”,方便调度资源(比如给 “接单” 活动分配更多 CPU,结束 “僵尸活动” 释放内存)。
故事对应 Android 概念
| 商场场景 | Android 概念 | 核心作用 |
|---|---|---|
| 店铺的身份证 | Uid(User ID) | 应用的唯一身份标识,管 “权限” 和 “数据隔离” |
| 活动的临时工号 | Pid(Process ID) | 进程的唯一运行标识,管 “调度” 和 “资源分配” |
举个具体例子:
- 你安装 “外卖 A” 时,系统(商场管理员)给它分配 Uid=10086(身份证终身有效,卸载才失效)。
- 你打开 “外卖 A”,系统为它创建一个 “接单进程”,分配 Pid=2023(工号仅在活动运行时有效,关闭 APP 后回收,下次打开可能变成 Pid=2024)。
- 如果你打开 “外卖 A” 的 “推送消息” 功能(多进程),系统会再创建一个进程,分配 Pid=2024,但它的 Uid 还是 10086(同一店铺的不同活动,身份不变)。
二、代码实证:看 Pid 和 Uid 长什么样
我们用两段代码,分别从「应用层」和「系统层」看 Pid 和 Uid 的获取与分配逻辑,小白也能看懂核心逻辑。
1. 应用层:获取自己的 Pid 和 Uid
每个应用都能轻松拿到自己的 Uid 和当前进程的 Pid,就像店铺自己看身份证和工号。
// 一个普通的 Activity,打印当前应用的 Uid 和 Pid
public class PidUidDemoActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 1. 获取 Uid:应用的“身份证”,安装时分配,卸载消失
// getApplicationInfo() 从系统获取应用信息,其中包含 Uid
int appUid = getApplicationInfo().uid;
// 2. 获取 Pid:当前进程的“工号”,进程启动时分配,进程结束回收
// Process.myPid() 是 Android 提供的静态方法,直接返回当前进程 ID
int currentPid = android.os.Process.myPid();
// 打印结果(日志里会看到类似:Uid=10086,Pid=2023)
Log.d("PidUidStory", "我的店铺身份证(Uid):" + appUid);
Log.d("PidUidStory", "当前活动工号(Pid):" + currentPid);
// 关键实验:开启多进程
// 在 AndroidManifest.xml 中给 Service 配置独立进程:
// <service
// android:name=".PushService"
// android:process=":push"/>
// 启动 PushService 后,它的 Pid 会变成新值(比如 2024),但 Uid 还是 10086
}
}
运行结果(日志):
D/PidUidStory: 我的店铺身份证(Uid):10086
D/PidUidStory: 当前活动工号(Pid):2023
如果启动 PushService(独立进程),日志会新增:
D/PidUidStory: PushService 进程工号(Pid):2024
D/PidUidStory: PushService 店铺身份证(Uid):10086
2. 系统层:Pid 和 Uid 是怎么分配的?
应用的 Uid 由 PMS(PackageManagerService,应用管理员) 安装时分配,Pid 由 AMS(ActivityManagerService,进程调度员) 启动进程时通过 Zygote(进程工厂) 分配。以下是简化的系统源码逻辑(看懂核心步骤即可)。
(1)UId 分配:PMS 的 “身份证发放逻辑”
PMS 是 Android 负责应用安装、卸载的核心服务,给每个应用分配唯一 Uid,规则如下:
- 系统应用 Uid 从 1000 开始(比如 Launcher 桌面 Uid=1000);
- 第三方应用 Uid 从 10000 开始(比如 “外卖 A” UId=10086);
- 同一开发者的应用(同一签名)若配置
sharedUserId,可共享同一个 Uid(比如 “外卖 A” 和 “地图 A” 共享 Uid=10086)。
// 简化的 PMS 分配 Uid 逻辑(系统源码层面)
public class PackageManagerService {
// 第三方应用 Uid 起始值(记住这个数字:10000)
private static final int USER_APP_UID_START = 10000;
// 下一个待分配的 Uid
private int nextAvailableUid = USER_APP_UID_START;
// 应用安装时调用:分配 Uid
public void installApp(PackageInfo appInfo) {
// 步骤1:检查是否需要共享 Uid(比如“外卖A”和“地图A”)
String sharedUserId = appInfo.sharedUserId;
if (sharedUserId != null) {
// 查找已有的 sharedUserId 对应的 Uid(同一签名才允许共享)
Integer existingUid = getUidBySharedUserId(sharedUserId, appInfo.signature);
if (existingUid != null) {
// 共享 Uid:直接复用已有的 Uid
appInfo.uid = existingUid;
Log.d("PMS", "共享 Uid:" + existingUid);
return;
}
}
// 步骤2:分配新 Uid(确保全局唯一)
while (isUidUsed(nextAvailableUid)) {
nextAvailableUid++; // 跳过已使用的 Uid
}
appInfo.uid = nextAvailableUid;
nextAvailableUid++; // 更新下一个待分配 Uid
// 步骤3:保存 Uid 到系统文件(/data/system/packages.xml)
saveUidToSystem(appInfo.packageName, appInfo.uid);
Log.d("PMS", "新应用 Uid:" + appInfo.uid);
}
// 辅助方法:检查 Uid 是否已被使用
private boolean isUidUsed(int uid) { /* 遍历已安装应用,判断 Uid 是否重复 */ }
// 辅助方法:根据 sharedUserId 和签名找已有的 Uid
private Integer getUidBySharedUserId(String sharedUserId, Signature signature) { /* 校验签名后返回 Uid */ }
}
(2)Pid 分配:AMS + Zygote 的 “工号发放逻辑”
Android 中所有应用进程都是从 Zygote 进程(进程工厂)fork(复制)出来的,Pid 由 Linux 内核分配,AMS 负责记录和管理。
// 简化的 AMS 启动进程逻辑(系统源码层面)
public class ActivityManagerService {
// 启动应用进程时调用
public void startAppProcess(String packageName) {
// 步骤1:先查应用的 Uid(从 PMS 拿“身份证”)
ApplicationInfo appInfo = PackageManager.getAppInfo(packageName);
int appUid = appInfo.uid;
// 步骤2:请求 Zygote 工厂创建新进程
ZygoteProcess zygote = getZygoteProcess();
// 向 Zygote 发送请求:fork 新进程,携带 Uid
int newPid = zygote.forkNewProcess(packageName, appUid);
// 步骤3:记录进程信息(Pid → Uid → 应用名的映射)
ProcessRecord record = new ProcessRecord();
record.pid = newPid; // 工号
record.uid = appUid; // 身份证
record.appName = packageName; // 店铺名
saveProcessRecord(record); // 保存到 AMS 管理列表
// 步骤4:通知新进程启动应用代码(比如 Application.onCreate())
bindAppToProcess(record);
Log.d("AMS", "新进程 Pid:" + newPid + ",对应 Uid:" + appUid);
}
}
// Zygote 进程:Android 的“进程工厂”,所有应用进程的父进程
public class ZygoteProcess {
// fork 新进程(调用 Linux 内核的 fork() 系统调用)
public int forkNewProcess(String packageName, int uid) {
// 步骤1:Linux 内核 fork 新进程,返回新进程的 Pid
// 注:fork() 会复制 Zygote 的内存空间(预加载的 Android 框架类),提高启动速度
int pid = nativeFork(); // 原生方法,调用 Linux 内核
if (pid == 0) {
// 步骤2:子进程(应用进程)逻辑
setProcessUid(uid); // 给新进程设置 Uid(和应用一致)
setProcessName(packageName); // 设置进程名(比如“com.waimai.a”)
loadAppCode(packageName); // 加载应用的 APK 代码
} else {
// 步骤3:父进程(Zygote)逻辑:返回 Pid 给 AMS
return pid;
}
return -1;
}
// 原生方法:调用 Linux fork() 系统调用
private native int nativeFork();
// 原生方法:设置进程的 Uid(Linux 层面的用户 ID)
private native void setProcessUid(int uid);
}
三、时序图:可视化 Pid 和 Uid 的生命周期
我们用两张时序图,清晰展示「应用安装分配 Uid」和「应用启动分配 Pid」的完整流程。
1. 流程 1:应用安装 → PMS 分配 Uid
系统存储 (packages.xml)PMS (应用管理员)安装器 (PackageInstaller)用户系统存储 (packages.xml)PMS (应用管理员)安装器 (PackageInstaller)用户点击“安装外卖A.apk”解析APK(提取包名、签名)请求安装(携带APK信息)校验签名合法性、包名唯一性分配 Uid=10086(从10000开始)保存“外卖A → Uid=10086”返回“安装成功”显示“安装完成”
2. 流程 2:应用启动 → AMS+Zygote 分配 Pid
权限管理 (PermissionManager)外卖A进程Zygote (进程工厂)PMS (应用管理员)AMS (进程调度员)桌面 (Launcher)用户权限管理 (PermissionManager)外卖A进程Zygote (进程工厂)PMS (应用管理员)AMS (进程调度员)桌面 (Launcher)用户点击“外卖A”图标请求启动“外卖A”查“外卖A”的Uid返回 Uid=10086请求fork进程(带Uid=10086)调用Linux fork(),生成Pid=2023子进程:设置Uid=10086、进程名加载APK,初始化Application返回Pid=2023记录“Pid=2023→Uid=10086→外卖A”通知启动MainActivity请求访问通讯录(带Uid=10086)检查Uid=10086是否有权限返回“允许”显示外卖A主界面
四、核心总结:Pid 和 Uid 的区别与联系
用一张表格讲透区别,再用 3 句话讲清联系,小白也能记牢。
1. 核心区别(必背)
| 对比维度 | Uid(身份证) | Pid(工号) |
|---|---|---|
| 本质 | 应用的唯一身份标识 | 进程的唯一运行标识 |
| 分配时机 | 应用安装时(PMS 分配) | 进程启动时(Zygote 分配) |
| 生命周期 | 随应用:安装→存在→卸载→消失(静态) | 随进程:启动→存在→结束→回收(动态) |
| 唯一性 | 设备内全局唯一(重装应用不变) | 系统内全局唯一(进程结束后可复用) |
| 核心作用 | 1. 权限控制(判断 “谁能做”)2. 数据隔离(判断 “谁能访问”)3. 共享资源(同一 Uid 共享数据) | 1. 进程调度(CPU 分配给谁)2. 资源管理(内存 / IO 归属)3. 进程管控(杀进程、监控) |
2. 关键联系(必懂)
- 一对一 / 一对多关系:一个 Pid 只能对应一个 Uid(一个进程只属于一个应用);一个 Uid 可以对应多个 Pid(一个应用可以启动多个进程,比如微信的主进程、推送进程)。
- 协同工作:系统先通过 Uid 确认应用身份(“你是谁,能做什么”),再通过 Pid 管理进程运行(“你在运行,给你资源”)。比如杀进程时,AMS 用 Pid 定位进程,同时验证 Uid 避免误杀系统进程。
- 底层关联:Android 的 Uid/Pid 本质是对 Linux 内核 Uid/Pid 的封装 —— 应用进程的 Linux 内核 Uid 就是 Android 的 Uid,Linux 内核 Pid 就是 Android 的 Pid。
五、常见误区(必避)
- 误区 1:Pid 是应用的标识?错!应用重启后 Pid 会变(工号回收再分配),但 Uid 不变(身份证不变)。比如 “外卖 A” 关闭后再打开,Pid 从 2023 变成 2024,但 Uid 还是 10086。
- 误区 2:只要配置 sharedUserId 就能共享 Uid?错!必须满足两个条件:① 应用签名相同(同一开发者);② 配置相同的
sharedUserId。否则即使包名相似,也不能共享 Uid(比如 “外卖 A” 和 “山寨外卖 A” 签名不同,无法共享 Uid)。 - 误区 3:Uid 相同就能随意访问数据?不完全对!Uid 相同是共享数据的前提,但还需要在代码中通过
Context.createPackageContext()显式获取对方的 Context,且对方应用需配置android:sharedUserId并开放权限。
通过商场故事、代码和时序图,相信你已经彻底懂了 Pid 和 Uid—— 记住:Uid 是应用的身份证,管 “身份和权限”;Pid 是进程的工号,管 “运行和调度” ,Android 就是靠这两个 ID 实现对应用和进程的精准管理。