1. 前面Android(java)学习笔记159提到Dalvik虚拟机启动初始化过程,就下来就是启动zygote进程:

zygote进程是所有APK应用进程的父进程:每当执行一个Android应用程序,Zygote就会孵化一个子线程去执行该应用程序(系统内部执行dvz指令完成的)

 特别注意:系统提供了一个app_process进程,它会自动启动ZygoteInit.java和SystemServer.java这两个类,app_process进程本质上是使用dalvikvm启动ZygoteInit.java。并且启动后加载Framework中大部分类和资源。

2. zygote进程启动流程:

(1)在init.rc中配置zygote启动参数:

init.rc存在于设备的根目录下,读者可以使用adb pull /init.rc ~/Desktop命令取出该文件,文件中和zygote相关的配置信息如下:

  1. service zygote /system/bin/app_process -Xzygote 
    /system/bin --zygote --start-system-server  
  2.     socket zygote stream 666  
  3.     onrestart write /sys/android_power/request_state wake  
  4.     onrestart write /sys/power/state on  
  5.     onrestart restart media  
  6.     onrestart restart netd  

首先第一行中使用service指令告诉操作系统将zygote程序加入到系统服务中,service的语法如下:

  1. service service_name 可执行程序的路径 可执行程序自身所需的参数列表 

service service_name 可执行程序的路径 可执行程序自身所需的参数列表

此处的服务被定义为zygote,理论上讲该服务的名称可以是任意的。可执行程序的路径正是/system/bin/app_process,也就是前面所讲的app_process,参数一共包含四个,分别如下:

-Xzygote,该参数将作为虚拟机启动时所需要的参数,是在AndroidRuntime.cpp类的startVm()函数中调用JNI_CreateJavaVM()时被使用的。

/system/bin,代表虚拟机程序所在目录,因为app_process完全可以不和虚拟机在同一个目录,而在app_process内部的AndroidRuntime类内部需要知道虚拟机所在的目录。

--zygote,指明以ZygoteInit类作为虚拟机执行的入口,如果没有--zygote参数,则需要明确指定需要执行的类名。

--start-system-server,仅在指定--zygote参数时才有效,意思是告知ZygoteInit启动完毕后孵化出第一个进程SystemServer。

接下来的配置命令socket用于指定该服务所使用到的socket,后面的参数依次是名称、类型、端口地址。

onrestart命令指定该服务重启的条件,即当满足这些条件后,zygote服务就需要重启,这些条件一般是一些系统异常条件。

(2)启动Socket服务端口

当zygote服务从app_process开始启动后,会启动一个Dalvik虚拟机,而虚拟机执行的第一个Java类就是ZygoteInit.java,因此接下来的过程就从ZygoteInit类的main()函数开始说起。main()函数中做的第一个重要工作就是启动一个Socket服务端口,该Socket端口用于接收启动新进程的命令。

(3)加载preload-classes

在ZygoteInit类的main()函数中,创建完Socket服务端后还不能立即孵化新的进程,因为这个"卵"中还没有必须"核酸",这个"核酸"就是指预装的Framework大部分类及资源。预装的类列表是在framework.jar中的一个文本文件列表,名称为preload-classes,该列表的原始定义在frameworks/base/preload-classes文本文件中,而该文件又是通过frameworks/base/tools/preload/

(4)加载preload-resources

preload-resources是在frameworks/base/core/res/res/values/arrays.xml中被定义的,包含两类资源,一类是drawable资源,另一类是color资源,如以下代码所示:

  1. <array name="preloaded_drawables"> 
  2.        <item>@drawable/sym_def_app_icon</item> 
  3. ... ...  
  4.    </array> 
  5.  
  6.    <array name="preloaded_color_state_lists"> 
  7.        <item>@color/hint_foreground_dark</item> 
  8.         ... ...  
  9.    </array> 

而加载这些资源是在preloadResources()函数中完成的,该函数中分别调用preloadDrawables()和preloadColorStateLists()加载这两类资源。加载的原理很简单,就是把这些资源读出来放到一个全局变量中,只要该类对象不被销毁,这些全局变量就会一直保存。

保存Drawable资源的全局变量是mResources,该变量的类型是Resources类,由于该类内部会保存一个Drawable资源列表,因此,实际上缓存这些Drawable资源是在Resources内部;保存Color资源的全局变量也是mResources,同样,Resources类内部也有一个Color资源的列表。

(5)使用folk启动新进程

        folk是Linux系统的一个系统调用,其作用是复制当前进程,产生一个新的进程。新进程将拥有和原始进程完全相同的进程信息,除了进程id不同。进程信息包括该进程所打开的文件描述符列表、所分配的内存等。当新进程被创建后,两个进程将共享已经分配的内存空间,直到其中一个需要向内存中写入数据时,操作系统才负责复制一份目标地址空间,并将要写的数据写入到新的地址中,这就是所谓的copy-on-write机制,即"仅当写的时候才复制",这种机制可以最大限度地在多个进程中共享物理内存。

       第一次接触folk的读者可能觉得奇怪,为什么要复制进程呢?在大家熟悉的Windows操作系统中,一个应用程序一般对应一个进程,如果说要复制进程,可能的结果就是从计算器程序复制出一个Office程序,这听起来似乎很不合理。要立即复制进程就需要首先了解进程的启动过程。

       在所有的操作系统中,都存在一个程序装载器,程序装载器一般会作为操作系统的一部分,并由所谓的Shell程序调用。当内核启动后,Shell程序会首先启动。常见的Shell程序包含两大类,一类是命令行界面,另一类是窗口界面,Windows系统中Shell程序就是桌面程序,Ubuntu系统中的Shell程序就是GNOME桌面程序。Shell程序启动后,用户可以双击桌面图标启动指定的应用程序,而在操作系统内部,启动新的进程包含三个过程。

第一个过程,内核创建一个进程数据结构,用于表示将要启动的进程。

第二个过程,内核调用程序装载器函数,从指定的程序文件读取程序代码,并将这些程序代码装载到预先设定的内存地址。

第三个过程,装载完毕后,内核将程序指针指向到目标程序地址的入口处开始执行指定的进程。当然,实际的过程会考虑更多的细节,不过大致思路就是这么简单。

在一般情况下,没有必要复制进程,而是按照以上三个过程创建新进程,但当满足以下条件时,则建议使用复制进程:即两个进程中共享了大量的程序。

举个例子,去澳大利亚看袋鼠和去澳大利亚看考拉,这是两个进程,但完成这两个进程的大多数任务都是相同的,即先订机票,然后带照相机,再坐地铁到首都机场,最后再坐14个小时的飞机到澳大利亚,到了之后唯一不同就是看考拉和袋鼠。为了更有效地完成这两个任务,可以先雇佣一个精灵进程,让它订机票、带相机、坐地铁、乘飞机,一直到澳大利亚后,从这个精灵进程中复制出两个进程,一个去看考拉,另一个去看袋鼠。如果你愿意,还可以去悉尼歌剧院,这就是进程的复制,其好处是节省了大量共享的内存。

由于folk()函数是Linux的系统调用,Android中的Java层仅仅是对该调用进行了JNI封装而已,因此,接下来以一段C代码来介绍folk()函数的使用,以便大家对该函数有更具体的认识:

/**
*FileName: MyFolk.c
*/
#include <sys/types.h>
#include <unistd.h>
int main(){
pid_t pid;
printf("pid = %d, Take camera, by subway, take air! \n", getpid());
pid = folk();
if(pid > ){
printf("pid=%d, 我是精灵! \n", getpid());
pid = folk();
if(!pid) printf("pid=%d, 去看考拉! \n", getpid());
}
else if (!pid) printf("pid=%d, 去看袋鼠! \n", getpid());
else if (pid == -) perror("folk"
);
getchar();
}

以上代码的执行结果如下:

$ ./MyFolk.bin
pid = , Take camera, by subway, take air!
pid=, 我是精灵!
pid=, 去看袋鼠!
pid=, 去看考拉!

folk()函数的返回值与普通函数调用完全不同。当返回值大于0时,代表的是父进程;当等于0时,代表的是被复制的进程。换句话说,父进程和子进程的代码都在该C文件中,只是不同的进程执行不同的代码,而进程是靠folk()的返回值进行区分的。

由以上执行结果可以看出,第一次调用folk()时复制了一个"看袋鼠"进程,然后在父进程中再次调用folk()复制了"看考拉"的进程,三者都有各自不同的进程id。

zygote进程就是本例中的"精灵进程",那些"拿相机、坐地铁、乘飞机"的操作就是zygote进程中加载的preload-classes类具备的功能。

ZygoteInit.java中复制新进程是通过在runSelectLoopMode()函数中调用ZygoteConnection类的runOnce()函数完成的,而该函数中则调用了以下代码用于复制一个新的进程。

 

forkAndSpecialize()函数是一个native函数,其内部的执行原理和上面的C代码类似。

当新进程被创建好后,还需要做一些"善后"工作。因为当zygote复制新进程时,已经创建了一个Socket服务端,而这个服务端是不应该被新进程使用的,否则系统中会有多个进程接收Socket客户端的命令。因此,新进程被创建好后,首先需要在新进程中关闭该Socket服务端,并调用新进程中指定的Class文件的main()函数作为新进程的入口点。而这些正是在调用forkAndSpecialize()函数后根据返回值pid完成的,如以下代码所示:

 

pid等于0时,代表的是子进程,handleChildProc()函数中的关键代码如下,首先是关于Socket服务端。

 

接着从指定Class文件的main()函数处开始执行,如以下代码所示:

 

至此,新进程就完全脱离了zygote进程的孵化过程,成为一个真正的应用进程。

3. 上面的说明可能不太形象,下面感性的说明一下,如下:

(1)我们来看看每个android进程如何产生的:

 下面来对Zygote进程孵化新进程的过程做进一步了解:
  • Zygote进程调用fork()函数创建出Zygote 子进程,
  • 子进程Zygote  共享父进程Zygote的代码区与连接信息。
      如下图所示,Fork()橙色箭头左边是Zygote进程,右边是创建出的Zygote‘子进程;然后Zygote’ 子进程将执行流程交给应用程序A,Android程序开始运行。
新生成的应用程序A会使用已有Zygote父进程的库与资源的连接信息,所以运行速度很快。
 
 
另外,对于上图,Zygote启动后,初始并运行DVM,而后将需要的类与资源加载到内存中。随后调用fork()创建出Zygote子进程,接着子进程动态加载并运行应用程序A。
      运行的应用程序A会使用Zygote已经初始化并启动运行的DVM代码,通过使用已加载至内存中的类与资源来加快运行速度。
 
(2)Android进程模型:
      Linux通过调用start_kernel函数来启动内核,当内核启动模块启动完成后,将启动用户空间的第一个进程——Init进程,下图为Android系统的进程模型图:
 
 
从上图可以看出,Linux内核在启动过程中,创建一个名为Kthreadd的内核进程,PID=2,用于创建内核空间的其他进程;同时创建第一个用户空间Init进程,该进程PID = 1,用于启动一些本地进程,比如Zygote进程,而Zygote进程也是一个专门用于孵化Java进程的本地进程,上图清晰地描述了整个Android系统的进程模型。

Android(java)学习笔记103:Framework运行环境之 Android进程产生过程的更多相关文章

  1. Java学习笔记【一、环境搭建】

    今天把java的学习重新拾起来,一方面是因为公司的项目需要用到大数据方面的东西,需要用java做语言 另一方面是原先使用的C#公司也在慢慢替换为java,为了以后路宽一些吧,技多不压身 此次的学习目标 ...

  2. Linux内核分析第六周学习笔记——分析Linux内核创建一个新进程的过程

    Linux内核分析第六周学习笔记--分析Linux内核创建一个新进程的过程 zl + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/U ...

  3. Android(java)学习笔记160:Framework运行环境之 Android进程产生过程

    1.前面Android(java)学习笔记159提到Dalvik虚拟机启动初始化过程,就下来就是启动zygote进程: zygote进程是所有APK应用进程的父进程:每当执行一个Android应用程序 ...

  4. Java 学习笔记之 Thread运行过程分析

    Thread运行过程分析: 以下是一个最普通的Thread实现过程,我们今天就来看仔细分析下他是如何运行的. public class ThreadRunMain { public static vo ...

  5. java学习笔记(1)java的基础介绍 、JDK下载、配置环境变量、运行java程序

    java工程师是开发软件的 什么是软件呢? 计算机包括两部分: 硬件: 鼠标.键盘.显示器.主机箱内部的cpu.内存条.硬盘等 软件: 软件包括:系统软件和应用软件 系统软件:直接和硬件交互的软件:w ...

  6. Android Studio 学习笔记(一)环境搭建、文件目录等相关说明

    Android Studio 学习笔记(一)环境搭建.文件目录等相关说明 引入 对APP开发而言,Android和iOS是两大主流开发平台,其中区别在于 Android用java语言,用Android ...

  7. Kettle学习笔记(一)— 环境部署及运行

    目录 Kettle学习笔记(一)-环境部署及运行 Kettle学习笔记(二)- 基本操作 kettle学习笔记(三)- 定时任务的脚本执行 Kettle学习笔记(四)- 总结 Kettle简介 Ket ...

  8. 《Java学习笔记(第8版)》学习指导

    <Java学习笔记(第8版)>学习指导 目录 图书简况 学习指导 第一章 Java平台概论 第二章 从JDK到IDE 第三章 基础语法 第四章 认识对象 第五章 对象封装 第六章 继承与多 ...

  9. Java学习笔记4

    Java学习笔记4 1. JDK.JRE和JVM分别是什么,区别是什么? 答: ①.JDK 是整个Java的核心,包括了Java运行环境.Java工具和Java基础类库. ②.JRE(Java Run ...

随机推荐

  1. nodebrew

    创建: 2019/05/30 完成: 2019/05/30  安装  安装 curl -L git.io/nodebrew | perl - setup 更新nodebrew nodebrew sel ...

  2. ios 支付宝支付集成

    支付宝支付: 下载官方demo,把需要的framwork下载下来,在自己的工程中,新建文件夹,然后全部塞进去,到build phases中把需要的全部导入,其中xcode7以上需要多导入两个.a文件, ...

  3. java.lang.ClassCastException: java.util.ArrayList cannot be cast to com.github.pagehelper.Page

    出现这个错误,首先看配置mybatis-config.xml中的<plugins> <plugin interceptor="com.github.pagehelper.P ...

  4. [USACO07DEC]观光奶牛Sightseeing Cows 二分答案+判断负环

    题目描述 Farmer John has decided to reward his cows for their hard work by taking them on a tour of the ...

  5. jmeter csv中获取带引号的数据详情(转)

    最近在工作中,对jmeter实践的点滴的记录这里分享,不一定正确,仅供参考和讨论,有想法的欢迎留言.谈论. 1技巧1:从csv中获取带引号的数据详情 背景:我们从csv中获取数据,在jmeter中使用 ...

  6. css奇技淫巧—border-radius

    官方介绍: 浏览器支持:IE9+, Firefox 4+, Chrome, Safari 5+,和Opera支持border-radius属性. border-radius 属性是一个最多可指定四个 ...

  7. 在邮箱服务器上执行Powershell命令Get-MessageTrackingLog 报错

    开启对应的服务即可. 中文环境: 英文环境:

  8. Codeforces Round #506 (Div. 3) - D. Concatenated Multiples(思维拼接求是否为k的倍数)

    题意 给你N个数字和一个K,问一共有几种拼接数字的方式使得到的数字是K的倍数,拼接:“234”和“123”拼接得到“234123” 分析: N <= 2e5,简单的暴力O(N^2)枚举肯定超时 ...

  9. ORACLE数据库的备份和还原。

    Oracle数据库备份与还原命令 数据导出: 1 将数据库TEST完全导出,用户名system 密码manager 导出到D:\daochu.dmp中 exp system/manager@TEST ...

  10. Hive 基本语法操练(一):表操作

    Hive 和 Mysql 的表操作语句类似,如果熟悉 Mysql,学习Hive 的表操作就非常容易了,下面对 Hive 的表操作进行深入讲解. **(1)先来创建一个表名为student的内部表** ...