4、Zygote进程优化:Zygote启动参数调优、预加载资源裁剪、Class预加载优化

Zygote,这个词在Android系统里,说白了就是「孵化器」的意思。所有应用进程,都是它fork出来的。所以Zygote的启动速度,直接决定了整个系统的开机时间。我做过不少项目,每次优化开机速度,Zygote这块都是必啃的硬骨头。

今天咱们就聊聊Zygote优化的三个核心方向:启动参数调优、预加载资源裁剪、Class预加载优化。这三个点,你吃透了,开机速度至少能快个10%-20%。

4.1 Zygote启动参数调优

Zygote启动时,会带一堆参数。这些参数,很多厂商都是默认值,其实大有文章可做。

先看一个典型的Zygote启动命令:

// 默认启动参数示例
/system/bin/app_process -Xzygote /system/bin --zygote --start-system-server

这里面有几个关键参数,我一个个说。

4.1.1 堆内存参数调整

Zygote进程的堆内存,默认是128MB到256MB。但说实话,这个值偏保守了。我遇到过一台低端设备,Zygote启动时频繁触发GC,导致开机时间硬生生多了3秒。

为什么会这样?因为Zygote要预加载大量资源,堆太小,GC就频繁。GC一多,CPU就忙不过来。

我个人习惯,会把最小堆调到192MB,最大堆调到384MB。像这样:

// 调优后的参数
setprop dalvik.vm.heapsize 384m
setprop dalvik.vm.heapstartsize 16m
setprop dalvik.vm.heapgrowthlimit 192m

注意,heapgrowthlimit是单个应用能用的最大堆,别设太大,否则低端机扛不住。

4.1.2 并行GC与后台GC

Zygote启动时,默认是串行GC。这效率太低了。我建议开启并行GC和后台GC:

// 开启并行GC
setprop dalvik.vm.parallelGCThreads 2
setprop dalvik.vm.backgroundGcEnabled true

嗯,这里要注意:并行GC线程数别设太多,2-4个就够了。设多了,线程切换开销反而更大。

4.2 预加载资源裁剪

Zygote启动时,会预加载一堆资源。这些资源,很多应用根本用不到。说白了,就是白费功夫。

我记得有一次,帮客户优化一款低端机。开机时间要控制在18秒以内,但实测总是19秒多。一查,Zygote预加载了300多张图片,其中200张是系统主题资源。但那个设备,根本就没装第三方主题。

所以,资源裁剪的核心思路就一个:只加载真正需要的资源

4.2.1 裁剪预加载图片

预加载图片的列表,在frameworks/base/core/res/res/values/arrays.xml里。像这样:

<!-- 预加载图片列表 -->
<string-array name="preloaded_drawables">
    <item>@drawable/ic_menu_gallery</item>
    <item>@drawable/ic_menu_help</item>
    <item>@drawable/ic_menu_manage</item>
    <!-- 这里可能有几百项 -->
</string-array>

我的做法是:先统计设备上最常用的50个应用,看看它们用了哪些drawable。没用的,直接删掉。一般能砍掉30%-40%的预加载图片。

小技巧: 可以用adb shell dumpsys package来查看应用依赖的资源。别凭感觉删,要基于数据。

4.2.2 裁剪预加载布局

除了图片,Zygote还会预加载一些布局文件。比如screen_simple.xmlalert_dialog.xml这些。但有些布局,比如keyguard_screen_touch_emergency.xml,在低端机上根本用不到。

我曾经在项目里,把预加载布局从120个砍到了80个。开机时间直接降了1.2秒。效果很明显。

4.3 Class预加载优化

Class预加载,是Zygote优化的重头戏。Zygote启动时,会加载大量Java类。这些类,很多应用根本不会用到。

你想想看,Zygote加载了1000个类,但实际只用了600个。那400个类,就是白白浪费了CPU和内存。

4.3.1 分析预加载类列表

预加载类的列表,在frameworks/base/config/preloaded-classes文件里。打开一看,密密麻麻几千行。

我的做法是:先跑一遍开机流程,用dumpsys meminfotraceview看看哪些类被真正加载了。然后对比预加载列表,找出那些「只预加载、不实际使用」的类。

// 查看实际加载的类
adb shell dumpsys meminfo --oom zygote | grep "Class"

嗯,这里要注意:有些类虽然开机时没用,但后续系统服务会用到。别一刀切,要逐个分析。

4.3.2 延迟加载策略

对于确实需要、但开机时用不到的类,我建议用延迟加载。说白了,就是用到的时候再加载。

比如,android.media.MediaPlayer这个类,开机时基本用不到。但Zygote默认会预加载它。我的做法是:从预加载列表里删掉它,然后在SystemServer里按需加载。

核心原则: 预加载的类,应该是「开机后5秒内一定会用到的类」。其他的,统统延迟加载。

4.3.3 使用ClassLoader优化

还有一个容易被忽略的点:ClassLoader的加载顺序。Zygote默认用的是PathClassLoader,它会从多个路径加载类。如果路径顺序不对,加载效率会很低。

我建议调整ClassLoader的搜索路径,把最常用的路径放在前面。像这样:

// 优化ClassLoader路径顺序
PathClassLoader classLoader = new PathClassLoader(
    "/system/framework/framework.jar:/system/framework/ext.jar",
    ClassLoader.getSystemClassLoader()
);

把framework.jar放在前面,因为90%的类都在这里面。这样能减少路径搜索时间。

4.4 实战避坑指南

说了这么多,最后分享几个我踩过的坑。

避坑1: 我曾经把预加载图片砍得太狠,结果系统UI启动时,有些图标显示不出来。后来才知道,有些图片虽然开机时没用,但系统服务启动后会用到。所以,裁剪前一定要做全量测试。
避坑2: 调整堆内存参数时,别只看Zygote。Zygote fork出来的应用进程,也会继承这些参数。如果堆设得太大,低端机上的应用会频繁OOM。我建议在Zygote层面设一个合理的上限,然后在应用层面再单独调整。
避坑3: Class预加载优化,别只看数量。有些类虽然体积小,但加载时依赖链很长。比如android.view.View,它一个类加载,会触发几十个相关类的加载。这种类,就算开机用不到,也别轻易删。删了反而得不偿失。

好了,Zygote优化的三个核心方向,就聊到这儿。下一章,咱们聊聊SystemServer的启动优化。那个坑更多,但优化空间也更大。