9.Framework的启动过程

Linux系统启动过程的最后,内核将读取init.rc文件,并启动该文件中定义的各种服务程序.由于Android系统相对于Linux内核而言仅仅是一个Linux应用程序而已,因此,该程序也是在init.rc中被声明,从而当Linux内核启动后能够接着运行Android内核.

系统中运行的第一个Dalvik虚拟机程序叫做zygote,接下来的所有Dalvik虚拟机进程都是通过这个zygote孵化出来的.zygote进程中包含两个主要模块,分别如下:

  • Socket服务端.该Socket服务端用于接收启动新的Dalvik进程的命令
  • Framework共享类及共享资源.当zygote进程启动后,会装载一些共享的类及资源,其中共享类是在preload-classes文件中被定义,共享资源是在preload-resources中被定义.因为zygote进程用于孵化出其他Dalvik进程,因此,这些类和资源装载后,新的Dalvik进程就不需要再装载这些类和资源了,这也就是所谓的共享.

zygote进程对应的具体程序是app_process,该程序存在于system/bin目录下,启动该程序的指令是在init.rc中进行配置的.

zygote孵化出的第一个Dalvik进程叫做SystemServer,SystemServer仅仅是该进程的别名,而该进程具体对应的程序依然是app_process,因为SystemServer是从app_process中孵化出来的.SystemServer中创建了一个Socket客户端,并有AMS负责管理该客户端,之后所有的Dalvik进程都将通过该Socket客户端间接被启动.当需要启动新的APK进程时,AMS中会通过该Socket客户端向zygote进程的Socket服务端发送一个启动命令,然后zygote会孵化出新的进程.

从系统架构的角度来讲,就在于此即先创建一个zygote,并加载共享类和资源,然后通过该zygote去孵化新的Dalvik进程,该架构的特点有两个.

  • 每一个进程都是一个Dalvik虚拟机
  • zygote进程预先会装载共享类和共享资源,这些类及资源实际上就是SDK中定义的大部分类和资源.因此,当通过zygote孵化出新的进程后,新的APK进程只需要去装载APK自身包含的类和资源即可,这就有效地解决了多个APK共享Framework资源的问题.

dalvikvm的执行语法为dalvikvm -cp 类路径 类名,dalvikvm的作用就是创建一个虚拟机并执行参数中指定的Java类.

dvz的作用是从zygote进程中孵化出一个新的进程,新的进程也是一个Dalvik虚拟机.该进程与dalvikvm启动的虚拟机相比,区别在于该进程中已经预装了Framework的大部分类和资源.

dvz -classpath /data/app/solarex.apk com.solarexsoft.demo.Main,dvz的语法是dvz -classpath 包名称 类名.一个APK的入口类是ActivityThread类,Activity类仅仅是被回调的类,因此不可以通过Activity类来启动一个APK.

Framework在启动时需要加载运行两个特定Java类,一个是ZygoteInit.java,另一个是SystemServer.java.为了便于使用,系统提供了一个app_process进程,该进程会自动运行这两个类,从这个角度来讲,app_process的本质就是使用dalvikvm启动ZygoteInit.java,并在启动后加载Framework中的大部分类和资源.

系统中只有一处使用app_process,那就是在init.rc中,使用时参数包含了--zygote--start-system-server,app_process和dalvikvm在本质上是相同的,唯一的区别是app_process可以指定一些特别的参数,这些参数有利于Framework启动特定的类,并进行一些特别的系统环境参数设置.

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

Socket编程中有两种方式触发Socket数据读操作.一种是使用listen()监听某个端口,然后调用read()去从这个端口上读数据,这种方式被称为阻塞式读操作,因为当端口没有数据时,read()函数将一直等待,直到数据准备好后才返回;另一种是使用select()函数将需要监测的文件描述符作为select()函数的参数,然后当该文件描述符上出现新的数据后,自动触发一个中断,然后在中断处理函数中再去读指定文件描述符上的数据,这种方式被称为非阻塞式读操作.LocalServerSocket中使用的正是后者,即非阻塞读操作.

当LocalServerSocket端口准备好后,main()函数中调用runSelectLoopMode()进入非阻塞读操作,该函数中首先将sServerSocket加入到被检测的文件描述符列表中.然后在while(true)循环中将该描述符添加到select的列表中,并调用ZygoteConnection类的runOnce()函数处理每一个Socket接收到的命令.

而在SystemServer进程中则会创建一个Socket客户端,具体的实现代码是在Process.java类中,而调用Process类是在AMS类中的startProcessLocked()函数中.Process.start()函数内部又调用了startViaZygote(),该函数的实体正是使用一个本地Socket向zygote中的Socket发送进行启动命令.主要过程就是将startViaZygote()的函数参数转换为一个ArrayList列表,然后再构造出一个LocalSocket本地Socket接口,并通过该LocalSocket对象构造出一个BufferedWriter对象,最后通过该对象将ArrayList列表中的参数传递给zygote中的LocalServerSocket对象,而在zygote端,就会调用Zygote.forkAndSpecialize()函数孵化出一个新的应用进程.

预装载的类列表是在framework.jar中的一个文本文件列表,名称为preload-classes,该列表的原始定义在frameworks/base/preload-classes文本文件中,而该文件又是通过frameworks/base/tools/preload/WritePreloadedClassFile.java类生成的.

在Android源码编译的时候,会最终把preload-classes文件打包到framework.jar中.有了这个列表后,ZygoteInit中通过调用preloadClasses()完成装载这些类.装载的方法很简单,就是读取preload-classes列表中的每一行,因为每一行代表了一个具体的类,然后调用Class.forName()装载目标类.

preload-resources是在frameworks/base/core/res/res/values/arrays.xml中被定义的,包含两类资源,一类是drawable资源,另一类是color资源.而加载这些资源是在preloadResources()函数中完成的,该函数中分别调用preloadDrawables()和preloadColorStateLists()加载这两类资源.加载的原理很简单,就是把这些资源读出来放到一个全局变量中,只要该类对象不被销毁,这些全局变量就会一直保存.保存Drawable资源的全局变量是mResources类,类型是Resources类,该类内部会保存一个Drawable资源列表,实际上缓存这些Drawable资源在Resources内部,Resources类内部也有一个Color资源的列表.

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

启动新的进程包含三个过程:

  • 内核创建一个进程数据结构,用于表示将要启动的进程.
  • 内核调用程序装载器函数,从指定的程序文件读取程序代码,并将这些程序代码装载到预先设定的内存地址.
  • 装载完毕后,内核将程序指针指向到目标程序地址的入口处开始执行指定的进程.

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

ZygoteInit.java中复制新进程是通过runSelectLoopMode()函数中调用ZygoteConnection类的runOnce()函数完成的,而该函数中则调用了Zygote.forkAndSpecialize() native函数复制一个新的进程.当新进程被创建好后,还需要做一些"善后"工作,因为当zygote复制新进程时,已经创建了一个Socket服务端,而这个服务端是不应该被新进程使用的,否则系统中会有多个进程接收Socket客户端的命令.因此,新进程被创建好后,首先需要在新进程中关闭该Socket服务端,并调用新进程中指定的Class文件的main()函数作为新进程的入口点.pid等于0时,handleChildProc()函数会关闭Socket服务端,接着调用ZygoteInit.invokeStaticMain()从指定Class文件的main()函数处开始执行.

SystemServer进程是zygote孵化出的第一个进程,该进程是从ZygoteInit.java的main()函数中调用startSystemServer()开始的.与启动普通进程的差别在于,zygote类为启动SystemServer提供了专门的函数startSystemServer(),而不是使用标准的forkAndSpecilize()函数,同时,SystemServer进程启动后首先要做的事情和普通进程也有所差别.

startSystemServer()定义了一个String[]数组,数组中包含了要启动的进程的相关信息,其中最后一项指定新进程启动后装载的第一个java类,为com.android.server.SystemServer,调用Zygote.forkSystemServer()从当前zygote进程孵化出新的进程,新进程启动后,在handleSystemServerProcess()函数中主要完成两件事情,第一是关闭Socket服务端,第二是执行com.android.server.SystemServer的main()函数,还做了一些额外的运行环境配置.

SystemServer进程在Android的运行环境中扮演了"神经中枢"的作用,APK应用中能够直接交互的大部分系统服务都是在该进程中运行,常见的比如WMS,AMS,PMS等,这些系统服务都是以一个线程的方式存在于SystemServer进程中.

SystemServer的main()函数首先调用的是init1()函数,这是一个native函数,内部会进行一些与Dalvik虚拟机相关的初始化工作,该函数执行完毕后,其内部会调用Java端的init2()函数,主要的系统服务都是在init2()函数中完成的.

从ServerThread的run()方法内部开始真正启动各种服务线程.基本上每个服务都有对应的Java类,启动这些服务的模式可归类为三种:

  • new XXX()
  • XXX.getInstance()
  • XXX.main()

AMS的启动模式如下:

  • 调用main()函数,返回一个Context对象,而不是AMS服务本身
  • 调用AMS.setSystemProcess()
  • 调用AMS.installProviders()
  • 调用systemReady(),当AMS执行完systemReady()后,会相继启动相关联服务的systemReady()函数,完成整体初始化.

AMS的systemReady()函数中会调用startHomeActivityLocked()函数,在getHomeIntent()中可以看到Intent的Action为Intent.ACTION_MAIN,intent.addCategory(Intent.CATEGORY_HOME),任何可以响应这个intent的应用程序都可以作为home activity.

results matching ""

    No results matching ""