ZygoteAndroid中非常重要的一个进程,它和Init进程SystemServer进程Android中有着不可替代的地位。

Zygote简介

Linux的进程是通过系统调用fork产生的,fork出的子进程除了内核中的一些核心的数据结构和父进程不相同外,其余的内存映像都是和父进程共享的。只有当子进程需要去改写这些共享的内存时,操作系统才会为子进程分配一个新页面。这就是所谓的写时复制(Copy On Write)

通常子进程fork出后,会继续执行系统调用execexec将用一个新的可执行文件的内容替换当前进程的代码段数据段

forkexecLinux启动应用的标准做法,Init进程也是这样来启动各种服务的。

不过Zygote创建应用程序时却只是用了fork,没有调用exec

首先,Android应用中跑的的是虚拟机,虚拟机中Java代码的不同才造成了应用的区别,而对于基础的运行环境,要求却是一样的。

其次,Zygote在初始化时就会会创建虚拟机,同时把需要的系统类库和资源文件加载的内存中。而Zygotefork出子进程后,这个子进程也会得到一个已经加载好基础资源的虚拟机

这样,接下来只需要装载APK相关的资源就可以运行了,可以做到提升效率

Zygote进程的初始化

Zygote进程在Init进程中以service的方式启动的。我们来看下它在init.rc中的配置内容:

import /init.${ro.zygote}.rc
on late-init......# Now we can start zygote for devices with file based encryption# 这里触发启动 zygotetrigger zygote-start
on zygote-start && (*) //*用来省略一些属性值的判断start netdstart zygotestart zygote_secondary

Android 5.0开始,Android开始支持64位编译,Zygote本身也会有32位64位的区别,因此,通过ro.zygote属性来控制启动不同版本的Zygote进程。在9.0的源码中,存在4个相关文件:

init.zygote32.rc
init.zygote32_64.rc
init.zygote64.rc
init.zygote64_32.rc

挑个特殊的看下(init.zygote32_64.rc):

service zygote /system/bin/app_process32 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygoteclass main......
service zygote_secondary /system/bin/app_process64 -Xzygote /system/bin --zygote --socket-name=zygote_secondaryclass main......
  • 从文件内容中可以看到定义了两个Zygote服务:zygotezygote_secondary
  • 两个服务最大的区别是可执行文件不同:一个是app_process32、一个是app_process64
  • init.zygote64_32.rc文件就不贴出来了,其实就是把两个可执行文件交换一下
  • 对于init.zygote32.rcinit.zygote64.rc分别只有一个Zygote服务

从这里我们就可以知道,Android将会支持4中运行模式:

  • 纯32位模式:ro.zygote的值为zygote32
  • 32位模式为主,64位为辅:ro.zygote的值为zygote32_64
  • 纯64位模式:ro.zygote的值为zygote64
  • 64位模式为主,32位为辅:ro.zygote的值为zygote64_32

而对于可执行文件app_process,源文件路径在frameworks/base/cmds/app_process,不过在学习源码前,我们先来看下app_process用法

app_process的用法

上面我们知道Zygote的启动是通过app_process来实现的

app_process主要作用是解析启动参数,然后根据启动参数选择不同的启动模式

为了更好的理解app_process,我们先来看明白它是怎么使用的。不过关于app_process参数的使用说明,源码中却只有:

fprintf(stderr,"Usage: app_process [java-options] cmd-dir start-class-name [options]\n");

有些太简洁了哈。。。。

我们还是来看几个示例吧,以Zygote为例:

 /system/bin/app_process32 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote

再来一个有趣版本(下面这段指令的目的是:运行一个Application,包含main函数类的名称为Test):

app_process -Djava.class.path=/data/local/tmp/Test.dex /data/lee Test

结合源码中的Usage(纯净版)...和这两个例子,我们可以这么总结:

  • -Xzygote-Djava.class.p**:属于[java-options],这些参数会传给虚拟机,并且参数必须以-开头,一旦遇到非-或者--,表示[java-options]结束
  • /system/bin/data/lee:属于cmd-dir,程序的运行目录,随便指定即可,文件操作时会以此为当前路径,正经的大多运行在/system/bin
  • Test:属于start-class-name,声明入口类的名称,有包名的话需要加上包名
  • --zygote:属于[options],这些参数都以符号--开头。参数--zygote表示要启动Zygote进程

app_processmain()

有了上面app_process用法的铺垫,我们再来分析main函数就会更容易些,老样子,先看整体:

int main(int argc, char* const argv[])
{// 1. 创建 AppRuntime 对象// 2. 保存 Java-Option 参数// 3. 解析 Option 参数:--开头的那种// 4. 根据参数的解析结果,准备ZygoteInit或者RuntimeInit相关的参数// 5. 将本进程名称改为nice-name指定的字符串// 6. 根据参数的解析结果,启动对应的Java类
}

很清晰了然了哈,我们再看下具体的源码

创建AppRuntime对象

main()源码:

AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
  • AppRuntime是在app_process中定义的类,继承了系统的AndroidRuntime类。
  • AndroidRuntime类是底层很重要很重要的一个类,主要作用是创建和初始化虚拟机,等下详细分析
  • 整个main()的后续流程都是通过这个runtime来操作的

保存Java-Option参数

    ......for (i = 0; i < argc; i++) {// 省略know_command相关......if (argv[i][0] != '-') { //如果不是-开头的话,解析结束//按照app_process的参数规则:java-option后面是cmd-dir,不是以-开头的//所以,正常情况解析到java-option就会停止break;}if (argv[i][1] == '-' && argv[i][2] == 0) {//如果是--开头并且后面是空字符时,结束解析//不知道加这个的目的是干啥++i; // Skip --.break;}runtime.addOption(strdup(argv[i]));// 省略打印信息......}......

解析Option参数

Option参数在源码中称为runtime argument

    //需要解析出来的参数都在这里了bool zygote = false;bool startSystemServer = false;bool application = false;String8 niceName;String8 className;// 按照参数的规则,上面java-option解析完后,紧接着是cmd-dir,也就是parent dir// 对于cmd dir不需要做额外处理,直接跳过++i;  // Skip unused "parent dir" argument.while (i < argc) {const char* arg = argv[i++];if (strcmp(arg, "--zygote") == 0) {zygote = true;niceName = ZYGOTE_NICE_NAME;} else if (strcmp(arg, "--start-system-server") == 0) {startSystemServer = true;} else if (strcmp(arg, "--application") == 0) {application = true;} else if (strncmp(arg, "--nice-name=", 12) == 0) {niceName.setTo(arg + 12);} else if (strncmp(arg, "--", 2) != 0) {//如果不是--开头,就认为是class nameclassName.setTo(arg);break;} else {--i;break;}}

准备ZygoteInit或者RuntimeInit需要的参数

    //定义一个字符参数集合Vector<String8> args;if (!className.isEmpty()) {// We're not in zygote mode, the only argument we need to pass// to RuntimeInit is the application argument.// 非Zygote模式,没有删掉官方注释,开心不// The Remainder of args get passed to startup class main(). Make// copies of them before we overwrite them with the process name.args.add(application ? String8("application") : String8("tool"));runtime.setClassNameAndArgs(className, argc - i, argv + i);// 省略打印部分......} else {// We're in zygote mode.maybeCreateDalvikCache();if (startSystemServer) {args.add(String8("start-system-server"));}// 省略ABI属性值的获取......String8 abiFlag("--abi-list=");abiFlag.append(prop);args.add(abiFlag);// In zygote mode, pass all remaining arguments to the zygote// main() method.// Zygote模式,app_process的参数会被统一打包进参数集合中for (; i < argc; ++i) {args.add(String8(argv[i]));}}

将进程名称改为nice-name指定的字符串

    if (!niceName.isEmpty()) {// 这是AndroidRuntime提供的函数,设置进程名称runtime.setArgv0(niceName.string(), true /* setProcName */);}// 在AndroidRuntime.cpp中void AndroidRuntime::setArgv0(const char* argv0, bool setProcName) {if (setProcName) {int len = strlen(argv0);if (len < 15) {pthread_setname_np(pthread_self(), argv0);} else {pthread_setname_np(pthread_self(), argv0 + len - 15);}}memset(mArgBlockStart, 0, mArgBlockLength);strlcpy(mArgBlockStart, argv0, mArgBlockLength);}

启动对应的Java

    if (zygote) {// 如果是--zygote,则执行ZygoteInit类,启动Zygote进程runtime.start("com.android.internal.os.ZygoteInit", args, zygote);} else if (className) {// 如果是指定了class name,则通过RuntimeInit执行传进来的类runtime.start("com.android.internal.os.RuntimeInit", args, zygote);} else {fprintf(stderr, "Error: no class name or --zygote supplied.\n");app_usage();LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");}

app_processAndroid中的应用

通过上面的源码分析我们知道:app_process除了能启动Zygote进程,也可以使用它来执行某个系统的Java

Android中的常用工具am就是一个很好的例子:

am是一个发送Intent的工具,像am startam broadcast等。

但是,am实际上只是一个包含几行代码的脚本文件,它的功能都是通过调用app_process来完成的

#!/system/bin/sh
if [ "$1" != "instrument" ] ; thencmd activity "$@"
elsebase=/systemexport CLASSPATH=$base/framework/am.jarexec app_process $base/bin com.android.commands.am.Am "$@"
fi

有木有,有木有,Android还是蛮惊喜的,哈哈哈!

大家可以仔细阅读下Am.java这个类,很有特点,调用关系找起来是真滴费神,加油哟!

启动虚拟机-AndroidRuntime

在分析app_processmain()时我们知道AppRuntime继承的AndroidRuntime类。

  • AndroidRuntime类是一个很重要的类,它负责启动虚拟机以及Java线程
  • AndroidRuntime类在一个进程中只有一个实例对象,保存在全局变量gCurRuntime

构造函数

AndroidRuntime类构造函数如下:

AndroidRuntime::AndroidRuntime(char* argBlockStart, const size_t argBlockLength) :mExitWithoutCleanup(false),mArgBlockStart(argBlockStart),mArgBlockLength(argBlockLength)
{// 初始化skia图形系统SkGraphics::Init();// Pre-allocate enough space to hold a fair number of options.mOptions.setCapacity(20);// 只能被初始化一次assert(gCurRuntime == NULL);        // one per processgCurRuntime = this;
}

启动虚拟机

本章只是简单介绍下虚拟机的启动过程,具体的实现细节后面单独拉出一篇来学习

app_processmain()函数中,最后调用了runtime.start()来执行Java类,这个start()函数就是在AndroidRuntime中定义的。代码比较长,我们还是先看整体流程:

void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{// 1. 打印启动log// 2. 获取系统目录 // 3. 启动虚拟机// 4. 调用onVmCreated函数// 5. 注册系统的JNI函数// 6. 准备Java类的main函数的相关参数// 7. 调用ZygoteInit类的main方法}

打印启动Log

源码片段:

ALOGD(">>>>>> START %s uid %d <<<<<<\n",className != NULL ? className : "(unknown)", getuid());

我们再看下系统启动过程的Log片段:

00:00:21.226808  3244  3244 D AndroidRuntime: >>>>>> START com.android.internal.os.ZygoteInit uid 0 <<<<<<
  • 这段Log标志着Android系统的启动
  • 因为以后的应用进程都是从Zygote进程fork出来的,所以后面不会再执行start()函数了
  • 如果Android的系统log中反复出现这段内容,而输出ID都是Zygote,则说明系统可能出现问题,Zygote进程在不断地重启

获取系统目录

源码片段

    const char* rootDir = getenv("ANDROID_ROOT");if (rootDir == NULL) {rootDir = "/system";if (!hasDir("/system")) {LOG_FATAL("No root directory specified, and /android does not exist.");return;}setenv("ANDROID_ROOT", rootDir, 1);}
  • 系统目录从环境变量ANDROID_ROOT读取
  • 如果没有设置,则默认设置为/system
    • 系统目录是在Init进程中创建出来
  • 如果/system目录不存在,直接退出

启动虚拟机

源码片段

    JniInvocation jni_invocation;jni_invocation.Init(NULL);JNIEnv* env;if (startVm(&mJavaVM, &env, zygote) != 0) {return;}

通过startVm启动虚拟机,我们先往下看,后面会详细介绍。

调用onVmCreated函数

    onVmCreated(env);

onVmCreated函数是一个虚函数,对于app_process来说实际上调用的是AppRuntime的重载函数,下面是AppRuntime中的代码逻辑:

    virtual void onVmCreated(JNIEnv* env){if (mClassName.isEmpty()) {return; // Zygote. Nothing to do here.}//  省略一部分和类加载有关的很有意思的注释......char* slashClassName = toSlashClassName(mClassName.string());mClass = env->FindClass(slashClassName);if (mClass == NULL) {ALOGE("ERROR: could not find class '%s'\n", mClassName.string());}free(slashClassName);mClass = reinterpret_cast<jclass>(env->NewGlobalRef(mClass));}

AppRuntimeonVmCreated函数中:

  • 如果是Zygote进程,变量mClassName的值会为null,会立刻返回
  • 如果是一个普通Java类的调用,mClassName中会存放类的名称
  • toSlashClassName(mClassName.string())的作用是将类名转换为类的全限定名
    • 类名com.android.Zygote转化为全限定名就变为/com/android/Zygote

注册系统的JNI函数

源码片段

    /** Register android functions.*/if (startReg(env) < 0) {ALOGE("Unable to register all android natives\n");return;}

startReg通过调用register_jni_procs函数将全局数组gRegJNI中的JNI本地函数注册到虚拟机中:

/** Register android native functions with the VM.*/
/*static*/ int AndroidRuntime::startReg(JNIEnv* env)
{......// 这个函数会修改当前进程的全局变量gCreateThreadFn函数指针为 javaCreateThreadEtc()// 此之后那么新建的子线程便会// 先执行javaCreateThreadEtc() -> javaThreadShell() -> javaAttachThread() -> jvm->AttachCurrentThread() // 此时从普通C层线程成为JVM线程androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);......if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {env->PopLocalFrame(NULL);return -1;}......
}

而对于gRegJNI数组,格式为

static const RegJNIRec gRegJNI[] = {......REG_JNI(register_android_util_Log),......REG_JNI(register_android_os_Binder),......REG_JNI(register_android_graphics_Paint),......REG_JNI(register_android_app_Activity),......
};

省略了很多元素,该数组中每一个成员都代表一类文件的JNI映射,其中REG_JNI是一个宏定义,该宏的作用就是调用相应的JNI注册方法

准备Java类的main函数的相关参数

源码片段

    jclass stringClass;jobjectArray strArray;jstring classNameStr;stringClass = env->FindClass("java/lang/String");assert(stringClass != NULL);strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);assert(strArray != NULL);classNameStr = env->NewStringUTF(className);assert(classNameStr != NULL);env->SetObjectArrayElement(strArray, 0, classNameStr);for (size_t i = 0; i < options.size(); ++i) {jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());assert(optionsStr != NULL);env->SetObjectArrayElement(strArray, i + 1, optionsStr);}

上面是非常经典的在Native层创建Java层对象的操作:

  • 创建一个java.lang.String的数组对象
  • 并根据传入的参数对数组对象逐个元素进行赋值

调用ZygoteInit类的main方法

源码片段

    // 转化为全限定名char* slashClassName = toSlashClassName(className != NULL ? className : "");// 获取类的class对象jclass startClass = env->FindClass(slashClassName);if (startClass == NULL) {ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);/* keep going */} else {jmethodID startMeth = env->GetStaticMethodID(startClass, "main","([Ljava/lang/String;)V");if (startMeth == NULL) {ALOGE("JavaVM unable to find main() in '%s'\n", className);/* keep going */} else {// 执行main函数env->CallStaticVoidMethod(startClass, startMeth, strArray);}// 此处main函数执行完毕,执行一些结束操作free(slashClassName);ALOGD("Shutting down VM\n");if (mJavaVM->DetachCurrentThread() != JNI_OK)ALOGW("Warning: unable to detach main thread\n");if (mJavaVM->DestroyJavaVM() != 0)ALOGW("Warning: VM did not shut down cleanly\n");

调用main方法前:

  • 先通过GetStaticMethodID获得main方法的ID
  • 然后使用CallStaticVoidMethod来调用Java层的函数

到这里,Zygote进程的初始化过程将转到Java层了,还记得app_processmain()函数中的启动逻辑么?

回顾一下哈:

    if (zygote) {// 如果是--zygote,则执行ZygoteInit类,启动Zygote进程runtime.start("com.android.internal.os.ZygoteInit", args, zygote);} else if (className) {// 如果是指定了class name,则通过RuntimeInit执行传进来的类runtime.start("com.android.internal.os.RuntimeInit", args, zygote);}

接下来,我们就来看看ZygoteInit.java类吧

初始化工作-ZygoteInit

ZygoteInit类负责Zygote进程Java层的初始化工作。
入口方法main()的具体代码如下(注释比较详细):

    public static void main(String argv[]) {// 创建Zygote服务管理类,用来注册socket监听ZygoteServer zygoteServer = new ZygoteServer();// Mark zygote start. This ensures that thread creation will throw// an error.// 调用此方法后,虚拟机会拒绝线程的创建,如果此时创建会产生错误ZygoteHooks.startZygoteNoThreadCreation();// Zygote goes into its own process group.Os.setpgid(0, 0);//......final Runnable caller;try {......省略打印相关// 启动DDMS虚拟机监控调试服务RuntimeInit.enableDdms();// 参数解析boolean startSystemServer = false;String socketName = "zygote";String abiList = null;boolean enableLazyPreload = false;for (int i = 1; i < argv.length; i++) {if ("start-system-server".equals(argv[i])) {// 开启系统服务startSystemServer = true;} else if ("--enable-lazy-preload".equals(argv[i])) {// 启动延迟加载enableLazyPreload = true;} else if (argv[i].startsWith(ABI_LIST_ARG)) {// abi类型,一个CPU对应一个abiabiList = argv[i].substring(ABI_LIST_ARG.length());} else if (argv[i].startsWith(SOCKET_NAME_ARG)) {// 解析socket namesocketName = argv[i].substring(SOCKET_NAME_ARG.length());} else {// 未知参数会抛出异常throw new RuntimeException("Unknown command line argument: " + argv[i]);}}// 没有指定ABI参数会抛出异常if (abiList == null) {throw new RuntimeException("No ABI list supplied.");}// 注册Zygote的socket监听端口,用来接收启动应用程序的消息zygoteServer.registerServerSocketFromEnv(socketName);// In some configurations, we avoid preloading resources and classes eagerly.// In such cases, we will preload things prior to our first fork.if (!enableLazyPreload) {// 没有启用延迟加载的情况.....省略一些log打印// 执行预加载操作,包括系统预加载类、Framework资源和openGL资源preload(bootTimingsTraceLog);.....省略一些log打印} else {// 延迟加载的情况,reset 线程的优先级Zygote.resetNicePriority();}// Do an initial gc to clean up after startup......省略log打印// 官方注释:运行几个指定的GC,尝试清除几代的软引用和可达的对象,还有别的垃圾// 此方法只在fork()前是好使的gcAndFinalize();......省略log打印// 一些与安全相关的初始化操作Zygote.nativeSecurityInit();// Zygote process unmounts root storage spaces.// 目的是将整体的存储目录/storage卸载,取而代之的是挂载临时目录// 这个动作和Android 的沙箱(隔离存储)有关Zygote.nativeUnmountStorageOnInit();// 呼应前面的startZygoteNoThreadCreation()方法// 通知虚拟机,现在可以在zygote进程中创建线程了ZygoteHooks.stopZygoteNoThreadCreation();if (startSystemServer) {// fork SystemServerRunnable r = forkSystemServer(abiList, socketName, zygoteServer);// {@code r == null} in the parent (zygote) process, and {@code r != null} in the// child (system_server) process.// 如果 r 为空,说明是 zygote 进程,不做任何处理,继续执行if (r != null) {// r 不为空,说明是孵化的子进程 systemserver,启动后直接返回r.run();return;}}Log.i(TAG, "Accepting command socket connections");// The select loop returns early in the child process after a fork and// loops forever in the zygote.// 这部分是在zygote进程中执行// 此处进入一个无限循环中,处理zygote socket接收到数据caller = zygoteServer.runSelectLoop(abiList);} catch (Throwable ex) {Log.e(TAG, "System zygote died with exception", ex);throw ex;} finally {// 请留意这部分,主要是给子进程用的// 子进程中是不需要在有zygote服务的// 所以这里的关闭理论上是为了在子进程中关闭无用的zygote服务zygoteServer.closeServerSocket();}// We're in the child process and have exited the select loop. Proceed to execute the// command.// 除systemserver外的进程,都是在这里真正的开始main函数的// 有没有感到疑惑,runSelectLoop是一个无线循环,要怎么才能周到这里呢?// 走到这里为什么一定是子进程呢?if (caller != null) {caller.run();}}

看完这个main函数大家一定有很多疑问,由于有了子进程,代码的处理逻辑变得有些复杂

现将本人学习过程中的疑惑点和理解与大家分享下

知识点:fork的结果

关于fork,我们需要记住:

  • fork会产生和当前进程完全一样的新进程
  • fork函数调用后
    • 新的进程将启动
    • 并和当前进程一起从fork函数返回
  • 关于fork的返回值
    • 新的进程返回0
    • 当前进程返回新进程的pid

疑问2:systemserver的启动

            if (startSystemServer) {// fork SystemServerRunnable r = forkSystemServer(abiList, socketName, zygoteServer);// {@code r == null} in the parent (zygote) process, and {@code r != null} in the// child (system_server) process.// 如果 r 为空,说明是 zygote 进程,不做任何处理,继续执行if (r != null) {// r 不为空,说明是孵化的子进程 systemserver,启动后直接返回r.run();return;}}

这段代码通过forkSystemServer来完成systemserverfork,而forkSystemServer逻辑是:

  • 执行pid = Zygote.forkSystemServer

    • 请注意,执行完后会产生两个进程:zygotesystemserver
    • 并且,两个进程同时开始执行,但是进程收到的pid是不一样的
    • 可参照前面的知识点
  • pid判断:

        // 对于systemserver进程,pid是0if (pid == 0) {if (hasSecondZygote(abiList)) {waitForSecondaryZygote(socketName);}zygoteServer.closeServerSocket();// handleSystemServerProcess这个函数会:// 通过反射找到SystemServer的main函数// 并包装到Runnable的run函数中return handleSystemServerProcess(parsedArgs);}// pid不等于0,父进程,也就是zygote,返回nullreturn null;
    

到这里,大家应该明白了下面这段代码的处理意义了吧:

        if (r != null) {r.run();return;}

疑问3:main函数最后的caller.run()怎么执行到的

应用程序的启动应该就是通过这个caller.run(),弄明白它问题就不大了

    public static void main(String argv[]) {......try{......caller = zygoteServer.runSelectLoop(abiList);}finally {zygoteServer.closeServerSocket();}......if (caller != null) {caller.run();}}

官方注释已经说了caller.run();才是子进程真正运行的地方,但是zygoteServer.runSelectLoop不是一个无线循环么,咋处理的呢?

我们来看下runSelectLoop函数:

    Runnable runSelectLoop(String abiList) {......while (true) {......try {ZygoteConnection connection = peers.get(i);final Runnable command = connection.processOneCommand(this);if (mIsForkChild) {// We're in the child. We should always have a command to run at this// stage if processOneCommand hasn't called "exec".if (command == null) {throw new IllegalStateException("command == null");}return command;} else {// We're in the server - we should never have any commands to run.......}}......}

两个重要的点:

  • 一个mIsForkChild属性

    • mIsForkChild属性为true时,会退出循环
    • 并返回一个Runnable对象
  • 一个processOneCommand函数

对于processOneCommand函数,它也有一个很重要的操作:

Runnable processOneCommand(ZygoteServer zygoteServer) {......// fork 子进程pid = Zygote.forkAndSpecialize(......;if (pid == 0) {// in child// 设置mIsForkChild为truezygoteServer.setForkChild();zygoteServer.closeServerSocket();......// handleChildProc这个函数会:// 通过反射找到启动类的main函数// 并包装到Runnable的run函数中return handleChildProc(......);} else {......return null;}
}

zygote的无限循环里,当监听到数据后:

  • 通过processOneCommand函数fork出了子进程,此时:

    • 存在两个进程正在运行:zygote子进程
  • 仍然还是根据pid来判断哪个是子进程
    • 对于子进程,设置mIsForkChild属性为true
    • 并且子进程将打包好的Runnable对象返回
    • 对于zygote,返回null

这样,子进程中的runSelectLoop()便会停止循环,执行到caller.run()

书中Android 5.0的源码是通过抛出异常的方式来执行caller.run(),也很有趣,哈哈哈

有了这些铺垫,相信下面zygote 启动应用程序这部分应该就不难了吧,哈哈哈

Zygote启动应用程序

我们已经知道,Zygote进程初始化完毕后,会通过runSelectLoop()化身为守护进程来执行启动应用程序的任务,先看下整个过程的时序图,再逐一详解吧

注册Zygote的socket

ZygoteInit类的main()函数

  • 首先调用了ZygoteServerregisterServerSocketFromEnv来创建一个本地的socket
  • 接着调用runSelectLoop来进入等来socket连接的循环中

registerServerSocketFromEnv()函数如下:

    void registerServerSocketFromEnv(String socketName) {if (mServerSocket == null) {int fileDesc;// 拼接后的fullSocketName为ANDROID_SOCKET_zygotefinal String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;try {String env = System.getenv(fullSocketName);fileDesc = Integer.parseInt(env);} catch (RuntimeException ex) {throw new RuntimeException(fullSocketName + " unset or invalid", ex);}try {FileDescriptor fd = new FileDescriptor();fd.setInt$(fileDesc);mServerSocket = new LocalServerSocket(fd);mCloseSocketFd = true;} catch (IOException ex) {throw new RuntimeException("Error binding to local socket '" + fileDesc + "'", ex);}}}

函数通过环境变量ANDROID_SOCKET_zygote来获取socketfileDesc

  • 这个fileDesc(文件描述符)怎么来的呢?我们再来回顾下init.rc中的内容:

    service zygote /system/bin/app_process32 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
    ......
    socket zygote stream 660 root system
    ......
    
  • Zygote服务启动时指定了一个option用来进行socket的创建
    • Init进程会根据这条选项来创建一个用于本地通信的socket
    • 并把这个socketfileDesc放到环境变量ANDROID_SOCKET_zygote
    • 环境变量字符串后面部分的zygote就是option中的名称
  • 得到fileDesc后,通过LocalServerSocket()创建一个本地的socket服务,并保存到全局变量mServerSocket

请求启动应用

Android启动一个新的进程是在ActivityManagerService中完成的,可能会有很多原因导致系统启动一个新的进程,最终在ActivityManagerService中都是通过调用方法startProcess()来实现:

final String entryPoint = "android.app.ActivityThread";
private ProcessStartResult startProcess(String hostingType, String entryPoint,ProcessRecord app, int uid, int[] gids, int runtimeFlags, int mountExternal,String seInfo, String requiredAbi, String instructionSet, String invokeWith,long startTime) {......final ProcessStartResult startResult;if (hostingType.equals("webview_service")) {......} else {startResult = Process.start(entryPoint,app.processName, uid, uid, gids, runtimeFlags, mountExternal,app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,app.info.dataDir, invokeWith,new String[] {PROC_START_SEQ_IDENT + app.startSeq});}......
}

可以看到,关键的函数为Process.start()。而Process.start()的第一个参数entryPoint就是启动后执行的Java类:android.app.ActivityThread

我们这里主要是想看下启动应用时发送给Zygote的参数有哪些?而关于应用的启动流程,后面再进行展开哈

我们跟踪下Process.start():

// class:Process
public static final ProcessStartResult start(final String processClass ......) {return zygoteProcess.start(processClass......);
}
// class:ZygoteProcess
public final Process.ProcessStartResult start(final String processClass ......) {return startViaZygote(processClass ......);
}
private Process.ProcessStartResult startViaZygote(final String processClass ......){ArrayList<String> argsForZygote = new ArrayList<String>();// --runtime-args, --setuid=, --setgid=,// and --setgroups= must go firstargsForZygote.add("--runtime-args");......argsForZygote.add(processClass);return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
}

参数有点多,代码部分意思一下哈,嘿嘿

  • start()方法调用startViaZygote()方法来启动应用
  • startViaZygote()首先将应用进程的启动参数保存到argsForZygote集合中
  • 然后调用zygoteSendArgsAndGetResult方法将应用程序进程的启动参数发送的Zygote进程
  • zygoteSendArgsAndGetResult()方法中会调用openZygoteSocketIfNeeded()方法用来创建用于通信的socket,整理后的代码是这样的:
    public static final String ZYGOTE_SOCKET = "zygote";
    public static final LocalSocketAddress address = new LocalSocketAddress(ZYGOTE_SOCKET, LocalSocketAddress.Namespace.RESERVED);
    final LocalSocket zygoteSocket = new LocalSocket();
    zygoteSocket.connect(address);
    
    • 对于Local Socket使用字符串作为地址就可以通信,灰常方便啊
  • socket连接建立完后,zygoteSendArgsAndGetResult利用这个socket进行启动参数的写入:
    writer.write(Integer.toString(args.size()));
    writer.newLine();
    for (int i = 0; i < sz; i++) {String arg = args.get(i);writer.write(arg);writer.newLine();
    }
    writer.flush();
    

到这里,请求应用启动的参数就发送给Zygote进程了,我们接下来看下Zygote的处理过程

处理启动应用的请求

Zygote进程通过runSelectLoop()来监听和处理启动应用的请求,我们看下runSelectLoop(),注释比较详细哈:

    Runnable runSelectLoop(String abiList) {ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();// 将socket zygote的文件描述符添加到fds集合中// 此处需要留意,mServerSocket是集合中第一个元素fds.add(mServerSocket.getFileDescriptor());peers.add(null);while (true) {// 动态生成pollFds数组,作为poll的监听参数StructPollfd[] pollFds = new StructPollfd[fds.size()];for (int i = 0; i < pollFds.length; ++i) {pollFds[i] = new StructPollfd();pollFds[i].fd = fds.get(i);pollFds[i].events = (short) POLLIN;}try {// 通过poll对pollFds数组中的文件进行事件监听// -1 表示阻塞直到有数据进来Os.poll(pollFds, -1);} catch (ErrnoException ex) {throw new RuntimeException("poll failed", ex);}// 当有数据到来时,才会执行到这里for (int i = pollFds.length - 1; i >= 0; --i) {// 循环遍历,查找接收到数据的文件if ((pollFds[i].revents & POLLIN) == 0) {continue;}if (i == 0) {// i=0 说明是 mServerSocket// 意味着客户端发起了连接请求// 按照LocalServerSocket的流程,需要创建一个LocalSocket来用于客户端的通信// acceptCommandPeer()就是按照这个流程来创建ZygoteConnection对象// 其实真正的通信对象还是LocalSocket// 然后将通信对象的FD添加到fds集合中// 大家没必要纠结这部分的处理方式,当做是LocalSocket的标准用法就可以啦ZygoteConnection newPeer = acceptCommandPeer(abiList);peers.add(newPeer);fds.add(newPeer.getFileDesciptor());} else {try {// 这里才是真正的处理部分ZygoteConnection connection = peers.get(i);// 执行对应connection的processOneCommand函数// 函数中会读取解析参数并进行子进程fork的相关操作final Runnable command = connection.processOneCommand(this);if (mIsForkChild) {......// 子进程,退出selectLoop循环return command;} else {......// zygote进程,关闭移除connectionif (connection.isClosedByPeer()) {connection.closeSocket();peers.remove(i);fds.remove(i);}}} catch (Exception e) {if (!mIsForkChild) {// 异常后的清除操作......ZygoteConnection conn = peers.remove(i);conn.closeSocket();fds.remove(i);} else {......}} finally {......mIsForkChild = false;}}}}}

从上面的代码中我们知道,对于消息真正的处理是在ZygoteConnection.processOneCommand函数中,我们来重点看下:

processOneCommand函数

函数分为了三部分:

  • 解析启动参数
  • 子进程fork
  • 子进程初始化

我们来逐一看下

解析启动参数

参数解析的流程是

  • 先通过readArgumentList方法从socket连接中读取多个参数行

    • 参数行的样式是--setuid=1
    • 行与行之间以\r\n或者\r\n分割
  • 读完后再调用Arguments类的parseArgs方法将数据解析为参数列表
    • 具体的参数意义,大家可以参考Arguments类的注释

解析完参数后,processOneCommand还会对解析出的参数进行检查和设置:

        ...... 省略一些参数属性判断// 检查客户端手有权限指定进程的用户ID、组ID// 如果是 root 进程,可以任意指定// 如果是 sys 进程,需要在ro.factorytest值 > 0时可以指定applyUidSecurityPolicy(parsedArgs, peer);// 判断是否具有invoke-with的执行权限applyInvokeWithSecurityPolicy(parsedArgs, peer);// 如果ro.debuggable是1的话,启动JDWP协议applyDebuggerSystemProperty(parsedArgs);// 处理invoke-with属性applyInvokeWithSystemProperty(parsedArgs);

细节就不展开了哈

fork子进程

参数检查无误后,processOneCommand函数会调用forkAndSpecialize方法来fork子进程:

    pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,parsedArgs.runtimeFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.startChildZygote,parsedArgs.instructionSet, parsedArgs.appDataDir);

forkAndSpecialize最终是通过native层的ForkAndSpecializeCommon来完成fork的,我们这里只是简单介绍下函数的主要工作哈:

  • 通过fork创建子进程
  • 在子进程中挂载emulate storage
  • 在子进程中设置用户ID、组ID、和进程所属的组
  • 在子进程中通过setrlimit系统调用设置进程的系统资源限制
  • 在子进程中通过capset系统调用设置进程的权限
  • 在子进程中通过selinux_android_setcontext设置应用进程的安全上下文

native层返回Java层

  • 如果pid==0,表示处于子进程中,执行handleChildProc函数
  • 如果pid!=0,表示在Zygote进程中,执行handleParentProc函数,并返回null

这部分代码在前面的疑问中其实已经展示过了哈:

    if (pid == 0) {// in childzygoteServer.setForkChild();zygoteServer.closeServerSocket();IoUtils.closeQuietly(serverPipeFd);serverPipeFd = null;return handleChildProc(parsedArgs, descriptors, childPipeFd,parsedArgs.startChildZygote);} else {// In the parent. A pid < 0 indicates a failure and will be handled in// handleParentProc.IoUtils.closeQuietly(childPipeFd);childPipeFd = null;handleParentProc(pid, descriptors, serverPipeFd);return null;}

子进程的初始化

Zygote进程fork出子进程后,调用handleChildProc方法来完成子进程的初始化工作。

  • handleChildProc方法首先关闭了监听用的socket和从Zygote中继承的文件描述符

        closeSocket();......for (FileDescriptor fd: descriptors) {IoUtils.closeQuietly(fd);}......
    
  • 接下来,根据启动参数是否有--runtime-init以及--invoke-with来判断如何初始化
        if (parsedArgs.invokeWith != null) {WrapperInit.execApplication(......);......} else {......return ZygoteInit.zygoteInit(......);......}
    
    • 启动apk应用都会带有--runtime-init参数,但是--invoke-with通常为null
    • --invoke-with不为null将会通过exec的方式启动app_process来执行Java
    • 正常情况下会调用ZygoteInit.zygoteInit方法
  • ZygoteInit.zygoteInit方法又调用了三个方法:RuntimeInit.commonInit()ZygoteInit.nativeZygoteInit()RuntimeInit.applicationInit(),最后return一个Runnable的对象给调用者
    • commonInit():一些通用配置的简单初始化:

      • 设置KillApplicationHandler为默认的UncaughtExceptionHandler
      • 设置时区
      • 设置http.agent属性,用于HttpURLConnection
      • 重置AndroidLog系统
      • 通过NetworkManagementSocketTagger设置sockettag,用于流量统计
    • nativeZygoteInit()
      • 这是一个本地方法,但是很重要,因为它做了这么一件事
      static void com_android_internal_os_ZygoteInit_nativeZygoteInit(JNIEnv* env, jobject clazz)
      {gCurRuntime->onZygoteInit();
      }
      
      • gCurRuntime?有木有很眼熟,它其实就是AppRuntime的实例,大家可以往上翻看AndroidRuntime初始化的部分
      • 然后调用了onZygoteInit,我们看下AppRuntime的函数实现:
      virtual void onZygoteInit()
      {sp<ProcessState> proc = ProcessState::self();proc->startThreadPool();
      }
      
      • 这段代码是不是也眼熟,哈哈哈,在Binder章节我们已经学过了
      • 主要是初始化Binder的使用环境,这样,应用进程就可以使用Binder
    • applicationInit():前两步初始化完成后,会执行并返回这个函数
      • 设置虚拟机的HeapUtilization0.75f
      • 设置当前的SDKVersion
      • 除了上面的两个,最重要的是调用了findStaticMain()函数来查找Java类的main方法,并包装成Runnable的形式
      protected static Runnable findStaticMain(......) {Class<?> cl = Class.forName(className, true, classLoader);Method m = cl.getMethod("main", new Class[] { String[].class });int modifiers = m.getModifiers();return new MethodAndArgsCaller(m, argv);
      }
      static class MethodAndArgsCaller implements Runnable {private final Method mMethod;private final String[] mArgs;......public void run() {......mMethod.invoke(null, new Object[] { mArgs });......}
      }
      
      • 到这里,函数的调用就结束了
      • 这都是一些Java反射的相关知识啦,不清楚的话要学习哟

processOneCommand函数的终点

processOneCommand函数执行完会出现两种情况:

  • 对于子进程,返回都打包好的MethodAndArgsCaller实例
  • 对于Zygote,继续循环

此时,子进程的runSelectLoop会退出循环,并返回MethodAndArgsCaller实例,最终执行到:

    caller = zygoteServer.runSelectLoop(abiList);if (caller != null) {caller.run();}

预加载系统类和资源

为了加快应用程序的启动,Android会把系统常用的Java类和一部分Framework的资源放在Zygote进程中进行预加载。这些预加载的类和资源在所有经zygote进程fork出的子进程中是共享的。

如图所示:

预加载操作是在ZygoteInit.main()函数中,通过preload()函数来完成的,代码如下:

    static void preload(TimingsTraceLog bootTimingsTraceLog) {......// 设置软引用保护,避免被GC回收掉beginIcuCachePinning();......// 加载系统类preloadClasses();......// 加载系统资源preloadResources();......// nativePreloadAppProcessHALs();......// 加载OpenGL资源preloadOpenGL();// 加载一些共享so库,其实就三个:android、compiler_rt、jnigraphicspreloadSharedLibraries();// 加载字体资源preloadTextResources();......// 加载webview相关资源WebViewFactory.prepareWebViewInZygote();// 取消软引用保护endIcuCachePinning();// 初始化JCA安全相关的参数warmUpJcaProviders();Log.d(TAG, "end preload");sPreloadComplete = true;}

注释比较详细,我们重点看下Java类、系统资源和共享库的加载

预加载Java类

Android将所有需要预加载Java类放在文本文件preload-classes中。

9.0源码中的文件存放路径是在frameworks/base/config/preloaded-classes,内容格式如下:

#
# This file has been derived for mainline phone (and tablet) usage.
#
......
android.animation.Animator
android.app.ActivityThread
android.app.FragmentManager
......

preloadClasses()函数的作用就是解析这个文件,并加载里面声明的的所有类,函数如下:

    private static final String PRELOADED_CLASSES = "/system/etc/preloaded-classes";private static void preloadClasses() {......InputStream is = new FileInputStream(PRELOADED_CLASSES);......BufferedReader br = new BufferedReader(new InputStreamReader(is), 256);int count = 0;String line;while ((line = br.readLine()) != null) {line = line.trim();if (line.startsWith("#") || line.equals("")) {continue;}......Class.forName(line, true, null);......}}

到此,对于这些类,虚拟机也仅仅是完成了装载而已。只有在首次主动使用时,才会真正的进行类的初始化

preloaded-classes声明了这么多类,它是怎么生成的呢?

生成流程是:

  • 首先有一个frameworks/base/tools/preload/WritePreloadedClassFile.java类,它对应的modulepreload
  • 其次还有有一个frameworks/base/tools/preload/20100223.compiled文件
  • 然后通过mmm frameworks/base/tools/preload可以编译出preload.jar
  • 最后通过java -Xss4M -cp out/host/linux-x86/framework/preload.jar WritePreloadedClassFile frameworks/base/tools/preload/20100223.compiled来解析,解析的结果是得到一个包含所有Java类的列表,列表的内容包括:
    • root对象
    • 被几个进程装载
    • 装载的平均时长
  • 最后文件生成路径在/frameworks/base/preloaded-classes

20100223.compiled是源码自带的,那么问题就来了,.compiled这种类型的文件是怎么生成的呢?

  • 首先,有一个frameworks/base/tools/preload/Compile.java类,也是在preload中,它是用来分析logpreload信息的
  • 然后我们可以通过java -Xss512M -cp out/host/linux-x86/framework/preload.jar Compile logcat.txt 20201111.compiled来生成分析结果,也就是.compiled文件

预加载系统资源

预加载系统资源的函数是preloadResources(),主要代码如下:

    private static void preloadResources() {......mResources = Resources.getSystem();mResources.startPreloading();if (PRELOAD_RESOURCES) {......TypedArray ar = mResources.obtainTypedArray(com.android.internal.R.array.preloaded_drawables);int N = preloadDrawables(ar);ar.recycle();......ar = mResources.obtainTypedArray(com.android.internal.R.array.preloaded_color_state_lists);N = preloadColorStateLists(ar);ar.recycle();......}mResources.finishPreloading();......}

预加载Framework的资源核心的两个函数是:

  • preloadDrawables:加载drawable资源
  • preloadColorStateLists:加载color状态定义资源

具体资源加载过程后面再详细学习,我们先看下预加载资源的定义,在源码目录frameworks/base/core/res/res/values/arrays.xml

    <array name="preloaded_drawables"><item>@drawable/action_bar_item_background_material</item><item>@drawable/activated_background_material</item></array><array name="preloaded_color_state_lists"><item>@color/primary_text_dark</item><item>@color/primary_text_dark_disable_only</item></array>

预加载共享库

预加载共享库的函数是preloadSharedLibraries(),代码如下:

    private static void preloadSharedLibraries() {Log.i(TAG, "Preloading shared libraries...");System.loadLibrary("android");System.loadLibrary("compiler_rt");System.loadLibrary("jnigraphics");}

一共是是三个so文件:

  • libandroid.so
  • libcompiler_rt.so
  • libjnigraphics.so

结语

Zygote进程的学习到这里就结束啦,又进一步加深了对Android的认知,哈哈哈哈!

为啥这里叒叒叒叒有结语呢?哈哈哈哈,好奇宝宝可以看这里!

一个无关紧要的私人计划

知识终归是知识,技巧还是有待磨练滴!
下一篇开始学习Android 的资源管理,加油~~

深入Android系统(七)Zygote进程相关推荐

  1. Android系统中的进程管理:内存的回收

    本文是Android系统进程管理的第三篇文章.进程管理的前面两篇文章,请参见这里: Android系统中的进程管理:进程的创建 Android系统中的进程管理:进程的优先级 本文适合Android平台 ...

  2. Android系统中的进程管理:进程的创建

    对于操作系统来说,进程管理是其最重要的职责之一. 考虑到这部分的内容较多,因此会拆分成几篇文章来讲解. 本文是进程管理系统文章的第一篇,会讲解Android系统中的进程创建. 本文适合Android平 ...

  3. Android系统中的进程管理:进程的优先级

    本文是Android进程管理系列文章的第二篇,会讲解进程管理中的优先级管理. 进程管理的第一篇文章:<进程的创建>请跳转至这里. 本文适合Android平台的应用程序开发者,也适合对于An ...

  4. Android 9 (P) Zygote进程启动源码分析指南二

         Android 9 Zygote进程启动源码分析指南二 Android 9 (P) 系统启动及进程创建源码分析目录: Android 9 (P)之init进程启动源码分析指南之一 Andro ...

  5. Android系统在新进程中启动自定义服务过程(startService)的原理分析 (下)

    Step 10. ActivityManagerService.attachApplicationLocked 这个函数定义在frameworks/base/services/java/com/and ...

  6. 根据用户查进程_【磨叽教程】Android进阶教程之在Android系统下各进程之间的优先级关系...

    导读:本文大约2000字,预计阅读时间3分钟.本文纯属技术文,无推广. 正文     首先应用进程的生命周期并不由应用本身直接控制,而是由系统综合多种因素来确定的.Android系统有自己的一套标准, ...

  7. 获取android com包名,Android系统中获取进程(和顶端包名)

    概要: android L前我们可以使用 getRunningTasks(int maxNum) maxNum int: The maxNumnumber of entries to return i ...

  8. Android系统级开发进程清理功能的一些记录和发现(一)

    Android系统级开发清理功能的一些记录和发现 客户需求做一键清理所有后台进程的功能,在查阅网上相关博客以及自己研究以后找到以下几种方法: killBackgroundProcesses() 这种方 ...

  9. Android系统级开发进程清理功能的一些记录和发现

    Android系统级开发清理功能的一些记录和发现 客户需求做一键清理所有后台进程的功能,在查阅网上相关博客以及自己研究以后找到以下几种方法: killBackgroundProcesses() 这种方 ...

  10. Android系统级开发进程清理功能的一些记录和发现(二)

    基于Android 7.0的近期任务列表进程清理实现 最近有小伙伴反应,上篇博客中提到的直接修改近期任务列表的方法在7.0的系统中无法使用,因为找不到onTaskViewDismissed()这个方法 ...

最新文章

  1. java9String类简单了解
  2. CentOS安装rpm包时遇到Header V3 DSA signature: NOKEY时解决办法
  3. ping不通win7、8解决方法以及nc后门的制作
  4. sqlserver查看被锁表、解锁
  5. Nginx(七):nginx原理解析
  6. 提升 JavaScript 技能的5个小技巧,新手小白值得学习!
  7. 关于二分限制最短路的题的总结
  8. UI——PS色彩搭配
  9. 简单6步,手把手搭建MinDoc文档库
  10. 米家蓝牙温湿度计2 换用 LIR2032 充电电池的问题
  11. 计算机二级excel高级筛选,Excel 如何按双条件执行高级筛选?
  12. python excel 单元格换行_数据标准化 使用Python脚本处理excel单元格换行符
  13. Java安全开发注意事项
  14. 看了他家的红木装修,彻底被圈粉了,庄重典雅又复古舒适
  15. 天嵌IMX6核心板竞品分析(启杨IMX6)
  16. amazon - amzreport 之 FBA Inventory Reports
  17. Dependency check配置Mysql数据库存储nvd数据
  18. 前端实现网络小说阅读器
  19. 那些年,兵败高考的大佬们
  20. WOL 微星 X570A-PRO 远程唤醒及远程桌面

热门文章

  1. 纸屑大小的芯片,也能当成超算用?MIT用「银-硅-铜合金」模拟突触,造出记忆力超强的芯片...
  2. input( ) 输入函数和注释
  3. [floyed][叉积][距离公式](JZOJ)泽泽在巴西
  4. 铸铁的弹性模量和泊松比_[转载]常用材料弹性模量及泊松比
  5. 你是我在考场门口一直张望的女生
  6. 错题更正~(摘抄为主
  7. AI(角度渐变icon绘制步骤)
  8. go的time.Time格式相关转换
  9. 建模方法(六)-爬山算法
  10. 华为手机 roomba_如何设置Wi-Fi连接的Roomba