Android系统启动预加载preload-classes类之重新生成
硬件平台:高通msm8953_64
系统平台:Android 7.1.2
编译平台:Ubuntu 14.04.2 LTS(64bit)
从接触Android系统驱动开始,就有一个需求,怎样提高android系统的启动速度。所以也在网上搜索了很多资料,看了很多优质的博客。很多文章都有提到:Zygote进程预加载类的时候是比较耗时间的
Android系统中preloaded-classes的文件路径:frameworks/base/preloaded-classes
Zygote进程预加载的那一部分比较耗时,这是一种用空间换取时间的优化方法。在预加载的过程中,有一部分是预加载Class。它的主要逻辑就是从一个preloaded-classes的文件中读取Class加载到内存中。preloaded-classes里面都是各个进程常用的类,所以很多人的建议是,不要去动这一部分的代码,也有建议是删掉一些加载时间比较长的Class,来缩短开机时间。
那么这里就有三个问题,我们先将问题提列出来,然后在来一一解决。
问题一:能否在系统启动过程中不加载preloaded-classes类
问题二:加载preloaded-classes类是按照什么逻辑标准来加载的
问题三:如何重新生成preloaded-classes类文件
1、 解答一:能否在系统启动过程中不加载preloaded-classes类?
说实话,这个疑问还真是我最开始的时候的想法。既然目的是为了加快系统启动,那么我们让系统在启动过程中不加载preloaded-classes类,那岂不是最快的方法了。然后就开始着手验证:我将源码中的preloaded-classes文件全部删除
方法如下:
rm -f frameworks/base/preloaded-classes
rm -f out/target/product/msm8953_64/system/etc/preloaded-classes
然后重新编译整个工程文件:make -j12
或者
只编译system.image文件:make systemimage -j12
然后重新烧入android的所有系统固件,发现android系统在启动过程中运行到开机动画之后就卡死了,无法正常的进入到系统launcher桌面。 经过查看系统启动信息发现,Zygote进程在ZygoteInit类的main()函数中,创建完Socket服务端后还不能立即孵化新的进程,因为这个"卵"中还缺少必要的"核酸",这个"核酸"就是预装的Framework大部分类及资源。
2、 解答二:加载preloaded-classes类是按照什么逻辑标准来加载的?
这里有一位前辈总结的很不错,可以参考一下。
链接:http://news.tuxi.com.cn/viewtt/q/20171227G00RSK00.html
启动过程中,首先通过registerZygoteSocket()登记端口,接着调用preloadClasses()装载相关类。这里大概要装载1000多个类,具体装载类见platform\frameworks\base\preloaded-classes。preloaded-classes这个文件是由WritePreloadedClassFile.java文件自动生成。
具体文件路径:frameworks/base/tools/preload/WritePreloadedClassFile.java
该类中有两个比较重要的定义:
static final int MIN_LOAD_TIME_MICROS = 1250;
static final int MIN_PROCESSES = 10;
分析该类的main函数,有如下一段代码:
public static void main(String[] args) throws IOException, ClassNotFoundException {
String rootFile = args[0];
Root root = Root.fromFile(rootFile);
//2.1部分
Writer out = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(Policy.PRELOADED_CLASS_FILE),
Charset.forName("US-ASCII")));
out.write("# Classes which are preloaded by"
+ " com.android.internal.os.ZygoteInit.\n");
out.write("# Automatically generated by frameworks/base/tools/preload/"
+ WritePreloadedClassFile.class.getSimpleName() + ".java.\n");
out.write("# MIN_LOAD_TIME_MICROS=" + MIN_LOAD_TIME_MICROS + "\n");
out.write("# MIN_PROCESSES=" + MIN_PROCESSES + "\n");
Set<LoadedClass> toPreload = new TreeSet<LoadedClass>();
//2.2部分
for (LoadedClass loadedClass : root.loadedClasses.values()) {
Set<String> names = loadedClass.processNames();
if (!Policy.isPreloadable(loadedClass)) {
continue;
}
if (names.size() >= MIN_PROCESSES ||
(loadedClass.medianTimeMicros() > MIN_LOAD_TIME_MICROS && names.size() > 1)) {
toPreload.add(loadedClass);
}
}
int initialSize = toPreload.size();
System.out.println(initialSize
+ " classses were loaded by more than one app.");
//2.3部分
for (Proc proc : root.processes.values()) {
if (proc.fromZygote() && !Policy.isService(proc.name)) {
for (Operation operation : proc.operations) {
LoadedClass loadedClass = operation.loadedClass;
if (shouldPreload(loadedClass)) {
toPreload.add(loadedClass);
}
}
}
}
//2.4部分
addAllClassesFrom("zygote", root, toPreload);
//2.5部分
for (LoadedClass loadedClass : toPreload) {
out.write(loadedClass.name + "\n");
}
out.close();
}
相关解释:
MIN_LOAD_TIME_MICROS:他是一个临界值,表示装载一个类,花费的时间大于1250微秒。
MIN_PROCESSES:也是一个临界值,表示一个类至少被10个进程装载。
Policy:策略类他会定义排除某些服务进程中,以及某些固定的类。
这个函数主要工作如下:
2.1、 创建preloaded-classes,写入注释信息(如第1步)
2.2、 遍历root.loadedClasses(所有装载的类信息),把符合条件的Class放入预加载文件中。具体条件如下:
2.2.1、 Policy.isPreloadable不属于策略排除之外的类。
2.2.2、 names.size() >= MIN_PROCESSES 至少被10个进程加载的类
2.2.3、 (和2.2或关系) 至少被两个进程加载并且加载时间大于1250微秒
2.3 、遍历root.processes装载类涉及的进程,内部的所有Operation(一个Operation包含一个类的加载开始时间,结束时间等),满足如下条件加入的类加入预加载文件。
2.3.1、 proc.fromZygote()(该进程的父进程不为NUll并且是Zygote进程)&& !Policy.isService (该进程不属于Policy中制定的服务进程)
2.3.2 、shouldPreload() (不属于策略排除之外的类,并且加载时间大于1250微秒)
2.4 、这个函数就是所有Zygote进程中加载过的类并且不属于策略排除之外的类,都要加入到预加载文件中。
2.5、 把前面三步中获取的需要加入预加载文件中的类toPreload写入预加载文件中。
3、 解答三:如何重新生成preloaded-classes类文件?
3.1、先修改frameworks/base/tools/preload/WritePreloadedClassFile.java源码中两个筛选条件定义:
static final int MIN_LOAD_TIME_MICROS = 2500; //1250;
static final int MIN_PROCESSES = 15; //10;
3.2、重新编译frameworks/base/tools/preload/中的所有源码
在linux服务器终端android源码根目录下执行以下几条命令:
source build/envsetup.sh
lunch msm8953_64-user
mmm frameworks/base/tools/preload/

可以看到编译完成之后生成的文件路径:./out/host/linux-x86/framework/preload.jar
3、利用新生成的preload.jar文件,重新生成preloaded-classes加载类列表文件
3.1、先将frameworks/base/preloaded-classes文件删除
3.2然后在linux服务器终端android源码根目录下面这条命令:
java -Xss512M -cp ./out/host/linux-x86/framework/preload.jar WritePreloadedClassFile ./frameworks/base/tools/preload/20100223.compiled
如果成功的话,打印信息如下:

接下来我们来解释一下这条命令各个参数的含义:
参数1:-Xss用于执行该程序所需要的Java虚拟机栈大小,此处使用512MB,java虚拟机默认的栈大小只有128k,不能满足该程序的运行,没有该参数会抛出java.lang.StackOverflowError错误信息。
参数2:./out/host/linux-x86/framework/preload.jar,该Jar是由frameworks/base/tools/preload子项目编译而成的。我们修改了frameworks/base/tools/preload/WritePreloadedClassFile.java源码后都需要重新编译生成新的preload.jar文件
参数3:WritePreloadedClassFile,是要执行的具体类。
参数4:./frameworks/base/tools/preload/20100223.compiled,是指frameworks/base/tools/preload目录下的那几个具体的.compiled文件,这里的20100223.compiled是工程中自带的,如下图所示。

执行完以上命令后,会在frameworks/base目录下产生preload-classes文本文件。从该命令的执行情况来看,预装的Java类信息包含在20100223.compiled文件中,而这个文件却是一个二进制文件。尽管我们目前能够确知如何产生preload-classes,这个20100223.compiled文件应该也是可以重新生成的,网上也有说法是使用下面的命令:
java -Xss512M -cp ./out/host/linux-x86/framework/preload.jar Compile logcat.txt 20180720.compiled
实际上我没有运行成功。
参考链接: