《Android内核剖析》读书笔记 第9章 Framework的启动过程

来源:互联网 发布:手机游戏录制软件 编辑:程序博客网 时间:2024/06/06 14:09

对于Linux内核来说,其实Android系统就是一个普通的Linux应用程序而已,只是在init.rc中被设置为默认启动;

  1. Framework运行环境综述
    任何系统启动过程的本质都是要建立一套系统运行所需的环境;android系统的启动其实就是建立dalvik虚拟机运行所需的环境,总体如下:

    1. android系统启动的第一个进程为zygote,意思为“受精卵”,因为接下来所有的Dalvik虚拟机进程都是通过它孵化出来的;该进程包括2个核心模块:
      1. Socket服务器:用于接收启动新的Dalvik进程的命令;
      2. Framework共享类与资源:所有Dalvik虚拟机进程都会需要的共享类及资源,其实就是android.jar中的大部分内容,待加载的类和资源列表分别通过preload-classes/preload-resources定义;
    2. zygote孵化出的第一个Dalvik虚拟机进程为SystemServer;该进程会负责加载android应用所需的所有基础服务(比如PowerManagerServiceAlarmManagerService、WifiService...),并且会创建一个由Ams(具体的东西详见后面的第19章说明)管理的Socket客户端,该客户端用于向zygote中的服务端发送启动新Dalvik进程命令;
    3. 从之前的运行环境架构图可以看出有如下特点:
      1. 在android系统中每一个应用就是一个独立的dalvik进程,他们之间相互独立,这样就保证了单个应用的crash不会影响其他应用;而且可以设置单个应用的资源和权限限制,比如内存等;
      2. 公共的类和资源只会加载一份,这样对应用而言只需加载自身所需的类和资源即可,这样就有效的节省了对内存的消耗、加载效率也会提升;
        这点和PC上运行多个JAVA应用是不一样的,在PC上一般不存在多个JAVA进程共享一份类和资源的情况;
  2. Zygote进程的启动过程
    1. 加载点为linux系统根目录下的init.rc中配置的zygote服务,该文件可以直接通过adb pull /init.rc .命令或者在DDMS视图中下载,对应于android源码中的路径为./system/core/rootdir/,具体配置为:
      service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server    class main
          socket zygote stream 660 root system
          onrestart write /sys/android_power/request_state wake
          onrestart write /sys/power/state on
          onrestart restart media
          onrestart restart netted
      该以上配置命令中核心信息包括:
      1. 用/system/bin/app_process命令启动名称为zygote的系统服务(注意:该进程无法在DDMS中直接查看,他属于系统服务);
        其作用可以简单理解为就是启动一个Dalvik虚拟机,并通过指定参数启动一个JAVA应用进程,和执行java命令本质上是一样的;
        该命令的源码位于./frameworks/base/cmds/app_process/app_main.cpp
        源码会依赖于./frameworks/base/include/android_runtime/AndroidRuntime.h
        具体实现位于./frameworks/base/core/jni/AndroidRuntime.cpp
        1. 启动Dalvik虚拟机的具体指令路径为:
          app_main.cpp中的main() ==> AndroidRuntime.cpp中的start() ==> AndroidRuntime.cpp中的startVm() 
          ==> ./dalvik/vm/Jni.cpp中的JNI_CreateJavaVM() ==> ./dalvik/vm/Init.cpp中的dvmStartup();
          期间会完成多个dalvik基础服务启动以及dalvik.system.NativeStart.main()的执行;
        2. Dalvik虚拟机环境准备完毕之后,利用FindClass/CallStaticVoidMethod执行具体Java文件的main方法;
      2. --zygote标示以com.android.internal.os.ZygoteInit类为虚拟机执行入口;
      3. --start-system-server仅在指定--zygote参数下有效,标示ZygoteInit启动完毕后孵化出第一个Dalvik进程SystemServer;
      4. socket标示zygote进程中启动的Socket服务端类型以及端口等信息;
      5. onrestart标示该服务重启后需要触发的操作,即需要唤醒电源、重启media/netted服务;
    2. ZygoteInit.main()核心加载逻辑
      1. registerZygoteSocket(); 构造LocalServerSocket启动Socket服务端,用以接收启动新的Dalvik进程的命令;
      2. preloadClasses(); 加载Framework共享类,待加载列表位于framework.jar/preloaded-classes文件;
        preloaded-classes源文件位于./frameworks/base/preloaded-classes;
        而该文件又可以通过java -Xss512M -cp ./out/host/darwin-x86/framework/preload.jar WritePreloadedClassFile ./frameworks/base/tools/preload/XXXXXX.compiled来生成;
        preload源码位于./frameworks/base/tools/preload/,最后一个参数XXXXXX.compiled二进制文件即是真正的待加载列表;而至于这个.compiled二进制文件如何来的,还未知!
      3. preloadResources(); 加载Framework共享资源,包括图片和颜色;待加载资源位于/frameworks/base/core/res/res/values/arrays.xml;
        其中定义了2个array,分别为preloaded_drawables/preloaded_color_state_lists;
      4. startSystemServer(); 孵化出第一个Dalvik虚拟机进程SystemServer;
        1. 指定新进程启动相关参数,主要是进程名称system_server(为什么DDMS中看到的进程名称为system_process呢?)、以及将要执行的第一个Java类:com.android.server.SystemServer
        2. 执行Zygote.forkSystemServer(…)创建新进程;
          该进程实际上是通过Linux系统的一个系统调用fork(); 完成的,其作用是复制当前进程所包括的所有信息,并产生一个新进程;这样做的目的主要是为了确保Zygote进程中加载的共享类和资源只会存在一份,从而有效节省系统资源;
        3. 执行handleSystemServerProcess(…);
          1. closeServerSocket(); 关闭从zygote进程中copy过来的Socket服务器;
          2. RuntimeInit.zygoteInit(…); 初始化一些额外的运行环境,并执行RuntimeInit.invokeStaticMain(…) 完成对SystemServer.main()函数的运行,至此就完成了新的Java进程SystemServer的创建了;
            而至于main()中执行的具体逻辑如下:
            1. 执行 Looper.prepareMainLooper(); 为主线程做异步消息队列准备;
            2. 加载android应用所需的所有基础服务,比如:Ams、Wms、Pms、WifiService…
            3. 执行 ActivityManagerService.self().systemReady(…); 
              1. 首先会执行 startSystemUi(…); 启动 com.android.systemui.SystemUIService服务(可以通过DDMS查看到com.android.systemui进程);
                该进程主要是完成顶部状态栏PhoneStatusBar、电池电量PowerUI、铃声RingtonePlayer这三个服务的加载;
                注意:该部分逻辑在老版本中可能没有,书中也没提到,我是以4.2版本中的源码为准的;
              2. 执行多个服务的systemReady()方法,因为很多服务需要等Ams初始化完成才能真正ready;
              3. 执行 mMainStack.resumeTopActivityLocked(); 启动任务列表中的最上面的一个Activity;
                startHomeActivityLocked()执行过程中,系统会查询所有能响应category为CATEGORY_HOME的Activity列表,若出现多个则可以由用户自己选择,并允许用户记住该选择;
                至此android系统的主页面应该就加载出来了,包括系统状态栏、以及Home;
                从上可以看出,任何人都可以做一个Laucher应用,只要申请可以响应对应的category即可,就像Facebook做的Home应用一样;
            4. 执行 Looper.loop(); 主线程进入接受消息状态;
      5. runSelectLoopMode(); 让之前新建好的Socket服务端进入非阻塞读操作,等待接收命令;
        当接收到请求时会调用ZygoteConnection.runOnce(),该方法的核心作用就是孵化出新的应用进程;
        1. 具体创建新进程是靠执行 Zygote.forkAndSpecialize(…) 完成的;与SystemServer进程的创建对比可以看出,两者的方式并不完全相同,但都是基于fork模式来复制进程;
        2. 对复制出来进程善后处理,这个和SystemServer新建后的处理一致,主要是关闭Socket服务器、以及启动新的目标Java文件ActivityThread中的main函数;

这个章节有非常多的细节待专研,目前的疑惑包括:

  1. dalvik虚拟机启动时如何和dalvik.system.NativeStart关联上的?而且该class居然找不到对应的jni文件?
  2. startSystemUi时启动的是一个Service,但实际加载的顶部状态栏PhoneStatusBar却明显和前台UI相关,如何实现的前台UI可以不依赖于Activity?
  3. 系统默认启动后还会加载一些基础应用,比如电话phone、短信sms等,这些应用又是在什么时候自动启动的呢?