玩转Android Framework:Zygote

本文将介绍有关Android Zygote相关的一些知识。

系统版本: Ubuntu 22.04 lts

AOSP分支: android-14.0.0_r28

在阅读本篇建议先阅读:

Android Init Language

juejin.cn/post/734166...
Init进程:

juejin.cn/post/734388...

相信如果是做了比较长时间App开发的话,对这个词应该是不陌生的,可能面试得时候也会被经常问到。

我看了很多Zygote相关的博客,要么是大段大段的抽象概念,感觉像是在应付面试的,要么就是大段大段的贴代码,各种跳转看的人头晕眼胀,所以我想尽量以详尽的叙述和少量关键代码的形式来讲讲,什么是Zygote。

什么是Zygote

Zygote翻译过来就是受精卵的意思,这个名字其实就已经说明了它的作用,是为了诞生"生命"用的。

那么对于Android来说,什么是最重要的"生命"? 那当然是我们的一个又一个App了。

打开模拟器,输入adb shell,然后列出所有进程ps -A,找到我们的zygote64pid:

然后输出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中,启动了zygotezygote_secondary:

这里就会有一个疑问了,怎么出来了2个zygote,还有上面的进程图里面,也出现了zygotezygote64,这其实是因为我们两个版本的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.cppSecondStageMain方法,就可以找到了:

Zygote Native起始工作流程

首先打开/frameworks/base/cmds/app_process/app_main.cpp,这个类便是Zygote Native的起始点,找到main方法:

然后直接看main方法的最后一行,可以看到这三行代码:

可以看到,这里进行了一个判断,我们先看自己最关心的,第一行里面,好像是和Zygote最相关的,如果希望运行到这里,需要zygotetrue,那么这个变量,到底什么情况下会为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方法,进入之后可以看到最后跳转的是AndroidRuntimestart方法:

其它代码暂时不用关心,往下翻,看到这里:

从这里就可以看到,我们进入了上面提到过的startVm方法,那么这个方法,其实就是我们启动虚拟机的地方了,进入这个方法里面,可以看到Android硬编码了一大堆虚拟机参数:

这个方法很长,中间我们都不需要关心,我们只需要知道,这个方法是用来启动虚拟机的就可以了,那么当虚拟机启动好了以后,我们还要做后续的工作。

我们再看最开始的这段代码:

c++ 复制代码
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);

可以看到他传了一个参数,这好像是个Java类,而且从名称上判断,这应该和Zygote初始化有关系。

那么我们的虚拟机是什么时候处理这个参数的?

AndroidRuntimestart方法中,在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里面的nativePreloadAppProcessHALsmaybePreloadGraphicsDriverpreloadSharedLibrariespreloadTextResources这些就不展开说了,有兴趣的可以查看源码。

至此,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新提供的一种新的应用启动方式,是为了加速我们的应用启动的,相关的介绍可以看这里:

juejin.cn/post/692270...

main方法后面的位置,我们可以看到zygoteServer的启动:

从注释我们也可以知道,到这,Zygote就已经进入等待的状态了,剩下的工作就交给ZygoteServer去处理了,至于ZygoteServerZygoteConnection是如何处理请求并fork处新的应用进程的,我后续会找机会再写一篇。

结尾

至此,我们已经梳理了整个Zygote初始化的过程,由于我只贴了关键代码,所以还是建议跟着AOSP源码去看这篇文章,才能理解整个的处理逻辑。

在Zygote的Init过程中,其实可以看到很多SystemServer相关的代码,这是因为Zygote最重要的需要fork进程之一,就是SystemServer了,下一篇我就会写SystemServer的启动流程分析。

相关推荐
追光天使44 分钟前
【Mac】和【安卓手机】 通过有线方式实现投屏
android·macos·智能手机·投屏·有线
小雨cc5566ru1 小时前
uniapp+Android智慧居家养老服务平台 0fjae微信小程序
android·微信小程序·uni-app
一切皆是定数2 小时前
Android车载——VehicleHal初始化(Android 11)
android·gitee
一切皆是定数2 小时前
Android车载——VehicleHal运行流程(Android 11)
android
problc2 小时前
Android 组件化利器:WMRouter 与 DRouter 的选择与实践
android·java
图王大胜3 小时前
Android SystemUI组件(11)SystemUIVisibility解读
android·framework·systemui·visibility
服装学院的IT男7 小时前
【Android 13源码分析】Activity生命周期之onCreate,onStart,onResume-2
android
Arms2067 小时前
android 全面屏最底部栏沉浸式
android
服装学院的IT男7 小时前
【Android 源码分析】Activity生命周期之onStop-1
android
ChinaDragonDreamer10 小时前
Kotlin:2.0.20 的新特性
android·开发语言·kotlin