赞
踩
在Zygote初始化的时候,会调用到ZygoteInit的main方法。在注册了ZygoteSocket的控制通道之后,就调用preload方法去加载一些预加载的数据。
- public static void main(String argv[]) {
- try {
- // Start profiling the zygote initialization.
- SamplingProfilerIntegration.start();
-
- registerZygoteSocket();
- EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
- SystemClock.uptimeMillis());
- preload();
- ...
preload方法会加载三个部分,preloadClasses,preloadResources和preloadOpenGL。
- static void preload() {
- preloadClasses();
- preloadResources();
- preloadOpenGL();
- }
preloadClasses的源代码不长,我们简单看一下:
首先,打开PRELOADED_CLASSES文件,读取文件中的内容
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream(PRELOADED_CLASSES);
如果不是以'#'开头的,则加载该类:
Class.forName(line);
这个PRELOADED_CLASSES文件位于frameworks/base/preloaded-classes。
我们看看这个文件的前几行:
- # Classes which are preloaded by com.android.internal.os.ZygoteInit.
- # Automatically generated by frameworks/base/tools/preload/WritePreloadedClassFile.java.
- # MIN_LOAD_TIME_MICROS=1250
- # MIN_PROCESSES=10
- android.R$styleable
- android.accounts.Account
- android.accounts.Account$1
- android.accounts.AccountManager
- android.accounts.AccountManager$12
- android.accounts.AccountManager$13
- android.accounts.AccountManager$6
- android.accounts.AccountManager$AmsTask
- android.accounts.AccountManager$AmsTask$1
- android.accounts.AccountManager$AmsTask$Response
- android.accounts.AccountManagerFuture
下面是完整代码,有删节。
- private static void preloadClasses() {
- final VMRuntime runtime = VMRuntime.getRuntime();
-
- InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream(PRELOADED_CLASSES);
- if (is == null) {
- Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
- } else {
- Log.i(TAG, "Preloading classes...");
- long startTime = SystemClock.uptimeMillis();
-
- // Drop root perms while running static initializers.
- setEffectiveGroup(UNPRIVILEGED_GID);
- setEffectiveUser(UNPRIVILEGED_UID);
-
- // Alter the target heap utilization. With explicit GCs this
- // is not likely to have any effect.
- float defaultUtilization = runtime.getTargetHeapUtilization();
- runtime.setTargetHeapUtilization(0.8f);
-
- // Start with a clean slate.
- System.gc();
- runtime.runFinalizationSync();
- Debug.startAllocCounting();
-
- try {
- BufferedReader br
- = new BufferedReader(new InputStreamReader(is), 256);
-
- int count = 0;
- String line;
- while ((line = br.readLine()) != null) {
- // Skip comments and blank lines.
- line = line.trim();
- if (line.startsWith("#") || line.equals("")) {
- continue;
- }
-
- try {
- Class.forName(line);
- if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
- System.gc();
- runtime.runFinalizationSync();
- Debug.resetGlobalAllocSize();
- }
- count++;
- } catch (ClassNotFoundException e) {
- Log.w(TAG, "Class not found for preloading: " + line);
- } catch (UnsatisfiedLinkError e) {
- Log.w(TAG, "Problem preloading " + line + ": " + e);
- } catch (Throwable t) {
- Log.e(TAG, "Error preloading " + line + ".", t);
- if (t instanceof Error) {
- throw (Error) t;
- }
- if (t instanceof RuntimeException) {
- throw (RuntimeException) t;
- }
- throw new RuntimeException(t);
- }
- }
-
- Log.i(TAG, "...preloaded " + count + " classes in "
- + (SystemClock.uptimeMillis()-startTime) + "ms.");
- } catch (IOException e) {
- Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
- } finally {
- IoUtils.closeQuietly(is);
- // Restore default.
- runtime.setTargetHeapUtilization(defaultUtilization);
-
- // Fill in dex caches with classes, fields, and methods brought in by preloading.
- runtime.preloadDexCaches();
-
- Debug.stopAllocCounting();
-
- // Bring back root. We'll need it later.
- setEffectiveUser(ROOT_UID);
- setEffectiveGroup(ROOT_GID);
- }
- }
- }

先看看两个重要参数:MIN_LOAD_TIME_MICROS和MIN_PROCESSES。
- public class WritePreloadedClassFile {
-
- /**
- * Preload any class that take longer to load than MIN_LOAD_TIME_MICROS us.
- */
- static final int MIN_LOAD_TIME_MICROS = 1250;
-
- /**
- * Preload any class that was loaded by at least MIN_PROCESSES processes.
- */
- static final int MIN_PROCESSES = 10;
- ...
我们再往下看,这个工具的用法:
- public static void main(String[] args) throws IOException,
- ClassNotFoundException {
- if (args.length != 1) {
- System.err.println("Usage: WritePreloadedClassFile [compiled log]");
- System.exit(-1);
- }
- String rootFile = args[0];
- Root root = Root.fromFile(rootFile);
- ...
这个工具要处理的内容,是一种叫compiled log的东西。
这个compiled log,是经由frameworks/base/tools/preload/Compile.java所编译出来的。
于是我们再看Compile.java的源码,
- public class Compile {
-
- public static void main(String[] args) throws IOException {
- if (args.length != 2) {
- System.err.println("Usage: Compile [log file] [output file]");
- System.exit(0);
- }
- ...
这个log是谁打出来的呢?靠跟踪是查不下去了,我们需要看另一头,虚拟机是如何打印出这个log的。
我们先从Android 2.3说起,在这个版本上,有一个宏LOG_CLASS_LOADING,打开这个宏,就可以让虚拟机将类的信息输出到log出。
[http://androidxref.com/2.3.7/xref/dalvik/vm/oo/Class.c#22]
虚拟机会输出一个这样格式的Log,用于记录一个类的加载时间。
- LOG(LOG_INFO, "PRELOAD", "%c%d:%d:%d:%s:%d:%s:%lld\n", type, ppid, pid, tid,
- get_process_name(), (int) clazz->classLoader, clazz->descriptor,time);
第1个参数:type:
定义类开始
我们看下源码就一目了然了:
- static void logClassLoadWithTime(char type, ClassObject* clazz, u8 time) {
- pid_t ppid = getppid();
- pid_t pid = getpid();
- unsigned int tid = (unsigned int) pthread_self();
-
- LOG(LOG_INFO, "PRELOAD", "%c%d:%d:%d:%s:%d:%s:%lld\n", type, ppid, pid, tid,
- get_process_name(), (int) clazz->classLoader, clazz->descriptor,
- time);
- }
为了计时方便,我们将dvmGetThreadCpuTimeNsec的计时封装在函数内,定义一个包装函数,代码如下:
- /*
- * Logs information about a class loading.
- */
- static void logClassLoad(char type, ClassObject* clazz) {
- logClassLoadWithTime(type, clazz, dvmGetThreadCpuTimeNsec());
- }
类的定义的点都在findClassNoInit方法中,代码见:http://androidxref.com/2.3.7/xref/dalvik/vm/oo/Class.c#1411
类的初始化的打点在dvmInitClass中,代码见:http://androidxref.com/2.3.7/xref/dalvik/vm/oo/Class.c#4228
4.x之后,Class.c变成了Class.cpp,不过对于打印类加载信息这部分没有什么实质上的变化。
- #if LOG_CLASS_LOADING
- static void logClassLoadWithTime(char type, ClassObject* clazz, u8 time) {
- pid_t ppid = getppid();
- pid_t pid = getpid();
- unsigned int tid = (unsigned int) pthread_self();
-
- ALOG(LOG_INFO, "PRELOAD", "%c%d:%d:%d:%s:%d:%s:%lld", type, ppid, pid, tid,
- get_process_name(), (int) clazz->classLoader, clazz->descriptor,
- time);
- }
- static void logClassLoad(char type, ClassObject* clazz) {
- logClassLoadWithTime(type, clazz, dvmGetThreadCpuTimeNsec());
- }
- #endif
定义类还是在findClassNoInit,http://androidxref.com/4.4.4_r1/xref/dalvik/vm/oo/Class.cpp#1473
初始化类还是在dvmInitClass中,http://androidxref.com/4.4.4_r1/xref/dalvik/vm/oo/Class.cpp#4241
下面我们摘取一段log中抽取出来的一个完整的片段,包含了android.text.format.DateFormat类的定义和初始化一个对象的完整日志。
- I/PRELOAD ( 6327): >356:6327:1074467156:unknown:0:Ljava/lang/Object;:206523751
- I/PRELOAD ( 6327): <356:6327:1074467156:unknown:0:Ljava/lang/Object;:206921667
log终于大功告成了,下面我们回到frameworks/base/tools/preload/Compile.java,继续讨论如何将log转化为compiled log.
frameworks/base/tools/preload/Compile.java是一个host的工具,运行在PC上。好在是java写的,还算跨平台,在Linux下编译出来的jar在Windows下也可以用。
按照新冬说的办法: mmm frameworks/base/tools/preload/
然后从out/host/linux-x86/framework中将preload.jar复制到可以用adb连接手机的PC上。
我们继续看Compile.java的源代码:
- 39 Root root = new Root();
- 40
- 41 List<Record> records = new ArrayList<Record>();
- 42
- 43 BufferedReader in = new BufferedReader(new InputStreamReader(
- 44 new FileInputStream(args[0])));
- 45
- 46 String line;
- 47 int lineNumber = 0;
- 48 while ((line = in.readLine()) != null) {
- 49 lineNumber++;
- 50 if (line.startsWith("I/PRELOAD")) {
- 51 try {
- 52 String clipped = line.substring(19);
- 53 records.add(new Record(clipped, lineNumber));
- 54 } catch (RuntimeException e) {
- 55 throw new RuntimeException(
- 56 "Exception while recording line " + lineNumber + ": " + line, e);
- 57 }
- 58 }
- 59 }

说实话,原生这段逻辑的处理方式实在是过于简单了,对log格式的处理实在是不足够现代化。每条期待TAG为PRELOAD,其它的log全部过滤。并且,取到type值的办法用的是最土的直接数出19个字符这样的方法。这样导致,用logcat -vtime抓出来的log是无法识别的。
下面我们来分析Record的生成过程。这里显示出现有设计的一个不足,就是采用":"来做分隔。
代码如下:
- 125 line = line.substring(1);
- 126 String[] parts = line.split(":");
这就导致了,如果包名中出现了":"的话,就会split失败。为了解决这个问题,只好打补丁。
Android采用了一个非常土的办法,将":"替换成u003A,这个过程需要手动去做。
这一段逻辑的完整代码如下:
- 109 Record(String line, int lineNum) {
- 110 char typeChar = line.charAt(0);
- 111 switch (typeChar) {
- 112 case '>': type = Type.START_LOAD; break;
- 113 case '<': type = Type.END_LOAD; break;
- 114 case '+': type = Type.START_INIT; break;
- 115 case '-': type = Type.END_INIT; break;
- 116 default: throw new AssertionError("Bad line: " + line);
- 117 }
- 118
- 119 sourceLineNumber = lineNum;
- 120
- 121 for (int i = 0; i < REPLACE_CLASSES.length; i+= 2) {
- 122 line = line.replace(REPLACE_CLASSES[i], REPLACE_CLASSES[i+1]);
- 123 }
- 124
- 125 line = line.substring(1);
- 126 String[] parts = line.split(":");
- 127
- 128 ppid = Integer.parseInt(parts[0]);
- 129 pid = Integer.parseInt(parts[1]);
- 130 tid = Integer.parseInt(parts[2]);
- 131
- 132 processName = decode(parts[3]).intern();
- 133
- 134 classLoader = Integer.parseInt(parts[4]);
- 135 className = vmTypeToLanguage(decode(parts[5])).intern();
- 136
- 137 time = Long.parseLong(parts[6]);
- 138 }

我们看看这些patch冒号的代码,如果遇到了新的类,我们也需要去添加新的类。
- 22 /**
- 23 * The delimiter character we use, {@code :}, conflicts with some other
- 24 * names. In that case, manually replace the delimiter with something else.
- 25 */
- 26 private static final String[] REPLACE_CLASSES = {
- 27 "com.google.android.apps.maps:FriendService",
- 28 "com.google.android.apps.maps\\u003AFriendService",
- 29 "com.google.android.apps.maps:driveabout",
- 30 "com.google.android.apps.maps\\u003Adriveabout",
- 31 "com.google.android.apps.maps:GoogleLocationService",
- 32 "com.google.android.apps.maps\\u003AGoogleLocationService",
- 33 "com.google.android.apps.maps:LocationFriendService",
- 34 "com.google.android.apps.maps\\u003ALocationFriendService",
- 35 "com.google.android.apps.maps:MapsBackgroundService",
- 36 "com.google.android.apps.maps\\u003AMapsBackgroundService",
- 37 "com.google.android.apps.maps:NetworkLocationService",
- 38 "com.google.android.apps.maps\\u003ANetworkLocationService",
- ...

比如我不幸遇上了虾米音乐:fm.xiami.yunos:ui,就写这样两行将”:“替换成u003A。
- "fm.xiami.yunos:ui",
- "fm.xiami.yunos\\u003Aui"
这方法土掉渣了,需要一个一个地探雷。。。
光读log还不行,还要去在线查询内存占用情况。我们来看frameworks/base/tools/preload/MemoryUsage.java中的measure方法。
- 221 private MemoryUsage measure() {
- 222 String[] commands = GET_DIRTY_PAGES;
- 223 if (className != null) {
- 224 List<String> commandList = new ArrayList<String>(
- 225 GET_DIRTY_PAGES.length + 1);
- 226 commandList.addAll(Arrays.asList(commands));
- 227 commandList.add(className);
- 228 commands = commandList.toArray(new String[commandList.size()]);
- 229 }
- 230
- 231 try {
- 232 final Process process = Runtime.getRuntime().exec(commands);
这里面要在adb shell中执行一个GET_DIRTY_PAGES的工具命令,我们看看这是何方神圣:
- 160 private static final String CLASS_PATH = "-Xbootclasspath"
- 161 + ":/system/framework/core.jar"
- 162 + ":/system/framework/ext.jar"
- 163 + ":/system/framework/framework.jar"
- 164 + ":/system/framework/framework-tests.jar"
- 165 + ":/system/framework/services.jar"
- 166 + ":/system/framework/loadclass.jar";
- 167
- 168 private static final String[] GET_DIRTY_PAGES = {
- 169 "adb", "shell", "dalvikvm", CLASS_PATH, "LoadClass" };
这个是要调用LoadClass类,位于system/framework/loadclass.jar中。那么这个loadclass.jar又是从哪里来的呢?答案是在frameworks/base/tools/preload/loadclass/目录中。
我们看loadclass目录中的Android.mk:
- LOCAL_PATH := $(call my-dir)
- include $(CLEAR_VARS)
-
- LOCAL_SRC_FILES := $(call all-subdir-java-files)
- LOCAL_MODULE_TAGS := tests
-
- LOCAL_MODULE := loadclass
-
- include $(BUILD_JAVA_LIBRARY)
于是我们make一下这个工具:mmm frameworks/base/tools/preload/loadclass/
然后我们把这个jar push到手机上的/system/framework/中去。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。