3、app_process的奥秘:Zygote的可执行文件app_process、main函数的调用链

好,我们继续往下挖。

上一章我们聊了init进程如何解析zygote.rc,最终把Zygote进程拉起来。但有个细节你可能注意到了——启动脚本里写的可执行文件是/system/bin/app_process,而不是zygote。为什么叫app_process?它跟Zygote到底是什么关系?

说实话,我第一次看到这个命名时也愣了一下。后来翻了源码才明白,这背后其实有一段设计演变的故事。

3.1 app_process是什么?

app_process,说白了就是Zygote进程的“壳”。它是一个C++编写的可执行文件,源码在frameworks/base/cmds/app_process/目录下。

它的核心任务就一个:启动Java运行时环境,然后调用Java层的main方法

你想想看,Android系统启动时,内核跑起来了,init进程跑起来了,但这时候还没有Java虚拟机呢。而Zygote是所有Java进程的父进程,它本身必须是一个Java进程。这就产生了一个“先有鸡还是先有蛋”的问题——谁来启动Java虚拟机?

答案就是app_process。它用C++写,直接编译成原生可执行文件。它负责加载libandroid_runtime.so,创建Java虚拟机,然后找到Zygote类的main方法并调用。

关键点:app_process = C++壳 + Java虚拟机启动器 + Zygote入口调用者

3.2 main函数的调用链

好,我们来看看这条调用链到底是怎么走的。我习惯把这条链分成三段:

  1. 原生层入口:app_process的main()函数
  2. 桥接层:AndroidRuntime::start()
  3. Java层入口:ZygoteInit.main()

咱们一步步拆开看。

3.2.1 第一段:app_process的main()

源码位置:frameworks/base/cmds/app_process/app_main.cpp

int main(int argc, char* const argv[])
{
    // 解析参数,判断是启动Zygote还是普通应用
    AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
    
    // 处理启动参数
    // 如果是zygote模式,会设置"--zygote"标志
    // 如果是system_server模式,会设置"--application"标志
    
    // 关键调用
    runtime.start("com.android.internal.os.ZygoteInit", 
                  args, zygote);
}

这里有个细节我当年踩过坑。AppRuntime是AndroidRuntime的子类,它重写了onVmCreated()方法。这个方法在Java虚拟机创建完成后会被回调。我在项目中遇到过一个问题,就是自定义的AppRuntime子类里,onVmCreated执行时机比预期晚,导致一些初始化逻辑顺序错了。

避坑指南:如果你需要自定义Zygote启动逻辑,记住onVmCreated是在JVM创建完成后、Java main方法调用前执行的。这个时机点很关键。

3.2.2 第二段:AndroidRuntime::start()

源码位置:frameworks/base/core/jni/AndroidRuntime.cpp

这个方法做了三件大事:

  • 启动Java虚拟机:调用JNI_CreateJavaVM()
  • 注册JNI函数:把原生方法注册到Java层
  • 调用Java main方法:通过JNI找到ZygoteInit类,调用它的main()

代码逻辑大致是这样:

void AndroidRuntime::start(const char* className, ...)
{
    // 1. 启动JVM
    JNIEnv* env;
    JavaVM* javaVM;
    JavaVMOption options[...];
    // 设置各种JVM参数...
    JNI_CreateJavaVM(&javaVM, &env, &args);
    
    // 2. 注册JNI函数
    register_jni_procs(gRegJNI, NELEM(gRegJNI));
    
    // 3. 找到Java类并调用main方法
    jclass clazz = env->FindClass(className);
    jmethodID method = env->GetStaticMethodID(clazz, "main", 
        "([Ljava/lang/String;)V");
    env->CallStaticVoidMethod(clazz, method, args);
}

嗯,这里要注意一点。JNI_CreateJavaVM这个调用,其实是在startReg之前还是之后?我翻过不同Android版本的代码,顺序略有差异。在Android 8.0之后,是先创建JVM,再注册JNI。但早期版本是反过来的。这个顺序会影响一些原生库的初始化行为。

3.2.3 第三段:ZygoteInit.main()

终于到Java层了。ZygoteInit.main()是Zygote进程的Java入口,它做了以下几件事:

public static void main(String[] argv) {
    // 1. 创建ZygoteServer,准备socket监听
    ZygoteServer zygoteServer = new ZygoteServer();
    
    // 2. 预加载资源(类、字体、系统资源等)
    preload(bootTimingsTraceLog);
    
    // 3. 启动SystemServer
    if (startSystemServer) {
        startSystemServer(abiList, zygoteSocketName, zygoteServer);
    }
    
    // 4. 进入循环,等待fork请求
    zygoteServer.runSelectLoop(abiList);
    
    // 5. 关闭服务
    zygoteServer.closeServerSocket();
}

我个人觉得,ZygoteInit.main()最精妙的设计就是那个runSelectLoop。它通过select机制监听socket,收到请求就fork子进程。这种设计让Zygote成为一个“永不退出”的进程,一直等着给应用“接生”。

3.3 调用链全景图

把三段串起来,整个调用链就是:

层级 函数 作用
原生层 app_process::main() 解析参数,创建AppRuntime
原生层 AndroidRuntime::start() 创建JVM,注册JNI,调用Java main
Java层 ZygoteInit.main() 预加载资源,启动SystemServer,进入select循环

注意:app_process这个可执行文件不仅用于启动Zygote,也可以启动普通Java应用。区别在于启动参数中是否带有--zygote标志。我曾经在调试一个系统应用时,误把普通应用当Zygote启动,结果预加载资源那一步直接OOM了。所以,参数解析那一步千万别搞错。

3.4 为什么叫app_process?

最后聊点历史。app_process这个名字,其实反映了它的原始设计意图——它是一个“应用进程启动器”。早期Android版本中,每个Java应用都是通过app_process直接启动的。后来Zygote机制引入后,app_process变成了Zygote的载体,但名字没改。

我个人觉得,这算是一个历史遗留的命名。如果让我重新设计,可能会叫它zygote_launcher或者java_vm_starter。但话说回来,Android开源项目里这种命名不一致的地方还挺多的,习惯了就好。

好,这一章我们搞清楚了app_process的本质,以及从main函数到ZygoteInit.main的完整调用链。下一章,我们会深入ZygoteInit内部,看看预加载到底加载了哪些东西,以及那个神秘的runSelectLoop是怎么工作的。