3、Zygote启动流程(中):app_process主函数入口、AndroidRuntime的start函数、JNI环境初始化

好,我们接着往下走。

上一章我们聊了init进程怎么把Zygote拉起来,说白了就是解析了rc文件,fork了一个子进程,然后执行了/system/bin/app_process这个可执行文件。那今天我们就从这个可执行文件的入口开始,一步步走进Zygote的世界。

3.1 app_process的主函数:一切的起点

你想想看,一个C++程序,入口肯定是main函数。app_process也不例外。它的源码在frameworks/base/cmds/app_process/app_main.cpp里。

我直接贴核心代码,咱们边看边聊:

int main(int argc, char* const argv[])
{
    // 创建AppRuntime实例
    AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
    
    // 处理启动参数
    // 这里有个细节:Zygote进程启动时,argv[1]是"-Xzygote"
    // 而普通应用进程启动时,argv[1]是"--application"
    int i;
    for (i = 0; i < argc; i++) {
        if (argv[i][0] != '-') {
            break;
        }
        if (argv[i][1] == '-' && argv[i][2] == 0) {
            ++i;
            break;
        }
        runtime.addOption(strdup(argv[i]));
    }
    
    // 解析剩余参数
    // 这里决定了是启动Zygote还是启动一个普通应用
    bool zygote = false;
    if (strcmp(argv[i], "--zygote") == 0) {
        zygote = true;
    }
    
    // 设置进程名
    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", 
                      ZYGOTE_NICE_NAME, zygote);
    } else {
        runtime.start("com.android.internal.os.ZygoteInit", 
                      "app_process", zygote);
    }
    
    return 0;
}

嗯,这里有个关键点:AppRuntime。它是什么?它是AndroidRuntime的子类。说白了,整个Java世界的启动,全靠这个runtime对象来驱动。

核心要点:

  • app_process的main函数非常简洁,主要就是创建AppRuntime并调用它的start方法
  • 通过命令行参数区分Zygote进程和普通应用进程
  • Zygote进程的nice name是"zygote",普通应用是"app_process"

3.2 AndroidRuntime的start函数:Java世界的点火器

好,现在进入重头戏。AppRuntime继承自AndroidRuntime,但start方法是在父类中实现的。我们来看看frameworks/base/core/jni/AndroidRuntime.cpp里的start函数:

void AndroidRuntime::start(const char* className, 
                           const char* niceName, 
                           bool zygote)
{
    // 设置进程名
    if (niceName) {
        set_process_name(niceName);
    }
    
    // 设置环境变量
    // 这个ANDROID_ROOT很重要,很多资源文件都依赖它
    const char* root = getenv("ANDROID_ROOT");
    if (root == NULL) {
        root = "/system";
        setenv("ANDROID_ROOT", root, 1);
    }
    
    // 启动Java虚拟机
    JniInvocation jni_invocation;
    jni_invocation.Init(NULL);
    JNIEnv* env;
    
    // 创建JavaVM
    JavaVM* javaVM;
    JavaVMOption options[100];
    // ... 各种JNI选项的设置,这里省略
    
    // 关键一步:创建Java虚拟机
    if (JNI_CreateJavaVM(&javaVM, &env, &vm_args) != JNI_OK) {
        // 创建失败的处理
        return;
    }
    
    // 注册Android的JNI函数
    if (startReg(env) < 0) {
        // 注册失败的处理
        return;
    }
    
    // 调用Java类的main方法
    jclass stringClass;
    jobjectArray strArray;
    // ... 构造参数数组
    
    jmethodID mid = env->GetStaticMethodID(startClass, "main", 
                                           "([Ljava/lang/String;)V");
    env->CallStaticVoidMethod(startClass, mid, strArray);
    
    // 注意:这里不会返回,因为ZygoteInit.main()会进入死循环
}

我个人习惯把start函数分成三个阶段来看:

  1. 准备阶段:设置进程名、环境变量
  2. 启动阶段:创建Java虚拟机、注册JNI函数
  3. 执行阶段:调用Java类的main方法

避坑指南:

我曾经在移植Android系统到新平台时,卡在JNI_CreateJavaVM这一步好几天。后来发现是ANDROID_ROOT环境变量没设置对,导致虚拟机找不到核心库。所以,环境变量的设置顺序很重要,一定要在创建虚拟机之前完成。

3.3 JNI环境初始化:连接C++和Java的桥梁

接下来我们重点看看startReg这个函数。它负责注册Android系统所有的JNI函数。说白了,就是告诉Java虚拟机:这些native方法对应的C++实现在哪里。

int AndroidRuntime::startReg(JNIEnv* env)
{
    // 设置线程的JNI环境
    androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);
    
    // 创建JNI环境
    env->PushLocalFrame(200);
    
    // 注册JNI函数
    if (register_jni_procs(gRegJNI, gRegJNI_SIZE, env) < 0) {
        env->PopLocalFrame(NULL);
        return -1;
    }
    
    env->PopLocalFrame(NULL);
    return 0;
}

这里有个重要的数据结构:gRegJNI。它是一个函数指针数组,每个元素指向一个JNI注册函数。我截取一部分给大家看看:

static const RegJNIRec gRegJNI[] = {
    REG_JNI(register_com_android_internal_os_RuntimeInit),
    REG_JNI(register_android_os_SystemClock),
    REG_JNI(register_android_util_EventLog),
    REG_JNI(register_android_content_AssetManager),
    REG_JNI(register_android_content_StringBlock),
    // ... 还有上百个注册函数
};

你想想看,Android系统有多少个JNI函数?光这里注册的就有上百个。每个注册函数内部会调用env->RegisterNatives来建立Java方法和C++函数的映射关系。

JNI注册的两种方式:

方式 说明 优点 缺点
静态注册 函数名按JNI规范命名 简单直接 函数名冗长,不易维护
动态注册 通过RegisterNatives注册 灵活,可动态绑定 需要额外代码

Android系统采用的是动态注册方式。为什么?因为静态注册要求函数名必须按照Java_包名_类名_方法名的格式来命名,你想想看,像com.android.internal.os.RuntimeInit这样的类,对应的C++函数名得有多长?维护起来简直是噩梦。

注意事项:

JNI注册的顺序是有讲究的。我记得有一次调试一个系统服务,发现某个native方法总是调用失败。查了半天,原来是它依赖的另一个JNI函数还没注册。所以,gRegJNI数组的顺序不能随意调整,有依赖关系的必须放在前面。

3.4 小结:从app_process到Java世界的完整链路

好,我们来梳理一下今天的内容。从app_process的main函数开始,到AndroidRuntime的start函数,再到JNI环境的初始化,这条链路其实就做了一件事:把C++的世界和Java的世界连接起来

我个人觉得,理解这个流程的关键在于把握三个核心点:

  • AppRuntime:它是整个过程的驱动器,负责协调各个步骤
  • JavaVM:它是Java世界的基石,所有Java代码都运行在它之上
  • JNI注册:它是两个世界的桥梁,没有它,Java代码就调不了native方法

下一章,我们会深入ZygoteInit的main方法,看看Zygote进程是如何完成初始化,并进入等待客户端连接的循环的。嗯,那里才是Zygote真正开始干活的地方。