1. 概览
ActivitityManagerService(下文简称AMS)作为系统中最重要的服务,在开机时就会被启动,而且一直存在。
那么,Android是如何启动AMS,以及启动AMS时做了哪些初始化工作呢?
从进入Android系统进程(SystemServer)到用户所见的桌面(HomeActivity),整个过程中,AMS都扮演着主角。
AMS就像一个大管家,总管着四大组件、进程调度、各类统计等信息。在AMS启动时,就表明了自己的身份:引导服务(BootstrapService),意味着AMS是最重要的一类服务,大多数其他系统服务的启动都依赖于AMS。
本章分析的对象是AMS在系统进程中的启动过程,虽然仅仅是启动,涉及到的知识点已然非常多。
2. 在系统进程中启动AMS
AMS对象随系统进程启动而构建,随着系统进程退出而消亡,可以说,AMS与系统进程共存亡。
本章分析的内容是系统进程启动时与AMS相关的一些初始化操作,先上一张总的启动时序图:
可以分为三个步骤:
- 初始化系统进程的运行环境;
- 初始化AMS对象;
- AMS对象启动的配套工作。
2.1 初始化系统进程的运行环境
SystemServer是我们理解Android系统进程的入口,它的初始化是从Native层开始的:Zygote从Native层调用SystemServer的main()函数,便开始了SystemServer的初始化。初始化很重要的一个步骤就是创建系统进程的运行环境,即创建一个SystemContext,调用关系如下所示:
SystemServer.main() // Zygote会从Native层调用该方法,进入SystemServer的执行代码
└── SystemServer.run() // SystemServer一旦运行起来,就一直循环处理消息队列中的消息
└── SystemServer.createSystemContext() // 创建SystemContext
SystemContext到底是什么呢?说到底,它还是一个Context类型的对象,需要借助于ActivityThread才能获取到:
ActivityThread.systemMain()函数用于创建系统进程的主线程,方法主体很简单,创建了一个新的ActivityThread对象,并调用了attach()方法,输入参数是true,表示需要将其绑定到系统进程。
应用进程创建ActivityThread对象是通过ActivityThread.main()函数完成的。由于系统进程的特殊性,专辟了一个systemMain()函数给系统进程。
接下来,我们来着重分析一下ActivityThread.attach()函数:
在上面的函数实现中,会创建几个很重要的对象:
这里是第一次调用getSystemContext()函数,所以mSystemContext为null,进而会通过ContextImpl.createSystemContext()函数创建一个新的对象:
上述函数首先会创建一个LoadedApk的对象,LoadedApk表示一个装载到内存的Apk,对于SystemContext,这个Apk就是framework-res.apk;
然后,创建一个ContextImpl的对象,进一步初始化资源相关的配置; 最终,返回的context就是SystemContext。
- mInitialApplication: Application对象,由于是通过framework-res.apk这个APK的Context创建而来,所以这个Application对象就对应到了framework-res.apk,这是系统中第一个运行的APK,所以叫做InitialApplication,它是运行在系统进程中。
兜兜转转弄了一圈,终于把SystemContext给创建好了,系统进程好好运行就好了,为什么要大费周张的搞一个运行环境Context出来呢?
操作系统运行基本单位是进程,我们写的任何Android代码最终都要落到一个实际的进程中去执行。然而,Android有意弱化了进程的概念,我们在写Android应用程序的时候,基础的概念都是运行环境(Context),而不是去考虑进程中有什么可以使用。
Android对待系统进程,也像对待普通的应用进程一样,都需要构建一个运行环境,就是Context。在构建AMS对象时,会将SystemContext传入,通过这个特殊的Context,AMS就能获取运行时所需要各种信息。
我们通过一张类图来总结一下,与系统进程运行环境初始化相关的各个类之间的关系:
-
Android要为应用进程创造一个运行环境,同样也需要为系统进程创造一个运行环境,在系统进程启动伊始,这个运行环境就需要创建完毕;
-
SystemServer会创建一个ActivityThread对象,代表系统进程的主线程,在ActivityThread对象构建的过程中,又会创建Instrumentation对象和framework-res.apk的LoadedApk对象,再通过LoadedApk创建Application对象;
-
在ActivityThread对象构建完毕后,SystemServer便可获取到系统进程的运行环境了,即SystemContext,这是ActivityThread通过ContextImpl创建而成的。
2.2 初始化ActivityManagerService
2.2.1 SystemService和SystemServiceManager
在Android起机的过程中,系统服务是相互影响的,所以有启动顺序的先后之分,譬如BatteryService,WindowManagerService就需要在ActivityManagerService创建之后才能创建。还有一些系统事件,譬如BOOT_COMPLETED广播,需要在系统完全启动之后才能发出。
Android为系统服务封装了SystemService类,设计了系统服务的生命周期:
部分系统服务的初始化依赖于当前系统已经起机到什么阶段,当系统服务启动时,onStart()函数会被调用; 系统启动到一定的阶段,onBootPhase()函数会被调用。所有系统服务的创建和生命周期的管理都是由SystemServiceManager这个类完成的,笔者着重分析该类的两个函数:
- startBootPhase(): 进入当前所在的起动阶段,即上文SystemService中定义的6个阶段之一
具体到本文中要分析的AMS,它并没有直接继承SystemService,而是通过内部类Lifecycle来继承实现的,
AMS.Lifecycle非常简单,就是调用了AMS的构造函数和start()函数,是一个间接初始化AMS的过程。
我们通过类图来总结一下SystemServiceManager, SystemService以及AMS三者之间的关系:
-
SystemService是系统服务的抽象类,封装了onStart()和onBootPhase()等生命周期函数供SystemServiceManager回掉;
-
AMS并不是直接继承SystemService,而是通过内部类Lifecycle间接完成了系统服务启动的生命周期;
-
SystemServiceManager管理了多个SystemService。
2.2.2 SystemServer和ActivityManagerService
SystemServer在完成一些简单的初始化后,就需要启动系统服务了,最重要的一系列服务是在SystemServer.startBootstrapServices()
这个函数中启动的,BootstrapServices的命名也很贴切,表示要启动一些”引导服务”,这些服务直接影响到其他服务的启动。
-
启动AMS之前,需要先连接installd进程。在系统进程(system_process)中,对应的服务就Installer,
通过Installer这个服务,就能完成一些重要目录的创建,譬如/data/user。
-
启动AMS。由此可见AMS的重要性,Android第二个启动的系统服务就是AMS。
实际上,这里通过SystemServcieManager来启动的是AMS.Lifecycle,AMS的真正初始化工作是由AMS.Lifecyle间接完成的。
-
启动PowerManagerService,这也是非常重要的一个系统服务。AMS也需要使用PowerManagerService的服务,
譬如,在启动Activity时,要避免系统进入休眠状态,就需要获取WakeLock。
-
这一步启动Lights、 DisplayManager、 PackageManager这些系统服务。
-
调用AMS.setSystemProcess()将当前进程设置为系统进程。为什么在SystemServer中需要调用AMS的方法来设置当前进程的信息呢?
因为AMS的职责之一就是维护所有进程的状态,不管是应用进程还是系统进程,都是AMS的管辖范围。
上述步骤中,与AMS直接相关的是第2步和第5步,我们再深入展开分析这两个步骤:
在第2步中,AMS.Lifecycle最终还是会调用AMS的构造器来实例化一个AMS对象:
再来看一下AMS对象初始化后,紧接着调用的AMS.start()函数:
经过第2步,AMS对象就已经构建完毕。构建时,要初始化的内容非常多,大致可以分成两类:
- 监测统计: Watchdog,CPU、内存、电量的使用统计
- 组件管理: broadcastQueues, services, providers, statckSupervisor,recentTasks, Android四大组件的相关信息都由AMS统一维护
在第5步中, 通过AMS对象调用了setSystemProcess()函数,目的是为了将当前进程(system_process)设置为系统进程:
-
通过ServiceManager向系统注册一些重要的服务。诸如meminfo、gfxinfo、dbinfo等信息都是由系统进程维护的,
可以通过adb shell dumpsys
命令输出。
-
ApplicationInfo包含了一个应用程序的信息,这些信息从AndroidManifest.xml的<application>标签中解析出来,譬如进程名、版本号、使用的主题等,那么,通过android这个包名,获取的ApplicationInfo自然就是Android系统这个应用程序的信息。
每一个普通的应用程序都会有一个AndroidManifest.xml文件,这个应用程序运行的环境,就是我们所说的”应用进程”;
有一个特殊的应用程序,具备更多的特权,这个应用程序的运行环境就是”系统进程”。
android就是这个特殊应用程序的包名,其所对应的<application>标签定义在frameworks/base/core/res/AndroidManifest.xml中,最终会编译在framework-res.apk中。
不论是应用进程还是系统进程,都有主线程,ActivityThread就是Android定义的主线程类。
AMS中的mSystemThread对象就是ActivityThread的实例,表示这是系统进程的主线程。
通过mSystemThread.installSystemApplicationInfo()这个函数调用,ApplicationInfo和ClassLoader就被设置到了
LoadedApk中,ApplicationInfo与LoadedApk的关系我们后文再描述。
-
ProcessRecord这个类用于描述一个运行的进程,AMS管理着所有ProcessRecord的状态,包括系统进程的ProcessRecord。
系统进程的ProcessRecord几个重要的属性值:
- persistent=true: 系统进程的ProcessRecord是常驻内存的
- maxAdj=SYSTEM_ADJ(-16): 在内存不足时,这个值越小,存活的几率就越大。SYSTEM_ADJ已经是倒数第二小了,可见系统进程在内存不足时被杀的可能性极小。
- active: 在上文中,我们介绍过ProcessRecord有Active和InActive两种状态,所谓”激活”,就是将应用程序的信息(IApplicationThread)绑定到进程,这样就能够通过ProcessRecord间接完成对进程的调度。
AMS通过mPidSelfLocked这个映射表来记录所有的ProcessRecord,(键 => 值)关系是(PID => ProcessRecord)。
在创建一个ProcessRecord之后,就需要集中对进程的信息进行调整了,AMS中管理进程的函数就两类:updateLruProcessLocked()用于更新最近使用进程列表,updateOomAdjLocked()用户更新进程的OOM ADJ。具体的调用场景和实现,留到后文分析。
2.3 AMS启动的配套工作
在引导服务(BootstrapServices)启动完毕后,SystemServer就开始启动核心服务(CoreServices),包括电池服务(BatteryService),用户行为统计服务(UsageStatsService)等; 最后,就是启动其他服务了,非常之多,不再此列举。部分服务在启动时,仍需要与AMS关联,譬如: AMS需要与WindowManagerService关联。AMS在这个过程之中有两个关键的步骤:
- AMS.installSystemProviders(): 表示要装载SettingsProvider, 很多系统服务都需要从这个数据库中读取配置信息;
- AMS.systemReady(): 表示当前SystemServer已经启动完毕, AMS仍需要做一些准备就绪工作。
这两个步骤牵扯到的逻辑非常之庞大,我们现在深入分析之。
2.3.1 installSystemProviders
以上有两个关键的函数,我们再进一步展开分析:
关键函数: generateApplicationProvidersLocked()
该函数基于AndroidManifest.xml文件的定义, 生成一个应用程序的Provider信息, 以方便AMS对Provider进行管理, 函数的逻辑如下所示:
-
第1步, 指定processName为”system”, uid为”Process.SYSTEM_UID(1000)”, 通过PackageManager,可以获取到运行在系统进程中的所有Provider的信息。这里获取的结果是一个元素为ProviderInfo类型的列表。一般而言,以下Provider会出现在返回结果中:
- framework-res.apk中的Provider, 定义在frameworks/base/core/res/AndroidManifest.xml
- SettingsProvider.apk中的Provider, 定义在frameworks/base/packages/SettingsProvider/AndroidManifest.xml
- 第2步, 通过PackageManager获取到的ProviderInfo只是一个静态的信息, 还需要绑定到具体的ProcessRecord。
要理解这个绑定关系,需要先了解AMS对Provider的管理方式:
-
AMS中使用ContentProviderRecord来管理一个ContentProvider。ProviderInfo, ApplicationInfo, 运行ContentProvider的ProcessRecord等信息都保存在ContentProviderRecord中。
-
AMS维护了一个ProviderMap, 支持从Authority或CompnentName到ContentProviderRecord的映射; AMS也维护了各种各样的ProcessRecord, 譬如: 前台进程, 内存常驻进程, 最近使用的进程等。 当一个ContentProvider绑定到具体的进程时, 就会添加到ProcessRecord中维护的mPubProviders的映射表中。所以, ProcessRecord.mPubProviders就表示进程所拥有的ContentProvider。
现在,我们再来看这块函数的逻辑:
-
首先, 需要确保ProcessRecord能够容纳一定数量的Provider, 前面通过PackageManager找到的ProviderInfo可能会关联到ProcessRecord中,所以, 在mPubProvider上已有容量的基础上, 再扩容的大小为找到的ProviderInfo的数量。
-
然后, 对找到的ProviderInfo列表进行遍历, 如有需要, 则新建一个ContentProviderRecord对象, 将其添加到mProviderMap中以方便AMS管理;同时, 也需要将其添加到PrcoessRecord.mPubProviders中。
-
最后, 由于可能新增其他APK中的ProviderInfo, 所以需要确保对APK进行dex优化。
关键函数: ActivityThread.installSystemProviders()
将一个ProviderInfo绑定到ProcessRecord后, AMS中就有了Provider的信息了, 但这时Provider还不能工作, 因为真正的ContentProvider还未创建, 该函数的就是将上面找到的ProviderInfo装载到系统进程之中, 函数的逻辑如下:
所有ContentProvider的创建都需要经过installContentProvider()函数, 它接收两个参数, 一个是进程的运行环境Context, 一个是
ProviderInfo列表; 对系统进程而言, 运行环境就是mInitialApplication。该函数中使用了ContentProviderHodler这个类,它实现了Parcelable接口, 通常表示这类数据结构需要跨进程传递, 应用进程中生成的ContentProvider需要向系统进程注册后才能使用, 所以, 需要将ContentProvider的信息从应用进程传递到系统进程, 这就用到了ContentProviderHolder进行数据封装。
基于ProviderInfo生成的ContentProviderHolder的函数实现是installProvider():
-
找到ProviderInfo对应的Context:
- 对于framework-res.apk中定义的com.android.server.am.DumpHeapProvider而言, 重新设置的Context就是mInitialApplication
- 对于SettingProvider.apk中定义的SettingsProvider而言, 它的包名为com.android.providers.settings, 不等于mInitialApplication的包名android, 所以, 会通过Context.createPackageContext()函数创建一个新的Context
-
反射创建ContentProvider对象, 之所以前面要找到ProviderInfo的Context, 就是因为只有与ContentProvider对应的Context才能从APK中加载Java字节码进行反射。在ContentProvider创建好后, 会调用ContentProvider.attach()函数, 其内部进行一些初始化操作后, 会调用ContentProvider.onCreate()函数, 这样以来, ContentProvider就进入其生命周期了。
-
设置CotentProviderHolder。要理解这一段逻辑, 需要先理解ContentProvider的管理机制. 在Android四大组件之ContentProvider一文中有详细的介绍, 在此, 我们只需要了解这里会找到ContentProviderHolder, 拿到这个数据结构后, 就能向AMS申报Provider可以使用了。
至此, installSystemProviders()函数的逻辑已经分析完毕,该函数的功能就是装载运行在系统进程中的Provider,诸如SettingsProvider这一类需要被很多其他系统服务用到的Provider,都将在这一步被装载。
2.3.2 systemReady
该函数的逻辑比较庞大,我们按序分成几个片段分析。
-
mSystemReady表示系统进程是否准备完毕, 由于systemReady()函数会被多次调用到,而且多线程并行,所以一旦mSystemReady为true,表示不再需要执行下面的逻辑了,直接回调函数入参goingCallback,这个回调函数我们放到后面分析;
-
进入这一步,表示mSystemReady为false,第一次进入systemReady()函数时,就是这种场景。这里会恢复最近任务列表;
-
涉及到PRE_BOOT_COMPLETED广播的处理。这个广播在BOOT_COMPLETED广播之前发送,而且只发给系统应用。系统应用收到该广播后,也应该标注已经处理过该广播,下次不用再派发过来。设计PRE_BOOT_COMPLETED广播的目的,是为了应对系统升级的场景:当从旧的版本升级时,系统应用可能有一些清除数据的需要,系统升级后的第一次起机时,就会向接收者派发这个广播。
PRE_BOOT_COMPLETED的派发实现在deliverPreBootCompleted()函数中,本文不展开分析。需要知道这里有两个控制变量:
- mDidUpdate: 默认为false; 如果为true,表示已PRE_BOOT_COMPLETED已经处理完毕,确切的说是已经检查完毕,因为在派发该广播之前,要检查是否已经向接收者派发过一次该广播了; 之所以该变量取名为update,是因为该广播的设计与系统升级后的操作有关;
- mWaitingUpdate: 默认为false; 如果有PRE_BOOT_COMPLETED的接收者,而且之前没有处理过该广播,则这个变量会被true,直到广播处理完成后才被重新置成false。
经过这3步mSystemReady就被设置为true了,再次调用该systemReady()函数,就会进入第1步的逻辑。
在AMS完全准备就绪之前,就可能有一些进程已经启动,在这里需要进行一个检查,如果非peristent的进程先于AMS启动,那么就需要杀掉这些进程。
注意,这里所指的进程是AMS管理的进程(系统进程和应用进程),Native进程并不在AMS的管辖范围。
在该代码片段的最后,输出了一行Event Log:
boot_process_ams_ready: xxx
进行日志分析时,可以根据这行日志信息判定AMS已经准备就绪,其他应用进程可以启动了。因为只有当AMS就绪后,才能开始管理应用进程。
在AMS完全准备完毕后,就可以从SettingsProvider中读取一些配置信息了,以上代码片段中,最重要的就是调用传入的goingCallback.run()函数:
这个函数以回调的形式,在AMS准备就绪后被调用。AMS准备就绪是个关键节点,在此之后,很多其他服务就可以开始进入准备就绪的状态了,其实就是调用这些系统服务的systemReady()函数。
以上代码片段,还有一个关键事件就是启动SystemUi,其实就是启动com.androi.systemui这个包(对应的APK是SystemUI.apk)中的一个服务。
回调完goingCallback.run()函数,AMS.systemReady()并没有就此结束,它还没有完成自己的使命。
以上代码片段是systemReady()函数的最后部分,一部分工作是发送通知:“当前用户已经进入使用状态了”; 另一部分工作就是启动桌面,有两处关键调用: startHomeActivityLocked()和mStackSupervisor.resumeTopActivitiesLocked(),在Activity启动过程一文中,我们再详细分析涉及Activity启动的一系列函数。
至此,systemReady()函数的执行逻辑已经分析完了,一共经历了4个步骤
- 片段1: 主要处理PRE_BOOT_COMPLETED广播;
- 片段2: 杀掉在AMS准备就绪之前就已经启动的进程。因为这些进程要被AMS管理起来,需要在AMS准备就绪之后才启动;
- 片段3: 主要任务是通知其他系统服务进入就绪状态。在AMS就绪完毕后,从SettingsProvider和本地文件中(urigrants.xml)读取一些配置信息。通知其他系统服务进入就绪状态,启动SystemUi;
- 片段4: 主要任务是启动桌面。
systemReady()函数调用完成之后,桌面就可见了,用户就真正见到了Android系统的可操作界面。想必,各位读者心中还有一些困惑,不是开机会发送ACTION_BOOT_COMPLETED广播吗,桌面都已经启动了,怎么一直都没见到这个广播在哪发送?下面就给大家解惑。
2.3.3 发送ACTION_BOOT_COMPLETED广播
在桌面启动后,桌面进程主线程的消息队列进入空闲状态,此时会发起跨进程调用AMS.activityIdle(),紧接着会引发下面的调用关系:
AMS.activityIdle()
└── ActivityStackSupervisor.activityIdleInternalLocked()
└── ActivityStackSupervisor.checkFinishBootingLocked()
└── AMS.postFinishBooting() // 向主线程抛出FINISH_BOOTING_MSG消息
└── AMS.MainHandler.handleMessage(FINISH_BOOTING_MSG)
└── AMS.finishBooting()
我们来看这一轮调用的落脚点AMS.finishBooting()函数:
以上函数的逻辑不复杂,主要经过以下流程:进行ABI检查,注册一些系统广播,启动之前被抑制启动的进程,发送ACTION_BOOT_COMPLETED广播,调度性能统计功能。
3 总结
本章节分析了系统进程启动过程中与AMS相关的逻辑,总体而言,可以分为三步:
-
初始化系统进程的运行环境。所谓运行环境,就是指的Context,Context蕴含了代码执行过程中所需要的信息,包括进程信息、包信息、资源信息等等。Android有意弱化进程的概念,强化Context的概念,在Android编程时,一定避免不了与Context打交道。对于系统进程而言,Context有一定的特殊性,所以单独造了一个SystemContext。
-
初始化AMS对象。AMS对象在系统进程构建,作为最重要的系统服务,AMS初始化要做的事情非常多。由于各种系统服务耦合在一块,相互影响,Android设计了“系统进程启动阶段”这个概念,就像一个简单的状态机,只有进入的某个阶段,才能做某些操作。譬如,只有进入PHASE_ACTIVITY_MANAGER_READY,AMS才能正常工作,这时才可以进行派发广播、管理进程等操作。
因为系统进程也在AMS的管辖范围之内,所以,AMS对象构建后有一个重要的任务,就是设置系统进程的一些属性。这时,会将第一个启动的应用frameworks-res.apk的信息装载到系统进程中,创建一个系统进程的ProcessRecord对象以便AMS管理。
-
AMS初始化的配套工作。这里所谓配套工作是指,系统要完全运行起来,还需要经由AMS进行一系列的运作:系统设置SettingsProvider会经由AMS装载到系统进程中;其他系统服务在AMS准备就绪后,也会进入就绪状态,表示可以正常工作;桌面会经由AMS启动,最终ACTION_BOOT_COMPLETED广播发出。