333 Android Zygote:所有 APP 进程的孵化器

系列导航330-启动总览 | 331-Bootloader | 332-init进程

上篇我们讲了 init 进程——用户态第一个进程,负责解析配置、挂载文件系统、管理属性、启动关键服务。其中最重要的一个服务,就是 Zygote

Zygote 是 Android 最独特的设计之一。它是所有 Java 应用进程的父进程,通过预加载类库和 fork 模型,实现了惊人的应用启动速度优化。

理解 Zygote,是理解 Android 整体架构的钥匙。


Zygote 是什么

Zygote(受精卵)——这个名字已经暗示了它的作用:它是 Android 系统中所有进程的”母体”。

从进程关系上看:

Bash
init (PID 1)
  └── zygote (PID 1 的子进程)
        ├── system_server (zygote fork 出来)
        └── com.example.myapp (zygote fork 出来,100+ 个 APP 进程)

从技术角度说,Zygote 是一个 Java 进程,它在启动时:

  1. 预加载所有 Java 核心类库(android.jar + framework.jar)
  2. 预创建一个虚拟机的实例(但还没启动 APP 的 main)
  3. 启动一个 ServerSocket,等待 AMS 发请求
  4. 收到 fork 请求后,通过 fork() 系统调用复制自己

Bash
Zygote 进程(已加载所有类库和资源)
        │
        │ AMS 请求 fork 新 APP 进程
        ▼
    fork() ─────────────────────────────────────────┐
        │                                            │
        ▼                                            ▼
    子进程(APP)                               父进程(继续监听)
    · 复制父进程地址空间
    · 继承所有已加载的类
    · 执行 Application.onCreate()
    · 启动 ActivityThread.main()

关键点:因为类已经在 Zygote 里加载过了,子进程不用重新加载类,节省了数秒钟的冷启动时间。


为什么需要 Zygote

Android 在 Dalvik 时代引入 Zygote,主要解决两个问题:

问题 1:每个进程要加载所有类 → 慢

在没有 Zygote 的设计里,每个 APP 进程都需要:

Bash
APP 进程启动
  → 创建一个 Dalvik/ART 虚拟机实例(~20-50MB 内存分配)
  → 加载 java.*, javax.*, android.*, dalvik.*, ...
  → 链接 native 库(JNI)
  → 执行 Application.onCreate()
  → 启动四大组件

这会导致:

  • 首次启动慢:每次都要重新加载几千个类
  • 内存浪费:每个进程都有自己的一份类数据(.dex 缓存)

问题 2:Native 库初始化开销

JNI 调用 native 代码时,需要 System.loadLibrary()。如果每个 APP 都加载同一套 so,内存中会有多份拷贝。

Bash
┌────────────────┬────────────────┐
│  Zygote (共享)  │  APP1 进程      │
│  libandroid.so │  -             │ ← Zygote 已加载,fork 后共享
│  libicu*.so     │  -             │
└────────────────┴────────────────┘

Zygote 通过 fork + copy-on-write 机制解决这两个问题:

  • fork 后子进程共享父进程已加载的类和内存(直到写了新数据才复制)
  • 每个 APP 进程只需要极少的启动时间

Zygote 的启动流程

Zygote 由 init 启动,对应配置 init.zygote64_32.rc(或 init.zygote64_64.rc):

Rc
service zygote /system/bin/app_process64
    class main
    socket zygote stream 660 root system
    socket usap_pool_min_streaming_native_pool stream 660 root system
    onexec /system/bin/linker64 /system/bin/app_process64
    priority 1000
    priority -20
    limit 16384 1000000 1000000 10 1000000
    group root readproc reserved_disk
    seclabel u:object_r:zygote_exec:s0

1. app_process_main 入口

/system/bin/app_process64 是 Zygote 的入口程序(app_main.cpp):

Cpp
int app_main(int argc, char* const argv[]) {
    // 解析命令行参数
    AppRuntime runtime;

    // 创建 AppRuntime(继承 AndroidRuntime)
    // 调用 start() 启动 Java 虚拟机
    runtime.start("com.android.internal.os.ZygoteInit", ...);
}

2. AndroidRuntime 启动 JVM

Cpp
// frameworks/base/core/jni/AndroidRuntime.cpp
void AndroidRuntime::start(const char* className, ...) {
    // 1. 创建 JVM
    JNI_CreateJavaVM(&mJavaVM, &mEnv, &opts);

    // 2. 注册 JNI 方法(给 Java 层调用的 native 方法)
   REGISTER__JNI_METHODS(mEnv);

    // 3. 加载核心 Framework 类
    jclass clazz = mEnv->FindClass(className); // "com.android.internal.os.ZygoteInit"
    mEnv->CallStaticVoidMethod(clazz, methodId, ...);
}

3. ZygoteInit.main(Java 层)

Java
// frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
public static void main(String[] argv[]) {
    // 1. 预加载资源(类、drawable、theme、打开的文件描述符)
    preload();

    // 2. 启用 class preloading(Android 9+)
    maybeEnableHeapRobustness();

    // 3. 注册 Zygote Socket(等待 AMS 发请求)
    registerZygoteSocket();

    // 4. 启动 System Server(核心系统服务)
    preLoadAndStartSystemServer();

    // 5. 进入循环,等待 AMS fork 请求
    caller = zygoteServer.runSelectLoop();
}

预加载(Preload)

ZygoteInit.main() 的第一步是 preload()。这是 Android 冷启动最耗时的阶段之一。

预加载的完整流程

Bash
┌───────────────────────────────────────────────────────────────┐
│                      Zygote preload()                        │
├───────────────────────────────────────────────────────────────┤
│  1. preloadClasses()                                         │
│     ・加载 ~1500-2000 个核心类                                 │
│     ・来自 framework.jar、core-libart.jar                   │
│     ・把类写入 JVM 内部的 class table(zygote 进程地址空间)   │
│                                                               │
│  2. preloadResources()                                       │
│     ・加载 drawable、layout、values 资源                      │
│     ・资源表 ResourceTable 全量加载到内存                      │
│     ・Zygote 会预打开 100+ 个 APK 的资源                      │
│                                                               │
│  3. preloadOpenGL()                                           │
│     ・加载 OpenGL ES 相关的 native 库                         │
│                                                               │
│  4. preloadSharedLibraries()                                  │
│     ・加载 libc.so, libm.so, libdl.so                        │
│     ・加载 android.so, libart.so 等                         │
│                                                               │
│  5. preloadTextResources()                                   │
│     ・预加载字体资源                                           │
└───────────────────────────────────────────────────────────────┘

preloadClasses() 详解

preloadClasses() 加载的是 frameworks/base/preloaded-classes 文件里的类:

Bash
# 查看预加载的类列表(部分)
adb shell cat /system/etc/preloaded-classes | head -50
# android.app.Activity
# android.app.Application
# android.app.ContextImpl
# android.app.Dialog
# android.content.ContentProvider
# android.content.Intent
# ...

这个列表在 AOSP 编译时由 class_loader.jar 生成,记录了”哪些类必须在 Zygote 阶段加载”。列表里大约有 2000 个类

为什么 preload 耗时

预加载不是单纯的读文件——JVM 加载一个类需要:

Bash
1. 读取 .dex 或 .jar 文件
2. 解析 DEX 格式(Header + ClassDefs + MethodIds)
3. 验证字节码(Bytecode Verifier)
4. 链接(Link)(解析字符串常量、设置虚方法表 vtable)
5. 初始化(<clinit> 静态块执行)

某些类的 <clinit> 里有耗时操作(如静态字段初始化时读取文件)。所以 Android 9+ 引入了 deferred preloading——不在启动时预加载所有类,而是 fork 时按需加载。


Zygote Socket

Zygote 启动时创建了一个 Unix Domain Socket,路径是 /dev/socket/zygote。AMS(ActivityManagerService)通过这个 socket 发送 fork 请求。

协议格式

AMS 发给 Zygote 的请求是一个 ASCII 字符串:

Bash
# 格式:&lt;command&gt; &lt;args&gt;
# 例如:
bindle apk_fd=23 umask=0x1b nice=0 is_zygote=true primary_zygote=false

Zygote 收到后会 fork 一个子进程,然后把 apk_fd(APK 文件描述符)通过 zygote Socket 发给子进程。

Bash
┌─────────────────────────────────────────────────────────────┐
│                     AMS → Zygote                             │
├─────────────────────────────────────────────────────────────┤
│  AMS.sendRequest(&quot;bindle apk_fd=23 umask=...&quot;)             │
│            │                                                 │
│            │  Unix Domain Socket (/dev/socket/zygote)       │
│            ▼                                                 │
│  Zygote fork() → 子进程(APP)                              │
│            │                                                 │
│            │  把 apk_fd 通过 SocketTransfer 给子进程         │
│            ▼                                                 │
│  子进程执行 ActivityThread.main()                           │
└─────────────────────────────────────────────────────────────┘

为什么用 Unix Domain Socket 而不是 Pipe

  1. 双向通信:fork 前子进程可以通过 socket 从父进程获取文件描述符
  2. 支持 fd 传递:普通的 pipe 只能传数据,不能传文件描述符。Zygote 需要把 APK fd 传给子进程
  3. 支持抽象 socket:不需要文件系统路径

fork 模型

Zygote 使用 fork() 系统调用创建 APP 进程。这是类 Unix 系统创建进程的标准方式。

Android 的 fork 实现

Cpp
// frameworks/base/core/jni/com_android_internal_os_Zygote.cpp
pid_t Zygote::forkAndSpecialize(...) {
    // 1. 设置进程名(android.process.*)
    // 2. 设置 NIC(网络接口能力,CAP_NET_RAW 等)
    // 3. 解析 uid/gid/capabilities 参数
    // 4. 调用 linux fork()
    return fork();
}

fork 之后发生了什么

Bash
Parent (Zygote)              Child (APP 进程)
─────────────────────────────────────────────────────────────
继续运行 runSelectLoop()    继承地址空间(copy-on-write)
                           独立 uid/gid/capabilities
                           创建新的进程命名空间(如果配置了)
                           执行 Java 代码(Application.onCreate)

为什么 fork 这么快

因为 copy-on-write(COW):

  • fork 的一瞬间只是复制页表(4KB × 进程地址空间),不复制实际内存
  • 父进程和子进程共享所有只读内存(类代码、虚拟函数表等)
  • 只有子进程开始写数据时,才会复制那一页

这使得 APP 进程启动只需要极少的内存复制。理论上,一个 512MB 的进程地址空间,fork 只需要复制几十 KB 的页表。


SystemServer

ZygoteInit.main() 中,Zygote 启动后立即 preLoadAndStartSystemServer() 创建 SystemServer。

SystemServer 是什么

SystemServer 是 Android 核心系统服务的容器进程。它在 Zygote 启动后立即被 fork 出来:

Bash
init → Zygote → SystemServer(fork)
                    │
                    ├── ActivityManagerService
                    ├── PackageManagerService
                    ├── WindowManagerService
                    ├── PowerManagerService
                    ├── BatteryService
                    ├── ContentService
                    └── ... 100+ 系统服务

SystemServer 的启动流程

Java
// frameworks/base/services/java/com/android/server/SystemServer.java
public static void main(String[] args) {
    new SystemServer().run();
}

private void run() {
    // 1. 准备 Looper
    Looper.prepareMainLooper();

    // 2. 加载 native 库(libandroid_servers.so)
    System.loadLibrary(&quot;android_servers&quot;);

    // 3. 创建 SystemServiceManager
    mSystemServiceManager = new SystemServiceManager(mSystemContext);

    // 4. 启动引导服务(Bootstrap Services)
    //    - PowerManagerService
    //    - ActivityManagerService
    //    - PackageManagerService
    startBootServices();

    // 5. 启动核心服务(Core Services)
    //    - BatteryService
    //    - UsageStatsService
    startCoreServices();

    // 6. 进入消息循环
    Looper.loop();
}

AMS 的特殊角色

ActivityManagerService(AMS)是 SystemServer 里最重要的服务。它负责:

  • 管理所有 Activity 的生命周期
  • 调度 Task 和 Stack
  • 启动新的 APP 进程(通过 Zygote)
  • 处理广播和 Intent 解析

AMS 启动后,会执行 finishBooting()——这就是 sys.boot_completed 被设置为 1 的时机:

Bash
AMS.finishBooting()
        │
        ▼
设置 sys.boot_completed=1
        │
        ▼
发送 ACTION_BOOT_COMPLETED 广播
        │
        ▼
调用 mPersistentPerApp.setProcessReady() 通知所有 APP

Zygote 的演进

Dalvik 时代(Android 2.2-4.4)

Zygote 设计最初就是为了解决 Dalvik 虚拟机加载慢的问题。当时没有 ART,Dalvik 的解释执行非常慢,预加载类库是唯一的优化手段。

ART 时代(Android 5.0-7.0)

ART 引入 AOT(Ahead-of-Time)编译,pre-initialized DEX( oat)文件在安装时生成。但 Zygote 的预加载逻辑基本没变。

Android 9+ 改进

Google 发现 Zygote 预加载所有类还是太慢了(2-4秒),引入了两个改进:

  1. Deferred preloading:不一次性加载所有类,而是记录哪些类会被用到,按需加载
  2. Zygote as a Service:Zygote 进程本身可以作为”服务”被多个”Zygote 实例”fork

Android 12+ 的改进

Android 12 引入了 Zygote pre-forkingstatsd 预热。当检测到用户行为模式时(如插入 SIM 卡),提前 fork 好进程,减少用户感知的启动延迟。


APP 进程的诞生

完整的 APP 启动链路:

Bash
用户点击桌面图标
        │
        ▼
┌───────────────────────────────────────────────────────────────┐
│ ActivityManagerService.startActivity()                      │
│  → 检查目标 Activity 是否已在运行                             │
│  → 如果没有,创建新进程                                      │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
┌───────────────────────────────────────────────────────────────┐
│ AMS.startProcessLocked()                                    │
│  → 创建 ActivityRecord                                      │
│  → 创建 ProcessRecord                                       │
│  → 通过 Zygote 进程间通信请求 fork                            │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
┌───────────────────────────────────────────────────────────────┐
│ ZygoteProcess.zygoteSendArgs()                              │
│  → 向 /dev/socket/zygote 写入请求(&quot;bindle ...&quot;)           │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
┌───────────────────────────────────────────────────────────────┐
│ ZygoteInit.main() ← runSelectLoop()                         │
│  → 读取请求,fork()                                          │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
┌───────────────────────────────────────────────────────────────┐
│  子进程(APP 进程)                                           │
│  1. 执行 handleChildProc()                                  │
│  2. 调用 ZygoteInit.invokeSystemServer()                    │
│  3. 创建 RuntimeInit.ApplicationInit()                      │
│  4. 执行 ActivityThread.main()                              │
│  5. 加载 APK dex 文件                                       │
│  6. 执行 Application.onCreate()                             │
│  7. 执行 Activity.onCreate()                               │
└───────────────────────────────────────────────────────────────┘

和 Windows 进程创建的对比

维度 Android Zygote Windows
进程创建 fork() + copy-on-write CreateProcess()
类共享 Zygote 预加载后 fork 共享 DLL(进程启动时加载)
地址空间隔离 fork 隔离(独立地址空间) 独立地址空间
服务端 Zygote Socket (UDS) CreateProcess API
APP 模型 四大组件 WinMain / WndProc

核心区别:Windows 用 DLL 做代码共享,DLL 在进程启动时由 ntdll.dll → kernel32.dll → app.exe 的导入表触发加载。Android 用 Zygote 做代码共享,通过 fork 把已加载的类直接共享给子进程。


Wandos 对比

Wandos 还没有实现类似 Zygote 的机制。它的 APP 进程创建是:

Bash
wandos kernel → 自研 init → 直接 execve() APP 程序

没有进程间类共享,没有 fork + copy-on-write。这对于教学目的足够了,但性能上远远比不上 Android 的 Zygote 设计。


动手环节

1. 查看 Zygote 进程

Bash
adb shell ps -A | grep zygote
# USER     PID   PPID   VSZ    RSS  NAME
# u0_a97   987   1      0      0     zygote64
# u0_a97   988   1      0      0     zygote64_secondary

2. 分析 Zygote 日志

Bash
adb shell logcat -b events | grep zygote
# 可以看到 fork 新进程的时机

3. 测试 fork 过程

Bash
# 查看 APP 进程的 fork 时间点
adb shell logcat -b events | grep &quot;bindle_process&quot;

# 查看 SystemServer 启动
adb shell logcat -b events | grep system_server

4. 分析 preloaded-classes

Bash
# 看预加载了哪些类
adb shell cat /system/etc/preloaded-classes | wc -l
# 大约 2000 行

# 看预加载耗时(从 dmesg 或 logcat 的 events)
adb shell dmesg | grep preinit

常见问题排查

症状 原因 解决
APP 首次启动奇慢 preload 时间太长 使用 Zygote pre-forking
Zygote 崩溃导致所有 APP 崩溃 fork 风暴(子进程继承父进程状态) 检查 native crash
APP 启动后类找不到 类不在 preloaded-classes 里 添加到 preloaded-classes
Zygote Socket 连接失败 socket 权限或 SELinux 检查 sepolicy
fork() 返回 -1(ENOMEM) 地址空间耗尽 检查进程数限制

系列导航


附录:关键 logcat 标签

Bash
# Zygote fork 日志
adb logcat -b events | grep &quot;zygote&quot;

# APP 进程创建
adb logcat -b events | grep &quot;proc_start&quot;

# SystemServer 启动
adb logcat -b events | grep &quot;system_server&quot;

# boot_completed 事件
adb logcat -b events | grep &quot;boot_completed&quot;

作者:公众号「解码日记」,专注于操作系统、虚拟机、内核级技术深度解析。每周三更新。

最后修改: 2024年3月15日

作者

评论

发表评论

您的邮箱地址不会被公开。