4、AndroidRuntime登场:AndroidRuntime类的初始化、startReg与startVm函数
好,我们继续往下走。上一章我们聊到了Zygote进程是怎么启动的,它调用了AppRuntime的start方法。那AppRuntime是谁?它其实就是AndroidRuntime的子类。说白了,真正的重头戏在AndroidRuntime这个类里。
我个人习惯把AndroidRuntime看作是Java世界的“点火器”。它负责两件大事:第一,把虚拟机给跑起来;第二,把Java层的运行时环境给注册好。今天我们就来拆解这两个核心函数——startVm和startReg。
4.1 AndroidRuntime的初始化流程
我们先看看AndroidRuntime的构造函数。其实它做的事情不多,主要是保存一些全局状态。
// 来自 frameworks/base/core/jni/AndroidRuntime.cpp
AndroidRuntime::AndroidRuntime(char* argBlockStart, size_t argBlockLength)
: mArgBlockStart(argBlockStart)
, mArgBlockLength(argBlockLength)
{
// 保存启动参数
// 设置默认的JNI版本
mJavaVM = nullptr;
mJNIEnv = nullptr;
}
嗯,这里要注意:构造函数本身并不启动虚拟机。它只是把启动参数保存下来,真正的初始化工作是在start方法里完成的。
我在项目中遇到过一个问题:有同事试图在构造函数里调用JNI函数,结果程序直接崩溃。为什么?因为那时候虚拟机还没创建呢。所以记住,AndroidRuntime的构造函数就是个“占位符”,别在里面做复杂操作。
4.2 startVm函数:虚拟机的启动配置
startVm这个函数,说白了就是根据系统属性来配置并启动ART虚拟机。它的代码量很大,我挑几个关键点来讲。
// 简化后的startVm流程
int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote) {
// 1. 设置虚拟机参数
JavaVMOption optList[kNumOptions];
// 2. 填充各种选项
// 3. 创建虚拟机
JNI_CreateJavaVM(pJavaVM, pEnv, &vm_args);
return 0;
}
你想想看,一个Android系统要启动,得配置多少东西?堆大小、调试模式、类路径、GC策略……这些全都在startVm里搞定。
4.2.1 关键配置项解析
| 配置项 | 作用 | 典型值 |
|---|---|---|
| -Xms / -Xmx | 初始/最大堆大小 | 根据设备内存动态调整 |
| -Xcheck:jni | JNI检查模式 | eng/debug版本开启 |
| -Xzygote | 标记为Zygote进程 | Zygote启动时设置 |
| -Xstacksize | 线程栈大小 | 通常为1MB |
我记得有一次调试一个内存泄漏问题,发现Zygote进程的堆大小设置得特别小。查了半天,原来是某个系统属性被改写了。所以这里给大家一个建议:永远不要手动修改这些默认配置,除非你非常清楚自己在做什么。
4.3 startReg函数:注册JNI原生方法
虚拟机启动好了,接下来就是startReg。这个函数负责注册Android系统底层的JNI方法。说白了,就是让Java代码能调用C++代码。
int AndroidRuntime::startReg(JNIEnv* env) {
// 设置线程的JNI环境
androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);
// 注册所有JNI方法
if (register_jni_procs(gRegJNI, gRegJNI_SIZE, env) < 0) {
return -1;
}
return 0;
}
这里有个关键数据结构——gRegJNI。它是一个函数指针数组,里面存了所有需要注册的JNI模块。每个模块都有一个注册函数。
// gRegJNI数组的部分内容
static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_os_Binder),
REG_JNI(register_android_os_Process),
REG_JNI(register_android_os_SystemProperties),
REG_JNI(register_android_view_ViewRootImpl),
// ... 还有几十个模块
};
你想想看,Android系统有多少个JNI模块?我数过,大概有100多个。从Binder到SurfaceFlinger,从Audio到Camera,全都在这里注册。
4.3.1 注册流程的细节
每个模块的注册函数长什么样?我们拿Binder举个例子:
int register_android_os_Binder(JNIEnv* env) {
// 找到Java类
jclass clazz = env->FindClass("android/os/Binder");
// 注册native方法
JNINativeMethod methods[] = {
{"nativeInit", "()V", (void*)android_os_Binder_init},
{"nativeDestroy", "()V", (void*)android_os_Binder_destroy},
};
env->RegisterNatives(clazz, methods, sizeof(methods)/sizeof(methods[0]));
return 0;
}
嗯,这里要注意:FindClass必须在正确的ClassLoader上下文中调用。我在项目中遇到过一个问题:在Zygote进程里注册JNI方法时,某些类找不到。后来发现是因为ClassLoader还没初始化好。解决方案是先确保系统类加载器可用,再注册JNI。
4.4 startVm与startReg的执行顺序
为什么先调startVm再调startReg?这个顺序是固定的,不能乱。
- 先有虚拟机:JNI环境依赖虚拟机,没有虚拟机就没法注册JNI方法。
- 再注册方法:注册JNI方法时,需要用到JNIEnv指针,这个指针是从虚拟机里拿到的。
- 最后启动Java:注册完JNI方法后,才能安全地调用Java代码。
我曾经见过有人试图在startVm之前调用JNI函数,结果直接段错误。说白了,这个顺序就是AndroidRuntime的“开机流程”,一步都不能错。
4.5 总结与思考
AndroidRuntime的登场,标志着Java世界的序幕正式拉开。startVm负责搭建舞台(虚拟机),startReg负责安装道具(JNI方法)。两者配合,才能让Zygote进程顺利进入Java世界。
我个人觉得,理解这两个函数的关键在于:它们不是孤立的。startVm的配置会影响startReg的执行,而startReg的结果又决定了后续Java代码能否正常运行。所以调试启动问题时,一定要把这两个函数当作一个整体来看。
下一章,我们会看到Zygote是如何调用Java层的main方法,正式进入Java世界的。嗯,那才是真正的“破晓时刻”。