ZYGOTE
前言
OK,这是Android系统启动的第二篇文章。第二篇我们讲解一个我们一直都在用,但是却很少提起的进程---Zygote。 提到Zygote可能了解一些的小伙伴会说,它是分裂进程用的。没错它最大的作用的确是分裂进程,但是它除了分裂进程外还做了什么呢。 还是老规矩,让我们抱着几个问题来看文章。最后在结尾,再对问题进行思考回复。
- 你能大概描述一遍Zygote的启动流程吗
- 我们为什么可以执行java代码,和zygote有什么关系
- Zygote到底都做了哪些事情
另外,我最近也看了一些写底层的文章。要么就是言简意赅到只知道Zygote的作用,但是完全不知道如何实现的,就像是背作文一样。要么是全篇都是代码,又臭又长,让人完全没有看下去的动力。
不过作为一个读者,太长的我可能完全不想看,太短的又真的完全就是被课文一点都不明白原理。
因此,本篇文章,仍旧会引入一些代码,方便大家通过代码方便记忆。但是又会将代码进行精简,以免给大家造成过度疲劳。我们的目的都是希望用最少的时间,能掌握更多的知识。
读源码时:不要过于纠结细节,不要过于纠结细节,不要过于纠结细节!重要的事情说三遍!!!
OK,让我们进入正题瞅瞅Zygote到底是个什么东西。
1.C++还是Java
选这个当标题当然是有原因的。我们知道的是Android系统启动的时候,运行的是Linux内核,执行的是C++代码。这是一个很有趣的事情。
因为我们在写App的时候,AndroidStudio默认给我们创建的都是Activity.java,而选择的语言,要么是Java要么就是Kotlin。
启动时候运行的是C++代码,应用层却可以使用Java代码,这到底是为什么呢?其实这就是Zygote的功劳之一。
2.Native层
Init进程创建Zygote时,会调用app_main.cpp的main() 方法。此时它依旧运行的是C++代码。我们通过下面的代码,来看下它到底做了什么。
这里它做的最关键的一件事就是启动AndroidRuntime(Android运行时)。另外这里需要注意的是 start方法 中的 "com.android.internal.os.ZygoteInit" 这个类似于全类名。他到底是做什么的?别着急,大概2分钟后,你可能就会得到答案。
Zygote的新手村:app_main.cpp
int main(int argc, char* const argv[])
{
// 创建Android运行时对象
AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
// 代码省略...
// 调用AppRuntime.start方法,
// 而AppRuntime是AndroidRuntime的子类,并且没有重写start方法
// 因此调用的是AndroidRuntime的start方法
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
}
精简后的代码告诉我们,这里一共就做了两件事,第一件创建AppRuntime,第二件调用start方法。
不过,AppRuntime是AndroidRuntime的子类,他没有重写start方法,因此这里调用的是 AndroidRuntime的start() 方法。
奇迹的诞生地:AndroidRuntime:
这里我依旧只保留关键代码,源码关键注释也进行了保留。由下方代码看出这里做了三件改变命运的事情。
- startVM -- 启动Java虚拟机
- startReg -- 注册JNI
- 通过JNI调用Java方法,执行com.android.internal.os.ZygoteInit 的 main 方法
/*
* Start the Android runtime.
*/
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
/* start the virtual machine */
JNIEnv* env;
if (startVm(&mJavaVM, &env, zygote, primary_zygote) != 0) {
return;
}
/*
* Register android functions.
*/
if (startReg(env) < 0) {
ALOGE("Unable to register all android natives\n");
return;
}
/*
* Start VM. This thread becomes the main thread of the VM, and will
* not return until the VM exits.
*/
jmethodID startMeth = env->GetStaticMethodID(startClass, "main","([Ljava/lang/String;)V");
env->CallStaticVoidMethod(startClass, startMeth, strArray);
}
有了JVM,注册了JNI,我们就可以执行Java代码了。
这里我个人建议不要过于纠结细节,比如JVM是如何创建的,JNI是如何注册的。如果感兴趣的童鞋,可以下载源码,到app_main.cpp里进行查看。这里就不进行赘述了。否则代码量太大,适得其反。
3.Java层
命运的十字路口:ZygoteInit.java:
之所以说是命运的十字路口,因为Zygote会在这里创建SystemServer,但是二者却走向了截然不同的道路。
从这里就开始执行Java代码了,当然这些Java代码是运行在JVM中的。
让我们通过代码来看一下,到底都做了些什么。
class ZygoteInit{
/**
* This is the entry point for a Zygote process. It creates the Zygote server, loads resources,
* and handles other tasks related to preparing the process for forking into applications.
* This process is started with a nice value of -20 (highest priority).
*/
// 上面是源码中的注释,小伙伴们可以自行翻译一下。
// 创建了ZygoteServer,加载资源,并且介绍了这个进程的优先级是最高的-20.
public static void main(String argv[]) {
ZygoteServer zygoteServer = null;
// 1. 预加载资源,常用的:resource,class,library等在此处进行加载
preload(bootTimingsTraceLog);
// 2. 创建ZygoteServer,实际是一个Socket用来进行跨进程间通信用的。
zygoteServer = new ZygoteServer(isPrimaryZygote);
// 3. fork出SystemServer进程,这个进程会创建AMS,ATMS,WMS,电池服务等一切只有你想不到没有它做不到的服务
forkSystemServer(abiList, zygoteSocketName, zygoteServer);
// 4.里面是一个while(true)循环,等待接收AMS创建进程的消息,类似于handler中的Looper.loop()
zygoteServer.runSelectLoop(abiList);
}
}
上面代码里的注释基本说明了ZygoteInit都干了啥。下面会再稍微总结下:
- 创建了ZygoteServer:这是一个Socket相关的服务,目的是进行跨进程通信。
- 预加载preload:预加载相关的资源。
- 创建SystemServer进程:通过forkSystemServer分裂出了两个进程,一个Zygote进程,一个SystemServer进程。而且由于是分裂的,所以新分裂出来的进程也拥有虚拟机,也能调用JNI,也拥有预加载的资源,也会执行后续的代码。
- 执行runSelectLoop():内部是一个while(true)循环,等待AMS创建新的进程的消息。(想想Looper.loop())
4. 戛然而止
没错就是这么突然,Zygote的故事到这就结束了。至于它是如何创建SystemServer,如何去创建App那就是后面的故事了。所以你还记得我的问题嘛?我替你总结一下Zygote到底做了什么:
- 创建虚拟机
- 注册JNI
- 回调Java方法ZygoteInit.java 的main方法,从这儿开始运行Java代码
- 创建ZygoteServer,内部包含Socket用于跨进程通信
- 预加载class,resource,library等相关资源
- fork 出了 SystemServer进程。他俩除了返回的pid不同,剩下一模一样。通过返回值不同来决定剩下的代码如何运行。这个留待后续进行讲解。
- 进入while(true)循环等待,等待AMS创建进程的消息(类似于Looper.loop())
5. 多说一句
这个系列才刚刚进行到第二章,我尽量让文章不是特别长的情况下,既有代码整理思路,又有总结方便记忆。代码是源码中摘抄的,省略了很大一部分,只能保证执行顺序。有兴趣的小伙伴可以下载源码进行查看。
其实最近我看了很多文章和视频,最后却选择写文章来对知识进行总结。归根结底就是因为一句话,好记性不如烂笔头。要是真的想快速记住,真的需要亲自查看一下源码。翻源码的同时,也是在不知不觉中锻炼你阅读源码的能力。 万一以后让你学习一个新的库,你也知道大概该怎么看。
都已经到这儿了,就稍微厚个脸皮,希望各位看官,能够点个赞,加个关注。毕竟一篇文章可能就要耗费几个小时的时间,上一篇文章就几个赞,感谢这几个小伙伴,让我有继续写下去的动力。 真心感谢你们。
另外,文章中有哪里出错,请及时指出。毕竟各位才是真正的大佬。希望各位Androider,可以一起取暖,度过这个互联网寒冬!加油各位!!!