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函数的调用链
好,我们来看看这条调用链到底是怎么走的。我习惯把这条链分成三段:
- 原生层入口:app_process的main()函数
- 桥接层:AndroidRuntime::start()
- 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是怎么工作的。