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_process64app_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 启动流程的完整链路

好了,咱们把整个链路串起来:

  1. 内核启动init进程
  2. init解析 init.rc,发现 service zygote
  3. init fork出子进程,执行 /system/bin/app_process
  4. app_process解析参数,识别到 --zygote
  5. 进入Zygote的main函数,开始Java世界的初始化

这里有个细节你可能没注意到——init在启动Zygote时,其实还做了件事:它把Zygote的进程ID写到了 /dev/cpuset/foreground/tasks。这是为了把Zygote绑定到特定的CPU核心上,保证它不被干扰。

我个人觉得,理解Zygote的启动入口,关键就两点:一是 init.rc 里怎么定义它,二是那些参数到底在控制什么。搞清楚了这些,你就能回答一个经典面试题:“如果我想让Zygote启动时多预加载一个类,应该改哪里?”

答案其实很简单——要么改 init.rc 传个自定义参数进去,要么直接改Zygote的Java代码。但具体怎么改,那就是下一节的内容了。