本文将介绍有关Android Zygote相关的一些知识。
系统版本: Ubuntu 22.04 lts
AOSP分支: android-14.0.0_r28
在阅读本篇建议先阅读:
Android Init Language
juejin.cn/post/734166...
Init进程:
相信如果是做了比较长时间App开发的话,对这个词应该是不陌生的,可能面试得时候也会被经常问到。
我看了很多Zygote相关的博客,要么是大段大段的抽象概念,感觉像是在应付面试的,要么就是大段大段的贴代码,各种跳转看的人头晕眼胀,所以我想尽量以详尽的叙述和少量关键代码的形式来讲讲,什么是Zygote。
什么是Zygote
Zygote翻译过来就是受精卵的意思,这个名字其实就已经说明了它的作用,是为了诞生"生命"用的。
那么对于Android来说,什么是最重要的"生命"? 那当然是我们的一个又一个App了。
打开模拟器,输入adb shell
,然后列出所有进程ps -A
,找到我们的zygote64
的pid
:
然后输出ps -A | grep 284
,就可以看到好多ppid
为284,也就是说由我们的Zygote孵化出来的进程了:
我们可以看到,这么多的进程,都是由Zygote孵化的,如果说Init是Android世界的起始点,那么Zygote可以说是Android 应用层的起始点了。
为什么我们需要Zygote
看这篇文章的人,应该都基本清楚字节码,虚拟机的这些概念,也能理解,我们的App里面的dex文件,最终是需要一个Dalvik虚拟机来运行的,还有每个App和他们的虚拟机,默认情况下应该就在单独一个进程里。
那么问题来了,如果我们每次启动一个应用,然后每次都从头开始一个进程,然后这个进程去做虚拟机的初始化工作并启动虚拟机,那手机就慢死了,因为每个应用开启前都要进行大量的重复工作。
所以,最好能有一个进程,让他在系统启动的时候去完成这些繁杂的重复工作,把运行应用程序之前所需的一些系统资源加载到内存里,然后常驻在Android系统中,如果有新的应用要启动,那么直接从这个进程fork
出来就好了,而且所有的这些预加载资源,会变成应用间共享的。
Zygote就是起这样一个作用的。
那么现在,我们知道了Zygote的作用和行为,接下来就要看看,Zygote是怎么完成这一系列工作得了。
谁启动了Zygote
Zygote是由Init进程启动的,打开/system/core/rootdir/init.rc
,可以看到如下的代码,在late-init
中,trigger了zygote-start
:
然后在zygote-start
中,启动了zygote
和zygote_secondary
:
这里就会有一个疑问了,怎么出来了2个zygote,还有上面的进程图里面,也出现了zygote
和zygote64
,这其实是因为我们两个版本的zygote一个是对32位的,一个是对64位的,我们可以打开 /system/core/rootdir/init.zygote64_32.rc
,就可以看到zygote_secondary
,就是32位版本的zygote:
根据最上面我们的进程图可以看到,基本上目前的进程主要是由zygote64
进程fork
来的,这个zygote,也就是我们的primary zygote,即主要的zygote,32版本的zygote,即是secondary zygote,即次要的zygote。
那么接下来我们就要找到late-init
是在哪里被trigger的,其实打开/system/core/init/init.cpp
的SecondStageMain
方法,就可以找到了:
Zygote Native起始工作流程
首先打开/frameworks/base/cmds/app_process/app_main.cpp
,这个类便是Zygote Native的起始点,找到main
方法:
然后直接看main
方法的最后一行,可以看到这三行代码:
可以看到,这里进行了一个判断,我们先看自己最关心的,第一行里面,好像是和Zygote最相关的,如果希望运行到这里,需要zygote
为true
,那么这个变量,到底什么情况下会为true
?
再往上翻,就可以看到,zygote
是在什么情况下设置为true
了:
再上面的代码逻辑,我就不贴出来了,主要是处理一些传进来的参数,我们从这里面的代码逻辑,就能看到,当我们的main
函数在有--zygote
这个参数的时候,zygote
就会被设置为true
,那么这个参数什么时候会被传进来?
其实在Init进程启动Zygote的时候,就会传进来了,打开/system/core/rootdir/init.zygote64.rc
,我们可以看到被传递进来的参数里,就有--zygote
:
那么我们就可以得出一个结论,在刚开始的时候
c++
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
这段代码,就会被执行。
接下来的问题就是,这段代码代表着什么?
首先,我们来看runtime
的定义,在main
函数的开始部分,我们就可以看到:
然后我们再看下同个文件的AppRuntime
这个类:
会发现这个类总共就80多行,看起来也没什么,下面我们再看看他的父类AndroidRuntime
,具体在/frameworks/base/core/jni/AndroidRuntime.cpp
,这个类里面,我们就可以看到大量虚拟机相关的代码了,比如startVm
:
好了,我们再回到刚才的代码,进入start
方法,进入之后可以看到最后跳转的是AndroidRuntime
的start
方法:
其它代码暂时不用关心,往下翻,看到这里:
从这里就可以看到,我们进入了上面提到过的startVm
方法,那么这个方法,其实就是我们启动虚拟机的地方了,进入这个方法里面,可以看到Android硬编码了一大堆虚拟机参数:
这个方法很长,中间我们都不需要关心,我们只需要知道,这个方法是用来启动虚拟机的就可以了,那么当虚拟机启动好了以后,我们还要做后续的工作。
我们再看最开始的这段代码:
c++
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
可以看到他传了一个参数,这好像是个Java类,而且从名称上判断,这应该和Zygote初始化有关系。
那么我们的虚拟机是什么时候处理这个参数的?
在AndroidRuntime
的start
方法中,在startVm
再往下翻,就可以看到这样一段代码:
这个className
,从start
开头的地方可以知道,就是我们传进来的com.android.internal.os.ZygoteInit
,再往下翻,我们就可以看到处理的逻辑了:
这段代码的意思,就是执行我们传进来的那个Java类的main方法了:
c++
jmethodID startMeth = env->GetStaticMethodID(startClass, "main", "([Ljava/lang/String;)V");
env->CallStaticVoidMethod(startClass, startMeth, strArray);
接下来,我们要进入Java的世界了。
ZygoteInit预加载资源
打开/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
,并找到它的main
方法:
找到这部分代码:
可以看到这个判断是,如果不启用lazy preload,那么就会开始预加载资源,启用了则跳过,然后再打开/system/core/rootdir/init.zygote64.rc
,可以看到我们的primary zygote是没有启用lazy preload的,但是打开/system/core/rootdir/init.zygote64_32.rc
我们就会看到我们的secondary zygote,启用了lazy preload,所以我们的primary zygote,也就是64位版本的zygote,是一定会执行preload
的:
进入preload
,可以看到Android预先加载了各种资源:
首先我们进入preloadClasses
,可以看到PRELOAD_CLASSES
这个常量:
他的值为:
从代码中,我们可以得知,会一行行的读取这个preloadClasses
文件,并且将对应的类加载进来:
那么这个文件在哪呢? 这个文件其实在/frameworks/base/config/preloaded-classes
,打开这个文件,就可以看到如下内容:
这个文件有17000多行,这些就是我们要预先加载的类了。
再回到preload
,进入preloadResources
方法:
可以看到这部分就是根据R.array.preloaded_drawable
,去加载一些Drawable文件,这个array的定义是在/frameworks/base/core/res/res/values/arrays.xml
下面:
可以看到注释里也提到了这是给zygote预加载资源用的。
至于preload
里面的nativePreloadAppProcessHALs
,maybePreloadGraphicsDriver
,preloadSharedLibraries
,preloadTextResources
这些就不展开说了,有兴趣的可以查看源码。
至此,Zygote就完成资源的预加载。
ZygoteInit启动Zygote Server并开始监听
从上面我们讲过,Zygote除了预加载这些虚拟机和资源文件,还需要做好准备,随时等待被fork
成一个应用进程,那么Zygote是怎么做到的?
其实Zygote是通过LocalSocket来完成这一点的。
再次打开/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
,并找到它的main
方法,可以看到在方法的开头定义的zygoteServer
:
然后下面可以看到zygoteServer
的初始化:
进入构造方法,我们就可以看到使用LocalSocket
相关的代码了:
打开我们模拟器的adb shell,输入:
bash
netstat -nlp | grep LISTEN | grep zygote
就可以看到端口监听是对得上的:
我们可以看到,怎么还有usap
的监听,这是干什么的?
其实这是Android新提供的一种新的应用启动方式,是为了加速我们的应用启动的,相关的介绍可以看这里:
在main
方法后面的位置,我们可以看到zygoteServer
的启动:
从注释我们也可以知道,到这,Zygote就已经进入等待的状态了,剩下的工作就交给ZygoteServer
去处理了,至于ZygoteServer
和ZygoteConnection
是如何处理请求并fork
处新的应用进程的,我后续会找机会再写一篇。
结尾
至此,我们已经梳理了整个Zygote初始化的过程,由于我只贴了关键代码,所以还是建议跟着AOSP源码去看这篇文章,才能理解整个的处理逻辑。
在Zygote的Init过程中,其实可以看到很多SystemServer
相关的代码,这是因为Zygote最重要的需要fork
进程之一,就是SystemServer
了,下一篇我就会写SystemServer
的启动流程分析。