玩转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的启动流程分析。

相关推荐
ji_shuke11 分钟前
opencv-mobile 和 ncnn-android 环境配置
android·前端·javascript·人工智能·opencv
sunnyday04262 小时前
Spring Boot 项目中使用 Dynamic Datasource 实现多数据源管理
android·spring boot·后端
幽络源小助理3 小时前
下载安装AndroidStudio配置Gradle运行第一个kotlin程序
android·开发语言·kotlin
inBuilder低代码平台3 小时前
浅谈安卓Webview从初级到高级应用
android·java·webview
豌豆学姐4 小时前
Sora2 短剧视频创作中如何保持人物一致性?角色创建接口教程
android·java·aigc·php·音视频·uniapp
白熊小北极4 小时前
Android Jetpack Compose折叠屏感知与适配
android
HelloBan4 小时前
setHintTextColor不生效
android
洞窝技术6 小时前
从0到30+:智能家居配网协议融合的实战与思考
android
QING6186 小时前
SupervisorJob子协程异常处理机制 —— 新手指南
android·kotlin·android jetpack
毕设源码-朱学姐7 小时前
【开题答辩全过程】以 基于安卓的停车位管理系统与设计为例,包含答辩的问题和答案
android