深入Android 15 Zygote:从进程孵化器到系统基石

或许你会有疑问:都2025年了,为什么还要写这样的文章?网上类似的资料早已不计其数。

这个想法其实源于我在"极客时间"上看到的一句话, "从学习框架,到向框架学习",作为一名从事Android应用开发多年的工程师,我希望对自己的知识做一次全新的系统性梳理。​恰恰是因为 当前Android领域的大环境不如往昔,​这类文章看似"无用",我才更下定决心要写------为自己作一次彻底的沉淀与总结。​​ 况且,关于Android 15的深入探讨目前相对较少,没有充分的调研与实践,又如何能言之有物?

环境准备

AOSP的源码有120多G,我们重点关注 frameworks/base 模块,没必要全部下载

php 复制代码
// 清华大学AOSP源,framework 部分
git clone https://aosp.tuna.tsinghua.edu.cn/platform/frameworks/base -b android-15.0.0_r1

IDE就用VSCode即可

Android系统的启动

这一部分不是本文的重点,但是要讲Zygote又绕不过,还是简单介绍一下

  1. 启动电源,引导程序BootLoader开始执行
  2. Linux内核启动,找到init.rc文件,启动init进程
  3. init.rc文件中,就有我们今天的主角Zygote,app_main.cpp 的main方法会被调用

init.rc文件

kotlin 复制代码
 //system/core/rootdir/init.rc

 import /init.environ.rc
 import /init.usb.rc
 import /init.${ro.hardware}.rc
 import /vendor/etc/init/hw/init.${ro.hardware}.rc
 import /init.usb.configfs.rc
 import /init.${ro.zygote}.rc
 
 //...

其中 ${ro.zygote} 会被替换成 ro.zyogte 的属性值,这个是由不同的硬件厂商自己定制的。 有四个值:zygote32、zygote64、zygote32_64、zygote64_32 ,也就是说可能有四种 .rc 文件,分别是:

  • init.zygote32.rc:zygote 进程对应的执行程序是 app_process(纯 32bit 模式)。
  • init.zygote64.rc:zygote 进程对应的执行程序是 app_process64(纯 64bit 模式)。
  • init.zygote32_64.rc:启动两个 zygote 进程(名为 zygote 和 zygote_secondary),对应的执行程序分别是 app_process32(主模式)、app_process64。
  • init.zygote64_32.rc:启动两个 zygote 进程(名为 zygote 和 zygote_secondary),对应的执行程序分别是 app_process64(主模式)、app_process32。

app_main.cpp详解

main函数入口

arduino 复制代码
int main(int argc, char* const argv[])
{
    //*** 前面省略 ***
  
    // 创建AppRuntime对象
    // computeArgBlockSize 是计算整个参数块的大小
    AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
    
    //*** 后面省略 ***
 }

新建了一个AppRuntime 对象,并且对 main方法的入参 进行了大小计算 computeArgBlockSize,原因是后面要对进程的名字进行修改,"app_process" -> "zygote", 亦或是 "zygote" -> "system_server" 进程名放在argv[0]中,知道参数大小,防止修改内存越界,同时准确的清空原参数块,写入新名称.

入参上下文解析

arduino 复制代码
    // Parse runtime arguments.  Stop at first unrecognized option.
    // 是否以 zygote 模式启动
    bool zygote = false;
    // 是否启动系统服务器
    bool startSystemServer = false;
    // 是否以应用模式启动
    bool application = false;
    String8 niceName;
    // 要启动的 Java 类名
    String8 className;

    ++i;  // Skip unused "parent dir" argument.
    while (i < argc) {
        const char* arg = argv[i++];
        if (strcmp(arg, "--zygote") == 0) {
            zygote = true;
            niceName = ZYGOTE_NICE_NAME;
        } else if (strcmp(arg, "--start-system-server") == 0) {
            startSystemServer = true;
        } else if (strcmp(arg, "--application") == 0) {
            application = true;
        } else if (strncmp(arg, "--nice-name=", 12) == 0) {
            niceName = (arg + 12);
        } else if (strncmp(arg, "--", 2) != 0) {
            className = arg;
            break;
        } else {
            --i;
            break;
        }
    }

典型的app_process 的命令行参数

ini 复制代码
# Zygote 模式启动系统服务器
app_process -Xzygote /system/bin --zygote --start-system-server --nice-name=system_server com.android.server.SystemServer

# 应用模式启动
app_process -Xapplication /system/bin --application --nice-name=com.example.app com.example.MainActivity

修改进程名

less 复制代码
    if (!niceName.empty()) {
        runtime.setArgv0(niceName.c_str(), true /* setProcName */);
    }

"app_process" -> "zygote"

Android Runtime 启动

Android开发者似乎不太常用Runtime这个词,iOS里面更常用,其实Android也是有的呀,只是叫法不同罢了! 根据 zygote 和 className 的不同,进入ZygoteInit 或者 RuntimeInit 的 main,init.rc 启动的 zygote 是 true。

scss 复制代码
    // 如果是启动zygote
    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    } else if (!className.empty()) {
        // 如果是启动应用程序
        runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
    } else {
        fprintf(stderr, "Error: no class name or --zygote supplied.\n");
        app_usage();
        LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
    }

Android Runtime start函数

arduino 复制代码
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote) {

    static const String8 startSystemServer("start-system-server");
    // Whether this is the primary zygote, meaning the zygote which will fork system server.
    bool primary_zygote = false;
    
        /*
     * 'startSystemServer == true' means runtime is obsolete and not run from
     * init.rc anymore, so we print out the boot start event here.
     */
    for (size_t i = 0; i < options.size(); ++i) {
        if (options[i] == startSystemServer) {
            primary_zygote = true;
           /* track our progress through the boot sequence */
           const int LOG_BOOT_PROGRESS_START = 3000;
           LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START,  ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));
        }
    }
    
    //...

标记当前的 Zygote 进程是否是Primary Zygote,64位系统,存在一个主Zygote和次Zygote。

  • 主 Zygote: 负责启动 system_server 进程,并孵化大部分的应用进程。它通常处理 64位 的应用(在64位设备上)。

  • 次 Zygote (Secondary Zygote): 在64位设备上,它负责孵化 32位 的应用进程,为的就是兼容32位应用。

primary_zygote 这个标志就是用来区分这两种 Zygote 的。

为何需要区分主次 Zygote?
  1. 解决 64 位设备兼容性问题

    • 单一 Zygote 同时加载 32/64 位库会导致:

      • 内存浪费(冗余加载)
      • 库冲突(如 libc 的 32/64 位版本不兼容)。
  2. 优化系统性能

    • 并行孵化:主次 Zygote 独立处理请求,避免 32/64 位应用竞争资源。
    • 启动加速:预加载按需分配,减少 Zygote 自身启动时间(主 Zygote 无需加载 32 位库)。
  3. 架构清晰性

    • 主 Zygote 专注系统服务和高性能应用,次 Zygote 处理兼容性需求,职责分离提升稳定性

创建ART虚拟机

startVm函数这里我们先不展开,另外Zygote创建的虚拟机,是可以被所有fork出来的进程共享的,所以我们启动的应用程序就不需要做这些工作了~

ini 复制代码
    JniInvocation jni_invocation;
    jni_invocation.Init(NULL);
    JNIEnv* env;
    if (startVm(&mJavaVM, &env, zygote, primary_zygote) != 0) {
        return;
    }

注册Jni函数

bash 复制代码
    /*
     * Register android functions.
     */
    if (startReg(env) < 0) {
        ALOGE("Unable to register all android natives\n");
        return;
    }

解析要调用的Java类名

scss 复制代码
    char* slashClassName = toSlashClassName(className != NULL ? className : "");
    jclass startClass = env->FindClass(slashClassName);
    if (startClass == NULL) {
        ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
        /* keep going */
    } else {
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
            "([Ljava/lang/String;)V");
        if (startMeth == NULL) {
            ALOGE("JavaVM unable to find main() in '%s'\n", className);
            /* keep going */
        } else {
            env->CallStaticVoidMethod(startClass, startMeth, strArray);

#if 0
            if (env->ExceptionCheck())
                threadExitUncaughtException(env);
#endif
        }
    }
  • com/android/internal/os/ZygoteInit :Zygote 进程的入口类
  • android/app/ActivityThread :应用程序主线程的入口类

跨越JNI边界 :当env->CallStaticVoidMethod()调用ZygoteInit.main(),标志着C++世界到Java世界的控制权移交,Zygote的核心孵化逻辑自此由Java层掌控。

Java世界

ZygoteInit.java

直接看main函数的实现

java 复制代码
        ZygoteServer zygoteServer = null;

        // Mark zygote start. This ensures that thread creation will throw
        // an error.
        ZygoteHooks.startZygoteNoThreadCreation();
  • 先创建了一个ZygoteServer对象,赋值null。ZygoteServer是 Zygote 的核心类,负责创建 Server Socket、监听和处理来自 ActivityManagerService 的命令。对象稍后会初始化。
  • ZygoteHooks.startZygoteNoThreadCreation(),这个方法禁止Zygote进程创建线程,目的是保证在zygote预加载和初始化阶段,避免并发问题,同时为fork做准备,我们知道fork进程的时候,如果有其他线程会有潜在的状态不一致的问题。(本质fork只复制调用fork的那一个线程,来创建一个全新的子进程, Linux中线程和进程都是使用task_struct来描述)。
  • 禁止子线程的创建,避免fork 多线程的问题,是zygote的很重要的设计哲学

学框架,向框架学习 ==》fork只保证当前调用的线程,如果有其他线程有锁竞争,会有安全隐患。在Linux内核看来,进程和线程都是task_struct来描述,区别只是资源共享与否

perl 复制代码
Tips: 我记得有一些OOM监控框架,在发生OOM时,会fork子进程来收集Hprof文件,那这个fork会有多线程的问
题吗? 我想大概率也是有的,只不过此时只需要尽快将Hprof文件,dump出来,然后裁剪上传服务器,App进程就可
以死了,所以影响也不大。

Preload

又到了很重要的知识点,Preload

scss 复制代码
            // In some configurations, we avoid preloading resources and classes eagerly.
            // In such cases, we will preload things prior to our first fork.
            if (!enableLazyPreload) {
                bootTimingsTraceLog.traceBegin("ZygotePreload");
                EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
                        SystemClock.uptimeMillis());
                preload(bootTimingsTraceLog);
                EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
                        SystemClock.uptimeMillis());
                bootTimingsTraceLog.traceEnd(); // ZygotePreload
            }

如果没有开启延迟预加载,就会进行预加载,那预加载什么呢?进入preload函数看一下

scss 复制代码
    static void preload(TimingsTraceLog bootTimingsTraceLog) {
        
        bootTimingsTraceLog.traceBegin("BeginPreload");
        // 回调开始preload了
        beginPreload();
        bootTimingsTraceLog.traceEnd(); // BeginPreload
        bootTimingsTraceLog.traceBegin("PreloadClasses");
        preloadClasses();
        bootTimingsTraceLog.traceEnd(); // PreloadClasses
        bootTimingsTraceLog.traceBegin("CacheNonBootClasspathClassLoaders");
        cacheNonBootClasspathClassLoaders();
        bootTimingsTraceLog.traceEnd(); // CacheNonBootClasspathClassLoaders
        bootTimingsTraceLog.traceBegin("PreloadResources");
        Resources.preloadResources();
        bootTimingsTraceLog.traceEnd(); // PreloadResources
        Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadAppProcessHALs");
        nativePreloadAppProcessHALs();
        Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
        Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadGraphicsDriver");
        maybePreloadGraphicsDriver();
        Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
        preloadSharedLibraries();
        preloadTextResources();
        // Ask the WebViewFactory to do any initialization that must run in the zygote process,
        // for memory sharing purposes.
        WebViewFactory.prepareWebViewInZygote();
        endPreload();
        warmUpJcaProviders();
        Log.d(TAG, "end preload");

        sPreloadComplete = true;
    }
  • beginPreload() :回调通知开始预加载
  • preloadClasses() :预加载系统类,这些类定义在 /system/etc/preloaded-classes 文件中
  • cacheNonBootClasspathClassLoaders() :缓存非启动类路径的类加载器
  • Resources.preloadResources() :预加载系统资源,包括 drawable、layout 等资源文件
  • nativePreloadAppProcessHALs() :预加载应用进程需要的硬件抽象层(HAL)
  • maybePreloadGraphicsDriver() :预加载图形驱动,优化图形性能
  • preloadSharedLibraries() :预加载共享库,如 android、jnigraphics 等
  • preloadTextResources() :预加载文本资源,如 Hyphenator、TextView 的字体缓存等
  • WebViewFactory.prepareWebViewInZygote() :在 Zygote 进程中准备 WebView
  • endPreload() :结束预加载阶段,执行清理工作
  • warmUpJcaProviders() :预热 Java 加密体系结构(JCA)提供者

预加载的东西也比较多,但是干的事情都是类似的,我这里拿preloadClassespreloadResourcesWebViewFactory.prepareWebViewInZygote 来举例,其他的大家可以自行研究

proloadClasses
csharp 复制代码
    private static void preloadClasses() {
        final VMRuntime runtime = VMRuntime.getRuntime();

        InputStream is;
        try {
            is = new FileInputStream(PRELOADED_CLASSES);
        } catch (FileNotFoundException e) {
            Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
            return;
        }
        
        BufferedReader br =
                    new BufferedReader(new InputStreamReader(is), Zygote.SOCKET_BUFFER_SIZE);
                    
        Class.forName(line, true, null);
        
        runtime.preloadDexCaches();
Android 基础框架类:
  • android.R 相关资源类

  • android.accessibilityservice 无障碍服务相关类

  • android.accounts 账户管理相关类

  • android.animation 动画相关类

  • android.annotation 注解相关类

  • android.apex APEX(Android Pony EXpress)相关类

    Android 应用组件类:
  • android.app.Activity 及其相关内部类

  • android.app.ActivityManager 活动管理器相关类

  • android.app.ActivityThread 应用主线程相关类

  • android.app.AlarmManager 闹钟管理器相关类

  • android.app.AppOpsManager 应用操作管理器相关类

  • android.app.Application 应用程序相关类

cacheNonBootClasspathClassLoaders

缓存非启动类路径加载器,减少fork后查找开销

Resources.preloadResources
ini 复制代码
    @UnsupportedAppUsage
    public static void preloadResources() {
        try {
            // 1. 获取系统 Resources 实例
            final Resources sysRes = Resources.getSystem();
            // 2. 开始预加载
            sysRes.startPreloading();
            if (PRELOAD_RESOURCES) {
                Log.i(TAG, "Preloading resources...");

                long startTime = SystemClock.uptimeMillis();
                // 3. 预加载 Drawable 资源
                TypedArray ar = sysRes.obtainTypedArray(
                        com.android.internal.R.array.preloaded_drawables);
                int numberOfEntries = preloadDrawables(sysRes, ar);
                ar.recycle();
                Log.i(TAG, "...preloaded " + numberOfEntries + " resources in "
                        + (SystemClock.uptimeMillis() - startTime) + "ms.");

                startTime = SystemClock.uptimeMillis();
                // 4. 预加载 ColorStateList 资源
                ar = sysRes.obtainTypedArray(
                        com.android.internal.R.array.preloaded_color_state_lists);
                numberOfEntries = preloadColorStateLists(sysRes, ar);
                ar.recycle();
                Log.i(TAG, "...preloaded " + numberOfEntries + " resources in "
                        + (SystemClock.uptimeMillis() - startTime) + "ms.");
            }
          // 5. 完成预加载
            sysRes.finishPreloading();
        } catch (RuntimeException e) {
            Log.w(TAG, "Failure preloading resources", e);
        }
    }
WebViewFactory.prepareWebViewInZygote
csharp 复制代码
    /**
     * Perform any WebView loading preparations that must happen in the zygote.
     * Currently, this means allocating address space to load the real JNI library later.
     */
    public static void prepareWebViewInZygote() {
        try {
            WebViewLibraryLoader.reserveAddressSpaceInZygote();
        } catch (Throwable t) {
            // Log and discard errors at this stage as we must not crash the zygote.
            Log.e(LOGTAG, "error preparing native loader", t);
        }
    }
csharp 复制代码
    /**
     * Reserve space for the native library to be loaded into.
     */
    static void reserveAddressSpaceInZygote() {
        System.loadLibrary("webviewchromium_loader");
        long addressSpaceToReserve;
        if (VMRuntime.getRuntime().is64Bit()) {
            // On 64-bit address space is really cheap and we can reserve 1GB which is plenty.
            addressSpaceToReserve = 1 * 1024 * 1024 * 1024;
        } else if (VMRuntime.getRuntime().vmInstructionSet().equals("arm")) {
            // On 32-bit the address space is fairly scarce, hence we should keep it to a realistic
            // number that permits some future growth but doesn't hog space. For ARM we use 130MB
            // which is roughly what was calculated on older OS versions. The size has been
            // growing gradually, but a few efforts have offset it back to the size close to the
            // original.
            addressSpaceToReserve = 130 * 1024 * 1024;
        } else {
            // The number below was obtained for a binary used for x86 emulators, allowing some
            // natural growth.
            addressSpaceToReserve = 190 * 1024 * 1024;
        }

        sAddressSpaceReserved = nativeReserveAddressSpace(addressSpaceToReserve);

        if (sAddressSpaceReserved) {
            if (DEBUG) {
                Log.v(LOGTAG, "address space reserved: " + addressSpaceToReserve + " bytes");
            }
        } else {
            Log.e(LOGTAG, "reserving " + addressSpaceToReserve + " bytes of address space failed");
        }
    }
  1. 基本功能:
  • 在 Zygote 进程中为 WebView 的原生库预留地址空间
  • 加载 webviewchromium_loader 原生库
  1. 地址空间分配策略:
  • 64位系统:预留 1GB 空间,因为地址空间充足
  • 32位 ARM 系统:预留 130MB 空间,考虑到地址空间稀缺性和历史经验值
  • 其他 32位系统(如 x86):预留 190MB 空间,基于模拟器二进制文件的需求
  1. 实现细节:
  • 通过 System.loadLibrary() 加载 WebView 的加载器库
  • 使用 VMRuntime 检测系统架构(64位/32位)和指令集
  • 调用 native 方法 nativeReserveAddressSpace() 实际预留空间
  • 使用 sAddressSpaceReserved 标志记录预留是否成功

Android性能优化之道 书中有谈到用不到WebView的进程,可以通过查找maps文件寻址,通过munmap将预分配的虚拟内存释放掉,释放的就是上面Zygote reserveAddressSpaceInZygote的虚拟内存。

为何要Preload
  • 提升应用启动性能:应用进程从 Zygote fork 出来时,这些类已经加载完成,可以直接使用
  • 节省内存:所有应用共享这些预加载的类,避免每个应用都单独加载一份
  • 优化系统响应:关键系统服务和常用组件类预加载后可以更快响应

preloadDexCaches

最后还将加载的这些类的DEX文件,缓存到内存中,同样Zygote可以和所有进程共享,节约内存,也加快了应用启动性能

学框架,向框架学习 ==》 预加载在应用开发中也经常使用,但是应用开发比较难把控的是,当前硬件设备的 cpu的负载,内存水位,网络IO情况,可能99%的情况做预加载是正优化,但是会有1%的情况是负优化

gcAndFinalize()

csharp 复制代码
/**

* Runs several special GCs to try to clean up a few generations of softly- and final-reachable

* objects, along with any other garbage. This is only useful just before a fork().

*/

private static void gcAndFinalize() {

ZygoteHooks.gcAndFinalize();

}

在创建新进程前清理内存,减少不必要的对象复制,注释明确指出这个操作只在 fork() 之前使用才有意义。

Android 15在此阶段引入Generational GC,有一些测试数据,Zygote堆内存清理效率提升50%(实测Pixel 7 Pro预加载后内存下降18%)。

Zygote.initNativeState(isPrimaryZygote)

  • 初始化 Zygote 进程的本地(native)状态,包括:
  • 从环境变量中获取 socket 文件描述符
  • 初始化安全属性
  • 根据需要卸载存储设备
  • 加载必要的性能配置信息
  • 该方法通过调用 native 方法 nativeInitNativeState() 实现具体功能,接收一个布尔参数 isPrimary :
  • 当 isPrimary 为 true 时,表示这是主 Zygote 进程
  • 当 isPrimary 为 false 时,表示这是辅助 Zygote 进程(zygote_secondary)

ZygoteHooks.stopZygoteNoThreadCreation()

前面我们提到禁用了线程的创建,初始化到此时,需要预加载的,初始化的都准备就绪,此时可以放开了线程的创建限制。 是如何禁止线程的创建的呢?其实也很简单,就是一个全局变量 gZygoteNoThreadSection,检测为true,thread创建会被拦截

rust 复制代码
// art/runtime/native/java_lang_Thread.cc
static void Thread_nativeCreate(JNIEnv* env, jclass, jobject java_thread, ...) {
  if (runtime->IsZygote() && runtime->IsZygoteNoThreadSection()) {
    env->ThrowNew(env->FindClass("java/lang/InternalError"), "Cannot create threads in zygote");
    return;
  }
  // 正常创建线程的逻辑...
}

创建ZygoteServer

ini 复制代码
zygoteServer = new ZygoteServer(isPrimaryZygote);

来到了Zygote中一个核心的对象,它负责Zygote进程的服务器功能,主要有如下职责

  • 监听来自客户端的连接请求
  • 处理应用进程创建命令
  • 管理 USAP(Unspecialized App Process)池

这里又有一个新的知识点USAP,暂时可以理解为Zygote预先初始化了一些进程,放入池子中,当有新的进程创建请求来到时候,从池子中直接分配, 目的肯定是空间换时间,加速应用的启动。

学框架,向框架学习 ==》 典型的池化的应用,线程池,对象池,进程池,空间换时间

startSystemServer

启动system_server进程,也是zygote很重要的职责之一,这里先提一下,后面文章会详细分析~

scss 复制代码
            if (startSystemServer) {
                Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);

                // {@code r == null} in the parent (zygote) process, and {@code r != null} in the
                // child (system_server) process.
                if (r != null) {
                    r.run();
                    return;
                }
            }

runSelectLoop

scss 复制代码
            // The select loop returns early in the child process after a fork and
            // loops forever in the zygote.
            caller = zygoteServer.runSelectLoop(abiList);

终于到了我们熟知的监听循环了,由zygoteServer来负责,ZygoteServer也有很多有意思的可以研究的,我们下一篇继续。

相关推荐
Xiaouuuuua3 小时前
我开源了一套springboot3快速开发模板
java·开发语言·开源
保持学习ing3 小时前
SpringBoot电脑商城项目--显示勾选+确认订单页收货地址
java·前端·spring boot·后端·交互·jquery
穆易青4 小时前
2025.06.20【pacbio】|使用Snakemake构建可重复的PacBio全基因组甲基化分析流程
java·运维·服务器
李明一.4 小时前
Java 全栈开发学习:从后端基石到前端灵动的成长之路
java·前端·学习
@佳瑞4 小时前
吐槽之前后端合作开发
java
小帅学编程4 小时前
Maven
java·maven
悟能不能悟5 小时前
在 MyBatis 的xml中,什么时候大于号和小于号可以不用转义
xml·java·mybatis
hqxstudying5 小时前
深入解析 Java List 实现类的底层原理
java·数据结构·算法·链表