Android Pid&Uid 详解:用商场故事看懂进程与应用的 “身份证” 和 “工号”

780 阅读10分钟

要理解 Android 中的 Pid 和 Uid,我们先讲个生活故事 —— 把你的手机比作一座「智能商场」,每个应用都是商场里的「店铺」,而你是商场的「管理员」。接下来,我们用这个故事拆解 Pid 和 Uid 的本质,再结合代码和时序图讲透实现原理。

一、故事开篇:商场里的 “身份” 与 “活动”

假设你是「手机商场」的管理员,需要解决两个核心问题:

  1. 怎么区分不同店铺的身份? 比如 “外卖 A” 和 “外卖 B” 是两家不同的店,不能让 “外卖 A” 随便拿 “外卖 B” 的客户数据。这时候需要给每家店发一张唯一的身份证(Uid) ,凭身份证判断 “你是谁”,决定你能访问哪些资源(比如客户通讯录、位置信息)。
  2. 怎么管理店铺的临时活动? “外卖 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

uid.png

系统存储 (packages.xml)PMS (应用管理员)安装器 (PackageInstaller)用户系统存储 (packages.xml)PMS (应用管理员)安装器 (PackageInstaller)用户点击“安装外卖A.apk”解析APK(提取包名、签名)请求安装(携带APK信息)校验签名合法性、包名唯一性分配 Uid=10086(从10000开始)保存“外卖A → Uid=10086”返回“安装成功”显示“安装完成”

2. 流程 2:应用启动 → AMS+Zygote 分配 Pid

pid.png

权限管理 (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. 关键联系(必懂)

  1. 一对一 / 一对多关系:一个 Pid 只能对应一个 Uid(一个进程只属于一个应用);一个 Uid 可以对应多个 Pid(一个应用可以启动多个进程,比如微信的主进程、推送进程)。
  2. 协同工作:系统先通过 Uid 确认应用身份(“你是谁,能做什么”),再通过 Pid 管理进程运行(“你在运行,给你资源”)。比如杀进程时,AMS 用 Pid 定位进程,同时验证 Uid 避免误杀系统进程。
  3. 底层关联:Android 的 Uid/Pid 本质是对 Linux 内核 Uid/Pid 的封装 —— 应用进程的 Linux 内核 Uid 就是 Android 的 Uid,Linux 内核 Pid 就是 Android 的 Pid。

五、常见误区(必避)

  1. 误区 1:Pid 是应用的标识?错!应用重启后 Pid 会变(工号回收再分配),但 Uid 不变(身份证不变)。比如 “外卖 A” 关闭后再打开,Pid 从 2023 变成 2024,但 Uid 还是 10086。
  2. 误区 2:只要配置 sharedUserId 就能共享 Uid?错!必须满足两个条件:① 应用签名相同(同一开发者);② 配置相同的 sharedUserId。否则即使包名相似,也不能共享 Uid(比如 “外卖 A” 和 “山寨外卖 A” 签名不同,无法共享 Uid)。
  3. 误区 3:Uid 相同就能随意访问数据?不完全对!Uid 相同是共享数据的前提,但还需要在代码中通过 Context.createPackageContext() 显式获取对方的 Context,且对方应用需配置 android:sharedUserId 并开放权限。

通过商场故事、代码和时序图,相信你已经彻底懂了 Pid 和 Uid—— 记住:Uid 是应用的身份证,管 “身份和权限”;Pid 是进程的工号,管 “运行和调度” ,Android 就是靠这两个 ID 实现对应用和进程的精准管理。