2、Zygote的启动入口:解析init.rc中Zygote的service定义、启动参数详解
好,咱们接着聊。上一节我讲了Zygote在整个Android启动流程中的位置,说白了它就是Java世界的“盘古”。那问题来了——系统是怎么知道要启动Zygote的?谁告诉init进程去fork它?
答案就在 init.rc 文件里。
我个人习惯,每次分析系统启动,第一件事就是打开这个文件。它就像一张启动蓝图,所有系统服务的启动顺序、依赖关系、参数配置,全写在这里面。
2.1 init.rc 中的 Zygote 服务定义
先看一段真实的 init.rc 片段。嗯,不同Android版本略有差异,但核心结构没变过:
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
priority -20
user root
group root readproc
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart netd
onrestart restart wificond
writepid /dev/cpuset/foreground/tasks
看到第一行没?service zygote 就是告诉init:嘿,给我创建一个叫zygote的服务,它的可执行文件是 /system/bin/app_process。
这里有个细节我当年踩过坑——app_process 其实是个符号链接,它指向 app_process64 或 app_process32。具体是64位还是32位,取决于你的设备架构。我在做某个低端平板项目时,发现Zygote起不来,查了半天才发现是64位镜像烧到了32位分区上……
2.2 启动参数逐项拆解
咱们把启动参数拆开来看,每个参数都有它的使命:
| 参数 | 含义 | 我的理解 |
|---|---|---|
-Xzygote |
传递给ART虚拟机的参数,表示当前运行在Zygote模式 | 这个参数告诉虚拟机:你现在的任务是预加载,别急着跑应用代码 |
/system/bin |
app_process的查找路径 | 其实就是告诉系统去哪找库文件,我见过有人改错这个路径导致启动卡在第一屏 |
--zygote |
标记当前进程以Zygote模式运行 | 核心开关!没有这个参数,app_process就是个普通Java进程 |
--start-system-server |
启动SystemServer | Zygote初始化完成后,会立即fork出SystemServer |
你想想看,为什么需要 --zygote 这个参数?因为 app_process 这个二进制文件其实有两个身份:
- 带
--zygote:它就是Zygote进程,负责预加载和孵化应用 - 不带这个参数:它就是普通的
app_process,用来执行一些简单的Java命令
我曾经在调试一个系统服务时,直接用 app_process 跑了个测试类,结果发现它没有预加载任何框架类,跑起来慢得离谱。嗯,这就是没加 --zygote 的区别。
2.3 关键配置项解析
除了启动参数,init.rc 里还有几个配置项值得注意:
class main —— 服务分类
Zygote属于 main 类服务。init进程启动时会按class启动服务,class main 是最核心的一批。我记得有一次为了调试,把Zygote改到了 late_start 类,结果整个系统启动顺序全乱了……
priority -20 —— 优先级
-20 是Linux进程的最高优先级。为什么Zygote要这么高?因为它要预加载所有应用共享的类,如果它被其他进程抢了CPU,系统启动就会变慢。说白了,它得跑在最前面。
socket zygote stream 660 root system —— 这个厉害了。它创建了一个名为 zygote 的Unix域Socket,权限是660,属主是root,属组是system。所有应用进程都是通过这个Socket向Zygote发送fork请求的。
我当年在做一个安全加固项目时,发现有人试图通过修改这个Socket权限来阻止应用启动。嗯,结果系统直接卡在启动动画了——因为连SystemServer都连不上Zygote了。
2.4 onrestart 的连锁反应
再看 onrestart 部分:
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart netd
onrestart restart wificond
这什么意思?就是说如果Zygote挂了,这些服务也得跟着重启。为什么?因为这些服务都依赖Zygote提供的Java运行环境。Zygote一死,它们就成了无根之木。
我曾经在项目里遇到过Zygote频繁重启的问题,每次重启,音频、相机、网络全部跟着闪断。用户反馈说“手机一卡一卡的”,其实就是Zygote在反复重启。排查下来发现是某个预加载的库有内存泄漏……
注意: 千万不要随意修改 onrestart 的依赖列表。加少了可能导致服务状态不一致,加多了可能引发重启风暴。我见过有人把 surfaceflinger 也加进去,结果Zygote一重启,整个UI也跟着闪黑。
2.5 启动流程的完整链路
好了,咱们把整个链路串起来:
- 内核启动init进程
- init解析
init.rc,发现service zygote - init fork出子进程,执行
/system/bin/app_process - app_process解析参数,识别到
--zygote - 进入Zygote的main函数,开始Java世界的初始化
这里有个细节你可能没注意到——init在启动Zygote时,其实还做了件事:它把Zygote的进程ID写到了 /dev/cpuset/foreground/tasks。这是为了把Zygote绑定到特定的CPU核心上,保证它不被干扰。
我个人觉得,理解Zygote的启动入口,关键就两点:一是 init.rc 里怎么定义它,二是那些参数到底在控制什么。搞清楚了这些,你就能回答一个经典面试题:“如果我想让Zygote启动时多预加载一个类,应该改哪里?”
答案其实很简单——要么改 init.rc 传个自定义参数进去,要么直接改Zygote的Java代码。但具体怎么改,那就是下一节的内容了。