此次旅程:
· 行程的起点是am。am是Android中很重要的程序,读者务必要掌握它的用法。我们利用am start命令,发起本次目标Activity的启动请求。
· 接下来进入ActivityManagerService和ActivityStack这两个核心类。对于启动Activity来说,这段行程又可分细分为两个阶段:第一阶段的主要工作就是根据启动模式和启动标志找到或创建ActivityRecord及对应的TaskRecord;第二阶段工作就是处理Activity启动或切换相关的工作。
· 首先讨论了AMS直接创建目标进程并运行Activity的流程,其中涉及目标进程的创建,在目标进程中Android运行环境的初始化,目标Activity的创建以及触发onCreate、onStart及onResume等其生命周期中重要函数调用等相关知识点。
· 接着又讨论了AMS先pause当前Activity,然后再创建目标进程并运行Activity的流程。其中牵扯到两个应用进程和AMS的交互,其难度之大可见一斑。
am和pm一样,也是一个脚本,它用来和AMS交互,如启动Activity、启动Service、发送广播等。其核心文件在Am.java中。
am start [-D] [-W] [-P <FILE>][--start-profiler <FILE>] [-S] <INTENT>
am最终将调用AMS的startActivityAndWait函数来处理这次启动请求。
用户在Android系统上想干的三件事情,分别用A、B、C表示,将每一件事情称为一个Task。一个Task还可细分为多个子步骤,即Activity。
A、B两个Task使用了不同的Activity来完成相应的任务。注意,A、B这两个Task的Activity之间没有复用。
再来看C这个Task,它可细分为4个Activity,其中有两个Activity分别使用了A Task的A1、B Task的B2。C Task为什么不新建自己的Activity,反而要用其他Task的呢?这是因为用户想做的事情(即Task)可以完全不同,但是当细分Task为Activity时,就可能出现Activity功能类似的情况。既然A1、B2已能满足要求,为何还要重复“发明轮子”呢?另外,通过重用Activity,也可为用户提供一致的界面和体验。
Android是如何组织Task及它所包含的Activity的:
· 本例中的Task包含4个Activity。用户可单击按钮跳转到下一个Activity。同时,通过返回键可回到上一个Activity。
· 虚线下方是这些Activity的组织方式。Android采用了Stack的方法管理这3个Activity。例如在A1启动A2后,A2入栈成为栈顶成员,A1成为栈底成员,而界面显示的是栈顶成员的内容。当按返回键时,A3出栈,这时候A2成为栈顶,界面显示也相应变成了A2。
多个Task又会是何种情况呢:
对多Task的情况来说,系统只支持一个处于前台的Task,即用户当前看到的Activity所属的Task,其余的Task均处于后台,这些后台Task内部的Activity保持顺序不变。用户可以一次将整个Task挪到后台或者置为前台。
· Activity由ActivityRecord表示,Task由TaskRecord表示。ActivityRecord的task成员指向该Activity所在的Task。state变量用于表示该Activity所处的状态(包括INITIALIZING、RESUMED、PAUSED等状态)。
· ActivityStack用mHistory这个ArrayList保存ActivityRecord。令人大跌眼镜的是,该mHistory保存了系统中所有Task的ActivityRecord,而不是针对某个Task进行保存。
· ActivityStack的mMainStack成员比较有意思,它代表此ActivityStack是否为主ActivityStack。有主必然有从,但是目前系统中只有一个ActivityStack,并且它的mMainStack为true。从ActivityStack的命名可推测,Android在开发之初也想用ActivityStack来管理单个Task中的ActivityRecord(在ActivityStack.java的注释中说过,该类为“State and management of asingle stack of activities”),但不知何故,在现在的代码实现将所有Task的ActivityRecord都放到mHistory中了,并且依然保留mMainStack。
· ActivityStack中没有成员用于保存TaskRecord。
Launch Mode用于描述Activity的启动模式,目前一共有4种模式,分别是standard、singleTop、singleTask和singleInstance。实际上启动模式就是用于控制Activity和Task关系的。
standard:一个Task中可以有多个相同类型的Activity。注意,此处是相同类型的Activity,而不是同一个Activity对象。例如在Task中有A、B、C、D4个Activity,如果再启动A类Activity, Task就会变成A、B、C、D、A。最后一个A和第一个A是同一类型,却并非同一对象。另外,多个Task中也可以有同类型的Activity。
· singleTop:当某Task中有A、B、C、D4个Activity时,如果D想再启动一个D类型的Activity,那么Task将是什么样子呢?在singleTop模式下,Task中仍然是A、B、C、D,只不过D的onNewIntent函数将被调用以处理这个新Intent,而在standard模式下,则Task将变成A、B、C、D、D,最后的D为新创建的D类型Activity对象。在singleTop这种模式下,只有目标Acitivity当前正好在栈顶时才有效,例如只有处于栈顶的D启动时才有用,如果D启动不处于栈顶的A、B、C等,则无效。
· singleTask:在这种启动模式下,该Activity只存在一个实例,并且将和一个Task绑定。当需要此Activity时,系统会以onNewIntent方式启动它,而不会新建Task和Activity。注意,该Activity虽只有一个实例,但是在Task中除了它之外,还可以有其他的Activity。
· singleInstance:它是singleTask的加强版,即一个Task只能有这么一个设置了singleInstance的Activity,不能再有别的Activity。而在singleTask模式中,Task还可以有其他的Activity。
除了启动模式外,Android还有其他一些标志用于控制Activity及Task之间的关系。这里只列举一二,详细信息请参阅SDK文档中Intent的相关说明。
· FLAG_ACTIVITY_NEW_TASK:将目标Activity放到一个新的Task中。
· FLAG_ACTIVITY_CLEAR_TASK:当启动一个Activity时,先把和目标Activity有关联的Task“干掉“,然后启动一个新的Task,并把目标Activity放到新的Task中。该标志必须和FLAG_ACTIVITY_NEW_TASK标志一起使用。
· FLAG_ACTIVITY_CLEAR_TOP:当启动一个不处于栈顶的Activity时候,先把排在它前面的Activity“干掉”。例如Task有A、B、C、D4个Activity,要要启动B,直接把C、D“干掉”,而不是新建一个B。
startActivityMayWait函数的目标是启动com.dfp.test.TestActivity,假设系统之前没有启动过该Activity,本例最终的结果将是:
· 由于在am中设置了FLAG_ACTIVITY_NEW_TASK标志,因此除了会创建一个新的ActivityRecord外,还会新创建一个TaskRecord。
· 还需要启动一个新的应用进程以加载并运行com.dfp.test.TestActivity的一个实例。
· 如果TestActivity不是Home,还需要停止当前正在显示的Activity。
· 首先需要通过PKMS查找匹配该Intent的ActivityInfo。
· 处理FLAG_CANT_SAVE_STATE的情况,但系统目前不支持此情况。
· 另外,获取调用者的pid和uid。由于本例的caller为null,故所得到的pid和uid均为am所在进程的uid和pid。
启动Activity的核心函数是startActivityLocked,该函数异常复杂,将用一节专门分析。
第三阶段的工作就是根据返回值做一些处理,那么res返回成功(即res== IActivityManager.START_SUCCESS的时候)后为何还需要等待呢?
这是因为目标Activity要运行在一个新的应用进程中,就必须等待那个应用进程正常启动并处理相关请求。
startActivityLocked函数的主要工作包括:
· 处理sourceRecord及resultRecord。其中,sourceRecord表示发起本次请求的Activity,resultRecord表示接收处理结果的Activity(启动一个Activity肯定需要它完成某项事情,当目标Activity将事情成后,就需要告知请求者该事情的处理结果)。在一般情况下,sourceRecord和resultRecord应指向同一个Activity。
· 处理app Switch。如果AMS当前禁止app switch,则只能把本次启动请求保存起来,以待允许app switch时再处理。从代码中可知,AMS在处理本次请求前,会先调用doPendingActivityLaunchesLocked函数,在该函数内部将启动之前因系统禁止app switch而保存的Pending请求。
· 调用startActivityUncheckedLocked处理本次Activity启动请求。
AMS提供了两个函数,用于暂时(注意,是暂时)禁止App切换。为什么会有这种需求呢?因为当某些重要(例如设置账号等)Activity处于前台(即用户当前所见的Activity)时,不希望系统因用户操作之外的原因而切换Activity(例如恰好此时收到来电信号而弹出来电界面)。
2.3.1.1 AMS.stopAppSwitches()
· 此处控制机制名叫app switch,而不是activity switch。为什么呢?因为如果从受保护的activity中启动另一个activity,那么这个新activity的目的应该是针对同一任务,这次启动就不应该受app switch的制约,反而应该对其大开绿灯。目前,在执行Settings中设置设备策略(DevicePolicy)时就会stopAppSwitch。
· 执行stopAppSwitch后,应用程序应该调resumeAppSwitches以允许app switch,但是为了防止应用程序有意或无意忘记resume app switch,系统设置了一个超时(5秒)消息,过了此超时时间,系统将处理相应的消息,其内部会resume app switch。
2.3.1.2 resumeAppSwitches()
在resumeAppSwitches中只设置mAppSwitchesAllowedTime的值为0,它并不处理在stop和resume这段时间内积攒起的Pending请求,那么这些请求是在何时被处理的呢?
· 从前面代码可知,如果在执行resume app switch后,又有新的请求需要处理,则先处理那些pending的请求(调用doPendingActivityLaunchesLocked)。
· 在resumeAppSwitches中并未撤销stopAppSwitches函数中设置的超时消息,所以在处理那条超时消息的过程中,也会处理pending的请求。
startActivityUncheckedLocked函数很长,一句话总结本例中startActivityUncheckedLocked函数的功能:创建ActivityRecord和TaskRecord并将ActivityRecord添加到mHistory末尾,然后调用resumeTopActivityLocked启动它。
即为新创建的ActivityRecord找到一个合适的Task。虽然本例最终的结果是创建一个新的Task,但是该函数的处理逻辑却比较复杂。
2.3.2.1 startActivityUncheckedLocked分析之一
第一阶段的工作主要确定是否需要为新的Activity创建一个Task,即是否设置FLAG_ACTIVITY_NEW_TASK标志。
2.3.2.2 startActivityUncheckedLocked分析之二
一堆复杂的逻辑处理,无非就是找到一个合适的Task,然后对应做一些处理。
2.3.2.3 startActivityUncheckLocked分析之三
第三阶段工作也比较复杂,它将创建一个新的TaskRecord,并调用startActivityLocked函数进行处理。
2.3.2.4 startActivityLocked函数分析
这部分内容和Activity之间的切换动画有关(通过这些动画,使切换过程看起来更加平滑和美观,需和WMS交互)。
有两个非常重要的关键点:
· 如果mResumedActivity不为空,则需要先暂停(pause)这个Activity。由代码中的注释可知,mResumedActivity代表上一次启动的(即当前正显示的)Activity。现在要启动一个新的Activity,须先停止当前Activity,这部分工作由startPausingLocked函数完成。
· 那么,mResumedActivity什么时候为空呢?当然是在启动全系统第一个Activity时,即启动Home界面的时候。除此之外,该值都不会为空。
2.3.3.1 startSpecificActivityLocked分析
从AMS中查询是否已经存在满足要求的进程(根据processName和uid来查找).如果该进程存在并已经向AMS注册(例如之前在该进程中启动了其他Activity),通知该进程中的启动目标Activity; 如果该进程不存在,则需要调用AMS的startProcessLocked创建一个应用进程,
2.3.3.2 startProcessLocked分析
FLAG_FROM_BACKGROUND相关知识点如下:
· FLAG_FROM_BACKGROUND标识发起这次启动的Task属于后台任务。很显然,手机中没有界面供用户操作位于后台Task中的Activity。如果没有设置该标志,那么这次启动请求就是由前台Task因某种原因而触发的(例如用户单击某个按钮)。
· 如果一个应用进程在1分钟内连续崩溃超过2次,则AMS会将其ProcessRecord加入所谓的mBadProcesses中。一个应用崩溃后,系统会弹出一个警告框以提醒用户。但是,如果一个后台Task启动了一个“BadProcess”,然后该Process崩溃,结果弹出一个警告框,那么用户就会觉得很奇怪:“为什么突然弹出一个框?”因此,此处将禁止后台Task启动“Bad Process”。如果用户主动选择启动(例如单击一个按钮),则不能禁止该操作,并且要把应用进程从mBadProcesses中移除,以给它们“重新做人”的机会。当然,要是该应用每次启动时都会崩溃,而且用户不停地去启动,那该用户可能是位测试工作者。
startProcessLocked通过发送消息给Zygote以派生一个应用进程,读者仔细研究所发消息的内容,大概会发现此处并未设置和Activity相关的信息,也就是说,该进程启动后,将完全不知道自己要干什么,怎么办?下面就此进行分析。
应用进程的入口是ActivityThread的main函数,它是在主线程中执行的. 在main函数内部将创建一个消息循环Loop,接着调用ActivityThread的attach函数,最终将主线程加入消息循环。
我们在分析AMS的setSystemProcess时曾分析过ActivityThread的attach函数,那时传入的参数值为true。现在来看设置其为false的情况.
本节从应用进程的入口函数main开始,分析了应用进程和AMS之间的两次重要交互,它们分别是:
· 在应用进程启动后,需要尽快调用AMS的attachApplication函数,该函数是这个刚呱呱坠地的应用进程第一次和AMS交互。此时的它还默默“无名”,连一个确定的进程名都没有。不过没关系,attachApplication函数将根据创建该应用进程之前所保存的ProcessRecord为其准备一切“手续”。
· attachApplication准备好一切后,将调用应用进程的bindApplication函数,在该函数内部将发消息给主线程,最终该消息由handleBindApplication处理。handleBindApplication将为该进程设置进程名,初始化一些策略和参数信息等。另外,它还创建一个Application对象。同时,如果该Application声明了ContentProvider,还需要为该进程安装ContentProvider。
我们知道,AMS创建一个应用进程后,会设置一个超时时间(一般是10秒)。如果超过这个时间,应用进程还没有和AMS交互,则断定该进程创建失败。所以,应用进程启动后,需要尽快和AMS交互,即调用AMS的attachApplication函数。
第一阶段的工作比较简单:
· 设置代表该应用进程的ProcessRecrod对象的一些成员变量,例如用于和应用进程交互的thread对象、进程调度优先级及oom_adj的值等。
· 从消息队列中撤销PROC_START_TIMEOUT_MSG。
至此,该进程启动成功,但是这一阶段的工作仅针对进程本身(如设置调度优先级,oom_adj等),还没有涉及和Activity启动相关的内容,这部分工作将在第二阶段完成。
第二阶段的工作主要是为调用ApplicationThread的bindApplication做准备,将在后面的章节中分析该函数的具体内容。bindApplication的功能就是创建并初始化位于该进程中的Android运行环境.
第三阶段的工作就是通知应用进程启动Activity和Service等组件,其中用于启动Activity的函数是ActivityStack realStartActivityLocked。
此处先来分析应用进程的bindApplication,该函数将为应用进程绑定一个Application。
由以上代码可知,bindApplication函数将设置一些初始化参数,其中最重要的有:
· 创建一个Application对象,该对象是本进程中运行的第一个Application。
· 如果该Application有ContentProvider,则应安装它们。
提示从以上代码可知,ContentProvider的创建就在bindApplication函数中,其时机早于其他组件的创建。
有两个关键函数,分别是:scheduleLaunchActivity和completeResumeLocked。其中,scheduleLaunchActivity用于和应用进程交互,通知它启动目标Activity。而completeResumeLocked将继续AMS的处理流程。
保存AMS发送过来的参数信息, //向主线程发送消息,该消息的处理在handleLaunchActivity中进行.
handleLaunchActivity的工作包括:
· 首先调用performLaunchActivity,该在函数内部通过Java反射机制创建目标Activity,然后调用它的onCreate及onStart函数。
· 调用handleResumeActivity,会在其内部调用目标Activity的onResume函数。
handleResumeActivity还完成了一件很重要的事情:
根据第2章对MessageQueue的分析,当消息队列中没有其他要处理的消息时,将处理以上代码中通过addIdleHandler添加的Idler对象,也就是说,Idler对象的优先级最低,这是不是说它的工作不重要呢?非也。至少在handleResumeActivity函数中添加的这个Idler并不不简单.
Idler将为那些已经完成onResume的Activity调用AMS的activityIdle函数。该函数是Activity成功创建并启动的流程中与AMS交互的最后一步。虽然对应用进程来说,Idler处理的优先级最低,但AMS似乎不这么认为,因为它还设置了超时等待,以处理应用进程没有及时调用activityIdle的情况。
AMS给了应用进程10秒的时间,希望它在10秒内调用activityIdle函数。这个时间不算长,和前面AMS等待应用进程启动的超时时间一样。
在activityIdleInternal中有一个非常重要的关键点,即处理那些因为本次Activity启动而被暂停的Activity。有两种情况需考虑:
· 如果被暂停的Activity处于finishing状态(例如Activity在其onStop中调用了finish函数),则调用finishCurrentActivityLocked。
· 否则,要调用stopActivityLocked处理暂停的Activity。
此处涉及除AMS和目标进程外的第三个进程,即被切换到后台的那个进程。不过至此,我们的目标Activity终于正式登上了历史舞台。
在我们分析startPausingLocked分支。根据前面的介绍,当启动一个新Activity时,系统将先行处理当前的Activity,即调用startPausingLocked函数来暂停当前Activity。
startPausingLocked将调用应用进程的schedulePauseActivity函数,并设置500毫秒的超时时间,所以应用进程需尽快完成相关处理。和scheduleLaunchActivity一样,schedulePauseActivity将向ActivityThread主线程发送PAUSE_ACTIVITY消息,最终该消息由handlePauseActivity来处理。
最后还是通过resumeTopActivityLocked来启动目标Activity。当然,由于之前已经设置了mPausingActivity为null,所以最终会走到③的分支。
根据前面的介绍,此次目标Activity将走完onCreate、onStart和onResume流程,但是被暂停的Activity才刚走完onPause流程,那么它的onStop什么时候调用呢?
答案就在activityIdelInternal中,它将为mStoppingActivities中的成员调用stopActivityLocked函数。