应用进程启动分析

133 阅读4分钟

通俗易懂且详尽地总结 Android 应用进程的启动流程,解析从系统服务请求到进程创建的核心机制:

一、核心流程概览:Zygote 的「孵化」使命

Android 应用进程的启动依赖 Zygote 进程(系统的「应用孵化器」),整体流程如下:

  1. Zygote 监听请求:通过 Unix 套接字(Socket)等待启动指令。

  2. 系统服务触发:SystemServer 中的 ActivityManagerService(AMS)  发送启动请求。

  3. Zygote 执行 fork:复制自身生成子进程,加载应用代码并执行入口函数。

关键角色

  • Zygote:预加载系统资源,通过 fork 高效创建应用进程。
  • AMS:系统服务的「调度中心」,决定何时启动新应用。
  • Socket 通信:用于跨进程传递启动参数(如应用类名、权限等)。

二、Zygote 服务端准备:从初始化到循环监听

1. 初始化 Socket 连接

Zygote 启动时,通过 init.rc 配置创建名为 zygote 的本地套接字:

ini

service zygote /system/bin/app_process64 ...
    socket zygote stream 660 root system  # 定义套接字名称和权限
  • 代码实现

    java

    LocalServerSocket mZygoteSocket = Zygote.createManagedSocketFromInitSocket("zygote");
    
    • 从环境变量中获取套接字文件描述符(FD),包装为 LocalServerSocket

2. 进入循环监听(runSelectLoop)

Zygote 通过 poll 机制监听套接字事件,进入休眠状态,等待客户端(如 AMS)连接:

java

while (true) {
    StructPollfd[] pollFDs = { mZygoteSocket.getFileDescriptor() };
    Os.poll(pollFDs, -1);  // 阻塞直到有数据到来
    if (pollFDs[0].revents & POLLIN) {
        ZygoteConnection connection = acceptCommandPeer();  // 接收连接请求
        socketFDs.add(connection.getFileDescriptor());
    }
}
  • 核心逻辑

    • 使用 IO 多路复用同时监听多个套接字,避免阻塞。
    • 接收到连接后,创建 ZygoteConnection 对象处理请求。

三、AMS 触发启动:构造参数与 Socket 通信

当用户点击应用图标时,AMS 通过 Process.start() 发起进程启动请求:

1. 构造启动参数

java

ArrayList<String> argsForZygote = new ArrayList<>();
argsForZygote.add("--setuid=" + uid);         // 用户 ID
argsForZygote.add("--setgid=" + gid);         // 组 ID
argsForZygote.add("--target-sdk-version=33"); // SDK 版本
argsForZygote.add("android.app.ActivityThread"); // 入口类(ActivityThread)
  • 关键参数

    • --setuid/--setgid:设置应用进程的用户权限。
    • 入口类通常为 ActivityThread,负责应用的主线程初始化。

2. 通过 Socket 发送请求

java

LocalSocket zygoteSessionSocket = new LocalSocket();
zygoteSessionSocket.connect(new LocalSocketAddress("zygote")); // 连接 Zygote 套接字
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(zygoteSessionSocket.getOutputStream()));
writer.write(String.join(" ", argsForZygote)); // 发送参数到 Zygote
  • 通信协议

    • 通过本地套接字(LocalSocket)实现进程间通信,高效且安全。
    • 参数以字符串形式传递,包含应用的上下文信息(如包名、权限)。

四、Zygote 处理请求:fork 子进程与资源继承

1. 解析参数与 fork 系统调用

Zygote 接收到参数后,通过 ZygoteConnection.processOneCommand() 解析并调用 forkAndSpecialize()

java

int pid = Zygote.forkAndSpecialize(
    parsedArgs.mUid,        // 用户 ID
    parsedArgs.mGid,        // 组 ID
    parsedArgs.mGids,       // 附加组 ID
    rlimits,                // 资源限制
    parsedArgs.mNiceName    // 进程名
);
  • fork 特性

    • 子进程继承 Zygote 的预加载资源(如 Framework 类库、系统资源),减少启动耗时。
    • 父进程(Zygote)继续监听套接字,子进程处理应用初始化。

2. 子进程初始化(handleChildProc)

子进程通过反射调用应用入口类的 main 方法:

java

Runnable command = ZygoteInit.zygoteInit(
    targetSdkVersion, 
    argv, 
    classLoader
);
command.run(); // 执行 ActivityThread.main()
  • 关键步骤

    1. Binder 初始化:调用 nativeZygoteInit() 初始化 Binder 通信环境。
    2. 主线程启动:通过 ActivityThread.main() 创建应用主线程,初始化 Application 和 UI 组件。

五、关键技术点解析

1. 为什么使用 fork 而非全新进程?

  • 效率优势:Zygote 预加载了系统运行时环境(如 ART 虚拟机、类库),fork 可直接复制这些资源,比全新启动进程快 30% 以上。
  • 一致性:子进程与 Zygote 共享只读内存(如系统代码段),减少内存占用。

2. 权限与资源隔离

  • 用户 ID 隔离:通过 --setuid 设置独立用户 ID,确保应用进程无法访问其他进程数据(如 /data/data/ 目录)。
  • 文件描述符管理:Zygote 关闭无关文件描述符(如监听套接字),避免子进程持有无效句柄。

3. 跨架构支持

  • 多 Zygote 实例

    • zygote(64 位)和 zygote_secondary(32 位)分别处理不同架构的应用。
    • 通过 ro.zygote 属性动态加载对应配置文件(如 init.zygote64_32.rc)。

六、总结:从点击图标到界面显示

  1. 用户触发:点击应用图标,AMS 收到启动请求。

  2. 参数传递:AMS 通过 Socket 向 Zygote 发送启动参数(类名、权限等)。

  3. 高效孵化:Zygote fork 子进程,继承预加载资源,减少启动耗时。

  4. 应用初始化:子进程执行 ActivityThread.main(),创建主线程并加载界面。

类比说明
Zygote 如同「应用工厂」,预生产通用零件(系统资源),当需要新应用时,通过 fork 快速克隆生产线(子进程),而非从头搭建,大幅提升效率。这一机制是 Android 系统流畅性的核心优化点之一。