码迷,mamicode.com
首页 > 移动开发 > 详细

Android4.4的zygote进程(上)

时间:2016-09-25 22:00:26      阅读:205      评论:0      收藏:0      [点我收藏+]

标签:

1背景

前些天为了在科室做培训,我基于Android 4.4重新整理了一份关于zygote的文档。从技术的角度看,这几年zygote并没有出现什么大的变化,所以如果有人以前研究过zygote,应该不会对本文写的内容感到陌生。

2zygote进程的描述

在Android中,zygote是整个系统创建新进程的核心装置。从字面上看,zygote是受精卵的意思,它的主要工作就是进行细胞分裂。

zygote进程在内部会先启动Dalvik虚拟机,继而加载一些必要的系统资源和系统类,最后进入一种监听状态。在后续的运作中,当其他系统模块(比如AMS)希望创建新进程时,只需向zygote进程发出请求,zygote进程监听到该请求后,会相应地“分裂”出新的进程,于是这个新进程在初生之时,就先天具有了自己的Dalvik虚拟机以及系统资源。

系统启动伊始,zygote进程就会被init进程启动起来,init进程的详情可参考我写的《Android4.4的init进程》一文,此处不再赘述。我们直接来看init.rc脚本里的相关描述吧。在这个脚本中是这样描述zygote的:

技术分享

可以看到,zygote对应的可执行文件就是/system/bin/app_process,也就是说系统启动时会执行到这个可执行文件的main()函数里。

3zygote进程的实现细节

zygote服务的main()函数位于frameworks\base\cmds\app_process\App_main.cpp文件中,其代码截选如下:

 

[cpp] view plain copy
 
  1. int main(int argc, char* const argv[])  
  2. {  
  3.     . . . . . .  
  4. AppRuntime runtime;  
  5.     const char* argv0 = argv[0];    // /system/bin/app_process  
  6.     argc--;  
  7.     argv++;  
  8.     . . . . . .  
  9.     int i = runtime.addVmArguments(argc, argv); // 会跳过-Xzygote,i的位置对应/system/bin  
  10.     . . . . . .  
  11.     while (i < argc) {  
  12.         const char* arg = argv[i++];        // 应该是/system/bin目录  
  13.         if (!parentDir) {  
  14.             parentDir = arg;  
  15.         } else if (strcmp(arg, "--zygote") == 0) {  
  16.             zygote = true;  
  17.             niceName = "zygote";  
  18.         } else if (strcmp(arg, "--start-system-server") == 0) {  
  19.             startSystemServer = true;  
  20.         }   
  21.         . . . . . .  
  22.     }  
  23.   
  24.     if (niceName && *niceName) {  
  25.         setArgv0(argv0, niceName);  
  26.         set_process_name(niceName);     // 一般改名为“zygote”  
  27.     }  
  28.     runtime.mParentDir = parentDir;  
  29.     if (zygote) {  
  30.         runtime.start("com.android.internal.os.ZygoteInit",  
  31.                 startSystemServer ? "start-system-server" : "");  
  32.     } else if (className) {  
  33.         . . . . . .  
  34.     } else {  
  35.         . . . . . .  
  36.     }  
  37. }  

 

 

3.1AppRuntime的start()

main()函数里先构造了一个AppRuntime对象,即AppRuntime runtime;而后把进程名改成“zygote”,并利用runtime对象,把工作转交给java层的ZygoteInit类处理。

这个AppRuntime类继承于AndroidRuntime类,却没有重载其start(...)函数,所以main()函数中调用的runtime.start(...)其实走的是AndroidRuntime的start(...),而且传入了类名参数,即字符串——“com.android.internal.os.ZygoteInit”。start()函数的主要代码截选如下:

【frameworks/base/core/jni/AndroidRuntime.cpp】 

 

[cpp] view plain copy
 
  1. void AndroidRuntime::start(const char* className, const char* options)  
  2. {  
  3.     . . . . . .  
  4.     const char* rootDir = getenv("ANDROID_ROOT");  
  5.     . . . . . .  
  6.     JniInvocation jni_invocation;  
  7.     jni_invocation.Init(NULL);              // 初始化JNI接口  
  8.     JNIEnv* env;  
  9.     if (startVm(&mJavaVM, &env) != 0) {     // 启动虚拟机  
  10.         return;  
  11.     }  
  12.     onVmCreated(env);  
  13.   
  14.     if (startReg(env) < 0) {             // 注册系统需要的jni函数  
  15.         ALOGE("Unable to register all android natives\n");  
  16.         return;  
  17.     }  
  18.     . . . . . .  
  19.     jclass startClass = env->FindClass(slashClassName);  
  20.     . . . . . .  
  21.         jmethodID startMeth = env->GetStaticMethodID(startClass, "main",  
  22.             "([Ljava/lang/String;)V");  
  23.         . . . . . .  
  24.             env->CallStaticVoidMethod(startClass, startMeth, strArray);  
  25.     . . . . . .  
  26. }  

 

代码中会先初始化JNI接口,并启动Dalvik虚拟机(startVm()),然后注册一些系统需要的jni函数,接着将传入的类名字符串参数辗转传给FindClass(),最后调用env->CallStaticVoidMethod()一句。 

抛开Java层和C++层的概念,上面的流程说白了就是,Zygote进程的main()函数在启动Dalvik虚拟机后,会调用另一个ZygoteInit类的main()静态函数。调用示意图如下:

技术分享

 

3.1.1加载合适的虚拟机动态库

一开始需要初始化JNI接口。

 

[cpp] view plain copy
 
  1. JniInvocation jni_invocation;  
  2. jni_invocation.Init(NULL);  

 

这两句是在Android 4.4上出现的,在Android 4.0上,还没有它们呢。 

jni_invocation的init()的代码如下:
【libnativehelper/JniInvocation.cpp】

 

[cpp] view plain copy
 
  1. bool JniInvocation::Init(const char* library)   
  2. {  
  3. #ifdef HAVE_ANDROID_OS  
  4.   char default_library[PROPERTY_VALUE_MAX];  
  5.   property_get(kLibrarySystemProperty, default_library, kLibraryFallback);  
  6. #else  
  7.   const char* default_library = kLibraryFallback;  
  8. #endif  
  9.   if (library == NULL) {  
  10.     library = default_library;  
  11.   }  
  12.   
  13.   handle_ = dlopen(library, RTLD_NOW);  
  14.   . . . . . .  
  15.   . . . . . .  
  16.   if (!FindSymbol(reinterpret_cast<void**>(&JNI_GetDefaultJavaVMInitArgs_),  
  17.                   "JNI_GetDefaultJavaVMInitArgs")) {  
  18.     return false;  
  19.   }  
  20.   if (!FindSymbol(reinterpret_cast<void**>(&JNI_CreateJavaVM_),  
  21.                   "JNI_CreateJavaVM")) {  
  22.     return false;  
  23.   }  
  24.   if (!FindSymbol(reinterpret_cast<void**>(&JNI_GetCreatedJavaVMs_),  
  25.                   "JNI_GetCreatedJavaVMs")) {  
  26.     return false;  
  27.   }  
  28.   return true;  
  29. }  

 

因为我们使用的是Android系统,所以已经定义了HAVE_ANDROID_OS宏,而且library参数为NULL,于是在JniInvocation的Init()函数中,会走到
[cpp] view plain copy
 
  1. property_get(kLibrarySystemProperty, default_library, kLibraryFallback);  
其中,kLibrarySystemProperty的定义是“persist.sys.dalvik.vm.lib”,这是个系统属性,它记录着系统实际需要用到的是哪种虚拟机,是dalvik还是ART,它们分别对应libdvm.so或libart.so。接着,dlopen()尝试加载libdvm.so或libart.so。这两个so中都export出了JNI_GetDefaultJavaVMInitArgs,JNI_CreateJavaVM和JNI_GetCreatedJavaVMs这三个接口函数。

 

具体加载动态库的函数是dlopen(),代码如下:
【bionic/linker/Dlfcn.cpp】

 

[cpp] view plain copy
 
  1. void* dlopen(const char* filename, int flags) {  
  2.   ScopedPthreadMutexLocker locker(&gDlMutex);  
  3.   soinfo* result = do_dlopen(filename, flags);  
  4.   if (result == NULL) {  
  5.     __bionic_format_dlerror("dlopen failed", linker_get_error_buffer());  
  6.     return NULL;  
  7.   }  
  8.   return result;  
  9. }  

 

 

本文不需细究dlopen()的实现,大家只需知道,它是个强大的库函数,可以打开某个动态库,并将之装入内存。调用dlopen()时传入的第二个参数是RTLD_NOW,它表示加载器会立即计算库的依赖性,从而在dlopen()返回之前,解析出每个未定义变量的地址。

3.1.2启动Dalvik虚拟机,startVm()

初始化JNI环境后,就可以启动Dalvik虚拟机了。下面是startVm()的代码截选:
【frameworks/base/core/jni/AndroidRuntime.cpp】

 

[cpp] view plain copy
 
  1. int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv)  
  2. {  
  3.     . . . . . .  
  4.     JavaVMInitArgs initArgs;  
  5.     JavaVMOption opt;  
  6.     . . . . . .  
  7.     . . . . . .  
  8.     opt.extraInfo = (void*) runtime_exit;  
  9.     opt.optionString = "exit";  
  10.     mOptions.add(opt);  
  11.     . . . . . .  
  12.     // Increase the main thread‘s interpreter stack size for bug 6315322.  
  13.     opt.optionString = "-XX:mainThreadStackSize=24K";  
  14.     mOptions.add(opt);  
  15.     . . . . . .  
  16.     . . . . . .  
  17.   
  18.     initArgs.version = JNI_VERSION_1_4;  
  19.     initArgs.options = mOptions.editArray();  
  20.     initArgs.nOptions = mOptions.size();  
  21.     initArgs.ignoreUnrecognized = JNI_FALSE;  
  22.   
  23.     // 启动dalvik虚拟机  
  24.     if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {  
  25.         . . . . . .  
  26.     }  
  27.     . . . . . .  
  28. }  

 

因为本文阐述的重点不是Dalvik虚拟机,所以就不再深究JNI_CreateJavaVM()了。我们大概知道该函数会调用到刚刚jni_invocation的init()中,FindSymbol()得到的动态库中相应的函数指针即可。

3.1.3注册Android内部需要的函数,startReg()

当虚拟机成功启动后,JNI环境也就建立好了,现在可以把JNIEnv*传递给startReg()来注册一些重要的JNI接口了。startReg()的代码截选如下:
【frameworks/base/core/jni/AndroidRuntime.cpp】

 

[cpp] view plain copy
 
  1. int AndroidRuntime::startReg(JNIEnv* env)  
  2. {  
  3.     androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);  
  4.     . . . . . .  
  5.     env->PushLocalFrame(200);  
  6.     if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {  
  7.         env->PopLocalFrame(NULL);  
  8.         return -1;  
  9.     }  
  10.     env->PopLocalFrame(NULL);  
  11.     return 0;  
  12. }  

 

 
[cpp] view plain copy
 
  1. static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env)  
  2. {  
  3.     for (size_t i = 0; i < count; i++) {  
  4.         if (array[i].mProc(env) < 0) { // 回调每个RegJNIRec数组项的mProc  
  5. #ifndef NDEBUG  
  6.             ALOGD("----------!!! %s failed to load\n", array[i].mName);  
  7. #endif  
  8.             return -1;  
  9.         }  
  10.     }  
  11.     return 0;  
  12. }  
注册jni函数的动作很简单,只是在遍历array数组,并尝试回调每个数组项的mProc回调函数。具体数组项类型的定义如下,而且struct定义的上方还顺带定义了REG_JNI宏:
[cpp] view plain copy
 
  1. #ifdef NDEBUG  
  2.     #define REG_JNI(name)      { name }  
  3.     struct RegJNIRec {  
  4.         int (*mProc)(JNIEnv*);  
  5.     };  
  6. #else  
  7.     #define REG_JNI(name)      { name, #name }  
  8.     struct RegJNIRec {  
  9.         int (*mProc)(JNIEnv*);  
  10.         const char* mName;  
  11.     };  
  12. #endif  
gRegJNI的定义截选如下:
[cpp] view plain copy
 
  1. static const RegJNIRec gRegJNI[] = {  
  2.     REG_JNI(register_android_debug_JNITest),  
  3.     REG_JNI(register_com_android_internal_os_RuntimeInit),   // 举例  
  4.     REG_JNI(register_android_os_SystemClock),  
  5.     REG_JNI(register_android_util_EventLog),  
  6.     REG_JNI(register_android_util_Log),  
  7.     REG_JNI(register_android_util_FloatMath),  
  8.     REG_JNI(register_android_text_format_Time),  
  9.     REG_JNI(register_android_content_AssetManager),  
  10.     . . . . . .  
  11.     . . . . . .  
这些注册动作内部,基本上就是为自己关心的类注册jni接口啦。比如上面的register_com_android_internal_os_RuntimeInit()函数,它的代码如下:

【frameworks/base/core/jni/AndroidRuntime.cpp】

 

[cpp] view plain copy
 
  1. int register_com_android_internal_os_RuntimeInit(JNIEnv* env)  
  2. {  
  3.     return jniRegisterNativeMethods(env, "com/android/internal/os/RuntimeInit",  
  4.                                          gMethods, NELEM(gMethods));  
  5. }  

 

它关心的就是RuntimeInit类,现在为这个类的native成员注册对应的实现函数,这些实现函数就记录在gMethods数组中:
[cpp] view plain copy
 
  1. static JNINativeMethod gMethods[] = {  
  2.     { "nativeFinishInit", "()V",  
  3.         (void*) com_android_internal_os_RuntimeInit_nativeFinishInit },  
  4.     { "nativeZygoteInit", "()V",  
  5.         (void*) com_android_internal_os_RuntimeInit_nativeZygoteInit },  
  6.     { "nativeSetExitWithoutCleanup", "(Z)V",  
  7.         (void*) com_android_internal_os_RuntimeInit_nativeSetExitWithoutCleanup },  
  8. };  
其他“注册动作”的格局大概也都是这样,我们就不赘述了。

3.1.4加载ZygoteInit类

AppRuntime的start()最后会加载Java层次的ZygoteInit类,并利用JNI技术的CallStaticVoidMethod()调用其静态的main()函数。

 

[cpp] view plain copy
 
  1. jclass startClass = env->FindClass(slashClassName);  
  2. . . . . . .  
  3.     jmethodID startMeth = env->GetStaticMethodID(startClass, "main",  
  4.         "([Ljava/lang/String;)V");  
  5.     . . . . . .  
  6.         env->CallStaticVoidMethod(startClass, startMeth, strArray);  

 

 

这是很关键的一步,就在这一步,控制权就转移到Java层次了。

3.2走入Java层——ZygoteInit.java

随着控制权传递到Java层次,ZygoteInit要做一些和Android平台紧密相关的重要动作,比如创建LocalServerSocket对象、预加载一些类以及资源、启动“Android系统服务”、进入核心循环等等。我们先画一张示意图:

技术分享

相应地,我们还可以把前文的调用关系也丰富一下,得到下图:

技术分享

 

3.2.1registerZygoteSocket()

我们先看ZygoteInit的main()函数调用的那个registerZygoteSocket()。这个函数内部其实会利用一个叫作“ANDROID_SOCKET_zygote”的环境变量。可是这个环境变量又是从哪里来的呢?为了解答这个问题,我们需要先看一下init进程service_start()函数。

3.2.1.1先看一下init进程的service_start()

前文我们已经列出了在init.rc脚本中,zygote服务是如何声明的。现在我们只关心其中和socket相关的部分:

 

[plain] view plain copy
 
  1. service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server  
  2.     . . . . . .  
  3.     socket zygote stream 660 root system  
  4.     . .  . . . .  

 

这个服务的socket选项表明,它需要一个名为“zygote”的“流型(stream)”socket。

当init进程真的启动zygote服务时,会走到service_start()。我们现在只关心service_start()里和socket相关的动作。

【system/core/init/Init.c】 

 

[cpp] view plain copy
 
  1. void service_start(struct service *svc, const char *dynamic_args)  
  2. {  
  3.     . . . . . .  
  4.     pid = fork();   // 先fork出新的service进程  
  5.     if (pid == 0)   
  6.     {  
  7.         struct socketinfo *si;  
  8.         struct svcenvinfo *ei;  
  9.         . . . . . .  
  10.         // 再为service进程创建必须的socket接口  
  11.         for (si = svc->sockets; si; si = si->next)   
  12.         {  
  13.             int socket_type = (  
  14.                     !strcmp(si->type, "stream") ? SOCK_STREAM :  
  15.                         (!strcmp(si->type, "dgram") ? SOCK_DGRAM : SOCK_SEQPACKET));  
  16.             int s = create_socket(si->name, socket_type,  
  17.                                       si->perm, si->uid, si->gid);  
  18.             if (s >= 0) {  
  19.                 // 将socket接口记入ANDROID_SOCKET_zygote环境变量  
  20.                 publish_socket(si->name, s);    
  21.             }  
  22.         }  
  23.         . . . . . .  
  24.             if (execve(svc->args[0], (char**) svc->args, (char**) ENV) < 0) {  
  25.                 ERROR("cannot execve(‘%s‘): %s\n", svc->args[0], strerror(errno));  
  26.             }  
  27.      }  
  28.      . . . . . .  
  29.  }  

 

每次为service创建新的子进程后,都会查看该service需要什么socket。比如zygote服务,明确提出需要一个“流型(stream)”的socket。

create_socket()会在/dev/socket目录中创建一个Unix范畴的socket,而后,publish_socket()会把新建的socket的文件描述符记录在以“ANDROID_SOCKET_”打头的环境变量中。比如zygote对应的socket选项中的socket名为“zygote”,那么该socket对应的环境变量名就是“ANDROID_SOCKET_zygote”。

create_socket()的代码截选如下:
【system/core/init/Util.c】

 

[cpp] view plain copy
 
  1. int create_socket(const char *name, int type, mode_t perm, uid_t uid, gid_t gid)  
  2. {  
  3.     struct sockaddr_un addr;  
  4.     int fd, ret;  
  5.     . . . . . .  
  6.     fd = socket(PF_UNIX, type, 0);  
  7. . . . . . .  
  8.     // "/dev/socket/zygote"  
  9.     snprintf(addr.sun_path, sizeof(addr.sun_path), ANDROID_SOCKET_DIR"/%s", name);  
  10.     ret = unlink(addr.sun_path);  
  11.     . . . . . .  
  12.     ret = bind(fd, (struct sockaddr *) &addr, sizeof (addr));  // 给套接字命名  
  13.     . . . . . .  
  14.     chown(addr.sun_path, uid, gid);  
  15.     chmod(addr.sun_path, perm);  
  16.     . . . . . .  
  17.     return fd;  
  18.     . . . . . .  
  19. }  

 

当用socket()函数创建套接字以后,使用bind()将“指定的地址”赋值给“用文件描述符代表的套接字”,一般来说,该操作被称为“给套接字命名”。通常情况下,在一个SOCK_STREAM套接字接收连接之前,必须通过bind()函数用本地地址为套接字命名。对于zygote,其套接字地址应该是“/dev/socket/zygote”。在调用bind()函数之后,socket()函数创建的套接字已经和指定的地址关联起来了,现在向这个地址发送的数据,就可以通过套接字读取出来了。

接下来,service_start()还调用了个publish_socket()函数,该函数的代码如下:
【system/core/init/Init.c】

 

[cpp] view plain copy
 
  1. static void publish_socket(const char *name, int fd)  
  2. {  
  3.     char key[64] = ANDROID_SOCKET_ENV_PREFIX;  
  4.     char val[64];  
  5.   
  6.     strlcpy(key + sizeof(ANDROID_SOCKET_ENV_PREFIX) - 1,  
  7.             name,  
  8.             sizeof(key) - sizeof(ANDROID_SOCKET_ENV_PREFIX));  
  9.     snprintf(val, sizeof(val), "%d", fd);  // 将文件描述符转为字符串  
  10.     add_environment(key, val);  
  11.   
  12.     /* make sure we don‘t close-on-exec */  
  13.     fcntl(fd, F_SETFD, 0);  
  14. }  

 

这么看来,所谓的“发布”(publish),主要是把socket的文件描述符记录进环境变量。

上面代码中的ANDROID_SOCKET_ENV_PREFIX的定义如下:

 

[cpp] view plain copy
 
  1. #define ANDROID_SOCKET_ENV_PREFIX   "ANDROID_SOCKET_"  

 

那么对于zygote而言,就是在环境变量“ANDROID_SOCKET_zygote”里记录文件描述符对应的字符串。

add_environment()的代码如下:

 

[cpp] view plain copy
 
  1. int add_environment(const char *key, const char *val)  
  2. {  
  3.     int n;  
  4.     for (n = 0; n < 31; n++) {  
  5.         if (!ENV[n]) {  
  6.             size_t len = strlen(key) + strlen(val) + 2;  
  7.             char *entry = malloc(len);  
  8.             snprintf(entry, len, "%s=%s", key, val);  
  9.             ENV[n] = entry;  
  10.             return 0;  
  11.         }  
  12.     }  
  13.     return 1;  
  14. }  

 

无非是把字符串记入一个静态数组而已,在后续的代码里,service_start()会调用execve(),并把ENV环境变量传递给execve()。

3.2.1.2registerZygoteSocket()里创建LocalServerSocket

OK,我们已经看到init进程在新fork出的zygote进程里,是如何记录“ANDROID_SOCKET_zygote”环境变量的。现在我们可以回过头来看zygote中的registerZygoteSocket()了,此处会切实地用到这个环境变量。

registerZygoteSocket()的代码如下:
【frameworks/base/core/java/com/android/internal/os/ZygoteInit.java】

 

[cpp] view plain copy
 
  1. private static void registerZygoteSocket() {  
  2.     if (sServerSocket == null) {  
  3.         int fileDesc;  
  4.         try {  
  5.             String env = System.getenv(ANDROID_SOCKET_ENV);  
  6.             fileDesc = Integer.parseInt(env);   // 从环境变量的字符串中解析出文件描述符  
  7.         } catch (RuntimeException ex) {  
  8.             throw new RuntimeException(  
  9.                     ANDROID_SOCKET_ENV + " unset or invalid", ex);  
  10.         }  
  11.   
  12.         try {  
  13.             sServerSocket = new LocalServerSocket(createFileDescriptor(fileDesc));  
  14.         } catch (IOException ex) {  
  15.             throw new RuntimeException(  
  16.                     "Error binding to local socket ‘" + fileDesc + "‘", ex);  
  17.         }  
  18.     }  
  19. }  

 

先从环境变量里读出socket的文件描述符,然后创建LocalServerSocket对象,并记入静态变量sServerSocket中。以后zygote进程会循环监听这个socket,一旦accept到连接请求,就创建命令连接(Command Connection)。监听动作的细节是在runSelectLoop()中,我们会在后文阐述,这里先放下。

 

现在我们可以画一张创建zygote socket接口的示意图,如下:

技术分享

 

请注意,图中明确画出了两个进程,一个add环境变量,另一个get环境变量。

3.2.2预加载一些类——preloadClasses()

注册完socket接口,ZygoteInit会预加载一些类,这些类记录在frameworks/base/preloaded-classes文本文件里。下面是该文件的一部分截选:

 

[plain] view plain copy
 
  1. # Classes which are preloaded by com.android.internal.os.ZygoteInit.  
  2. # Automatically generated by frameworks/base/tools/preload/WritePreloadedClassFile.java.  
  3. # MIN_LOAD_TIME_MICROS=1250  
  4. # MIN_PROCESSES=10  
  5. android.R$styleable  
  6. android.accounts.Account  
  7. android.accounts.Account$1  
  8. android.accounts.AccountManager  
  9. android.accounts.AccountManager$12  
  10. android.accounts.AccountManager$13  
  11. android.accounts.AccountManager$6  
  12. android.accounts.AccountManager$AmsTask  
  13. android.accounts.AccountManager$AmsTask$1  
  14. android.accounts.AccountManager$AmsTask$Response  
  15. . . . . . .  
  16. . . . . . .  

 

在Android4.4上,这个脚本文件已经长达两千七百多行了,它里面记录着加载时间超过1250微秒的类,ZygoteInit尝试在系统启动时就把它们预加载进来,从而省去后续频繁加载时带来的系统开销。

preloadClasses()的代码截选如下:

 

[java] view plain copy
 
  1. private static void preloadClasses()   
  2. {  
  3.     . . . . . .  
  4.     InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream(  
  5.                                             PRELOADED_CLASSES);  // 即"preloaded-classes"  
  6.     . . . . . .  
  7.     . . . . . .  
  8.         try {  
  9.             BufferedReader br = new BufferedReader(new InputStreamReader(is), 256);  
  10.             . . . . . .  
  11.             while ((line = br.readLine()) != null)   
  12.             {  
  13.                 line = line.trim();  
  14.                     . . . . . .  
  15.                     Class.forName(line);  // 使用加载当前类的类加载器来加载指定类  
  16.                     . . . . . .  
  17.                     count++;  
  18.                 . . . . . .  
  19.             }  
  20.             . . . . . .  
  21.         } catch (IOException e) {  
  22.             . . . . . .  
  23.         } finally {  
  24.             . . . . . .  
  25.             runtime.preloadDexCaches();  
  26.             . . . . . .  
  27.         }  
  28.     . . . . . .  
  29. }  

 

在一个while循环里,每次读取一行,然后调用Class.forName()方法来装载类。这个工作量可不小,毕竟有两千多行哩。

 

3.2.3预加载一些系统资源——preloadResources()

除了预加载一些类,zygote进程还要预加载一些系统资源。

 

[java] view plain copy
 
  1. private static void preloadResources()   
  2. {  
  3.     . . . . . .  
  4.         mResources = Resources.getSystem();  
  5.         mResources.startPreloading();  
  6.         if (PRELOAD_RESOURCES) {  
  7.             . . . . . .  
  8.             TypedArray ar = mResources.obtainTypedArray(  
  9.                     com.android.internal.R.array.preloaded_drawables);  
  10.             int N = preloadDrawables(runtime, ar);  
  11.             ar.recycle();  
  12.             . . . . . .  
  13.             ar = mResources.obtainTypedArray(  
  14.                     com.android.internal.R.array.preloaded_color_state_lists);  
  15.             N = preloadColorStateLists(runtime, ar);  
  16.             ar.recycle();  
  17.             . . . . . .  
  18.         }  
  19.         mResources.finishPreloading();  
  20.     . . . . . .  
  21. }  

 

首先,从preloaded_drawables数组资源中读取一个类型数组(TypedArray),具体的资源文件可参考frameworks/base/core/res/res/values/arrays.xml,截选如下:
技术分享


加载第一类资源需要调用preloadDrawables(),逐个加载TypedArray里记录的图片资源:基本上有两大类资源:
1)一类和图片有关(preloaed_drawables)
2)另一类和颜色有关(preloaded_color_state_lists)

 

[java] view plain copy
 
  1. private static int preloadDrawables(VMRuntime runtime, TypedArray ar)   
  2. {  
  3.     int N = ar.length();  
  4.     for (int i=0; i<N; i++) {  
  5.         . . . . . .  
  6.         int id = ar.getResourceId(i, 0);  // 获得i项对应的资源id  
  7.         . . . . . .  
  8.         if (id != 0) {  
  9.             if (mResources.getDrawable(id) == null) {  
  10.                 throw new IllegalArgumentException(  
  11.                         "Unable to find preloaded drawable resource #0x"  
  12.                         + Integer.toHexString(id)  
  13.                         + " (" + ar.getString(i) + ")");  
  14.             }  
  15.         }  
  16.     }  
  17.     return N;  
  18. }  

 

说起来之前mResources.obtainTypedArray()获取TypedArray时,其内部用的是AssetManager。得到TypedArray之后,我们就可以通过调用ar.getResourceId(i, 0)来得到数组项对应的资源id了。

其中的mResources是ZygoteInit的私有静态成员:

 

[java] view plain copy
 
  1. private static Resources mResources;  

 

mResources的getDrawable()函数内部,会调用loadDrawable()。这样,这些图片资源就都加载到ZygoteInit的mResources里了。

另一些资源是颜色资源,是用preloadColorStateLists()加载的:

 

[java] view plain copy
 
  1. private static int preloadColorStateLists(VMRuntime runtime, TypedArray ar) {  
  2.     int N = ar.length();  
  3.     for (int i=0; i<N; i++) {  
  4.         . . . . . .  
  5.         int id = ar.getResourceId(i, 0);  
  6.         . . . . . .  
  7.         if (id != 0) {  
  8.             if (mResources.getColorStateList(id) == null) {  
  9.                 throw new IllegalArgumentException(  
  10.                         "Unable to find preloaded color resource #0x"  
  11.                         + Integer.toHexString(id)  
  12.                         + " (" + ar.getString(i) + ")");  
  13.             }  
  14.         }  
  15.     }  
  16.     return N;  
  17. }  

 

也是在一个for循环里逐个加载颜色集,比如arrays.xml里的

<item>@color/primary_text_dark</item>
这个颜色集的参考文件是frameworks/base/core/res/res/color/primary_text_dark.xml,

技术分享

现在,我们画一张加载系统资源的调用关系图:

技术分享

转自http://blog.csdn.net/codefly/article/details/48403529

Android4.4的zygote进程(上)

标签:

原文地址:http://www.cnblogs.com/lzlltmf/p/5906792.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!