Activity的启动过程涉及到的逻辑非常庞大,很难做到单点突破,建议读者在读本文之前,把Activity的四种启动模式、ActivityManagerService的启动过程、Activity的管理方式都再读一遍,储存一些背景知识。也是由于内容太多,笔者把启动过程分为上、下两部分介绍,另外一部分在Activity的启动过程(下)一文中。
为了尽量能够把Activity启动的主线流程呈现出来,本文选择HomeActivity的启动过程进行分析,按照启动过程中函数的调用时序,以每个关键函数为段落进行分析。
之所以要挑选HomeActivity,是因为它是起机后第一个要启动的Activity,很多状态都处于初始条件,能够简化分析过程。
之所以要以函数为段落进行分析,是因为启动过程本来就是一个时序,哪个时间点,进入哪个函数,完成哪些事,是本文想要分析重点。
通过阅读本文,能够深入理解Activity启动过程中的关键节点以及关键事件。但不得不说,Activity启动的场景非常多,远非本文的篇幅能够涵盖,读者应该结合自身的工作环境,来探究在不同场景下Activity启动所经过的路径。
概述
HomeActivity就是桌面的主屏,其Category为CATEGORY_HOME。在ActivityManagerService的启动过程一文中,我们分析过在ActivityManagerService准备就绪的最后,即systemReady()函数的最后部分,就会启动桌面,实际上就是要求启动HomeActivity。
本章分析的对象就是HomeActivity的启动过程,先上一个最上层的时序图:
我们先树立个顶层的概念:Activity的调度由AMS完成,AMS以栈式结构管理者所有的Activity,直接管理Activity的栈称为TaskRecord,后文中会用“任务”来表述这个栈;管理任务的栈称为ActivityStack;在ActivityStack之上还有一个管理者ActivityStackSupervisor。Activity的启动过程就是AMS对栈结构进行调整的过程,Activity的各级管理者都会参与进来,各司其职。
阅读过早期Android源码的读者可能知道,Android最开始对Activity调度的设计并没有像现在这么复杂,例如ActivityStackSupervisor是到支持多屏显示才出现的,还有诸如多窗口的实现,也是直到Android 6.0才出现的。Android不断在更迭,保持自身活力,适应新的需求。
1. AMS.startHomeActivityLocked()
这是启动HomeActivity的第一个函数,这时候,系统进程(system_server)才刚刚启动完毕(systemReady),第一个要启动的Activity就是HomeActivity,意味着要让用户看到手机的主屏,又称之为桌面。
以上函数的主体逻辑是:
-
通过PackageManager解析出可以处理HomeIntent的Activity,这里的HomeIntent其实就是一个设置了Category为CATEGORY_HOME的Intent;
-
如果找到,则需要通过Intent启动目标Activity。这时候,需要把包名信息设置到Intent中。启动Activity之前,会从解析到ActivityInfo中获取Activity所属的进程信息,在第一次启动HomeActivity时,宿主进程显然还没有创建,所以,接下来的逻辑转到了ActivityStackSupervisor(下文简称ASS)中,这会经过一个无比的漫长的旅途。
2. ASS.startHomeActivity()
ASS接收Activity启动的控制权后,就开始彰显其精密的调度手段了。
到这一步,HomeActivity的静态信息(Intent, ActivityInfo)已经准备好了,接下来,就是要把Activity的静态信息注入到一个动态的运行环境中。
以上函数有牵扯到3个函数调用:
-
moveHomeStackTaskToTop(): 目的是为了把HomeActivity挪到任务顶;HomeStack就是HomeActivity所在的任务;
-
startActivityLocked():这会进入启动Activity的漫长过程;注意这里给的参数大部分为null或者0,表示HomeActivity是系统中启动的第一个Activity,它由系统进程启动;如果是由普通的应用进程启动一个Activity,那这些参数都应该赋值为当前Activity的唤起者信息;
-
resumeTopActivityLocked():在ASS.inResumeTopActivity这个变量的控制下调用,因为resumeTopActivityLocked()可能被多次调用,所以通过该变量来避免重复执行函数逻辑。对于本例中第一次进入,ASS.inResumeTopActivity为false,所以并不会触发调度resumeTopActivityLocked()函数,在后文,我们会再次见到这个函数,届时再深入分析。
在HomeActivity启动之前,就需要将,来看moveHomeStackTaskToTop()这个函数的具体实现:
该函数仅仅是做一些上层的调用,牵扯到两个概念:HomeStack和HomeTask。 HomeTask就是HomeActivity的宿主任务,HomeStack就是HomeTask所在的栈。该函数的目的就是为了将HomeTask挪到HomeStack的栈顶位置,为后面的HomeActivity启动做准备。
然而,这里有一个值得读者思考的地方,HomeStack是在什么时候创建的?此刻HomeStack里面有些什么内容呢?
其实,早在系统进程启动时候,就已经创建了HomeStack对象:
SystemServer.startOtherService()
└── AMS.setWindowManager()
└── ASS.setWindowManager()
└── ASS.createStackOnDisplay(HOME_STACK_ID, Display.DEFAULT_DISPLAY);
- SystemServer中启动时会创建WindowManagerService(后文简称WMS);
- AMS的很多调度逻辑都需要与WMS进行交互,所以需要将WMS对象设置到AMS中;
- ASS也需要WMS对象,所以进一步将WMS设置到ASS中;
- ASS在获取到WMS对象之后,就可以创建ActivityStack了,因为ActivityStack需要绑定到显示设备上。
每一个ActivityStack都有一个编号,HomeStack的编号是HOME_STACK_ID(0),它是绑定到默认的显示设备上的,即手机屏幕。
下面的函数调用本意是将HomeStack中,类型为HOME_ACTIVITY_TYPE(1)的任务挪到HomeStack的栈顶位置:
ActivityStack中用TaskHistory数组记录了所有的任务,要将HomeTask挪到栈顶的位置,就是先找到HomeTask,然后将其删除,再在栈顶的位置添加一个新的HomeTask。
这里还调用了updateTaskMovement()函数,这个函数在后文还会见到很多次,其实并没有什么特别的地方,只是针对Persistable的任务,记录一个时间,这个时间在恢复任务的时候会用到。
实际上,此时的HomeStack,栈中什么都没有,TaskHistory数组的长度为0,HomeTask这个时候还没创建呢,所以,以上分析的函数逻辑并不会执行。正是由于HomeStack中还什么都没有,接下来的getHomeActivity()也会返回null,也就不会调用AMS.setFocusedActivityLocked(),但该函数非常重要,会在后文中被调用的。
该函数的逻辑其实是为其他Activity切换到HomeActivity设计的,第一次启动HomeActivity的时候,大部分逻辑都不会走到。接下来,才是Activitiy启动的重头戏。
至此,HomeActivity启动过程的第一个大部分已经分析完毕,完成两件大事:其一是构建HomeIntent,其二是调度HomeStack。
3. ASS.startActivityLocked()
Activity启动的旅途由此就开启了,这个过程无比的繁杂,涉及到的函数逻辑都很庞大。每一个Activity的启动都会经过这个函数调用,由于Activity本身的启动模式、宿主任务的状态、宿主进程的状态都会影响到Activity的启动过程,所以中间的判断分支非常多,就本例中的HomeActivity启动而言,相比一般的Activity启动会简单很多,是一个比较好的入门案例。
先来介绍一下函数入参的意义:
函数入参 |
描述 |
caller |
调用者进程的操作接口。本例中,该参数被设置为null。 |
intent |
启动当前Activity的Intent。 |
revolvedType |
Intent的解析类型。本例中为null。 |
aInfo |
Activity的静态信息类,解析AndroidManifest.xml的<activity>得到的数据结构。 |
voiceSession,voiceInteractor |
语音相关的接口。本例中均为null。 |
resultTo |
调用者Activity。本例为null,但通常情况下,该参数都不为null,因为一个Activity通常由另外一个Activity启动。HomeActivity相当于一个初始的Activity。 |
resultWho |
描述调用者Activity的字符串。与resultTo是配套存在的,本例为null。 |
requestCode |
请求码。当调用者Activity需要获取待启动Activity的数据时,可以设置该参数来启动Activity,通过Activity.startActivityForResult()便可传入requestCode参数,取一个大于或等于0的整数即可。默认情况下为”-1”,启动HomeActivity时,该参数为”0”。 |
callingPid,callingUid |
调用者的进程号和用户号。本例中均为0,表示从系统进程中启动Activity |
callingPackage |
调用者包名。本例中为null,表示系统进程。 |
realCallingPid,realCallingUid |
|
startFlags |
启动Activity的Flag |
options |
|
ignoreTargetSecurity |
|
componentSpecified |
|
outActivity |
作为该函数的输出参数,记录启动的ActivityRecord |
container |
|
inTask |
指定待启动Activity所在的任务。本例为null |
如此多的函数入参,意味着该函数的执行场景非常多。从一个Activity启动另外一个Activity,总要考虑前一个Activity的状态,陷入复杂的数据传递过程,好在HomeActivity的启动过程场景相对简单,很多参数都置为null。
有了输入,我们再提前来看函数的返回值是些什么取值:
返回值 |
描述 |
START_SUCCESS(0) |
启动成功 |
START_INTENT_NOT_RESOLVED(-1) |
无法解析Intent所请求的Activity |
START_CLASS_NOT_FOUND(-2) |
无法找到目标Activity的类 |
START_FORWARD_AND_REQUEST_CONFLICT(-3) |
带RequestCode启动Activity时,存在的一种与resultTo冲突的场景 |
START_PERMISSION_DENIED(-4) |
调用者没有获取启动Activity的授权 |
START_NOT_VOICE_COMPATIBLE(-7) |
当前并不支持语音 |
START_NOT_CURRENT_USER_ACTIVITY(-8) |
目标Activity并非对当前用户可见 |
START_SWITCHES_CANCELED(4) |
App Switch功能关闭时,需要将待启动的Activity推入Pending状态 |
总结一下,小于零的返回值,表示启动失败;大于等于零的返回值,表示启动成功,通过一些大于零的整数来区分不同的启动场景。还有一些返回值没有在这里列出,因为还依赖于后续函数调用的返回值。
下面,我们就一层层深入函数,分析代码逻辑了。
以上大片函数逻辑,关键节点在在于创建一个ActivityRecord:
-
在创建之前要进行参数设置(sourceRecord和resultRecord等)、常规检查(譬如调用者的包名是否存在)、权限检查;
-
在创建ActivityRecord之后,也有一些处理,包括AppSwitch,优先启动之前被阻塞的Activity,然后,进入了Activity过程的下一阶段: startActivityUncheckedLocked(),从函数命名可以看出,该做的检查已经做完了,剩下的函数调用就不需要进行额外的检查了(Unchecked)。
启动Activity过程的第一个大函数分析下来,想必读者内心是崩溃的,如此复杂的判定,竟然还只是创建了一个ActivityRecord!接下来的分析,只会更加崩溃,越往后的处理逻辑就越复杂。读者在后文中如果有阅读障碍,一定要再回过头来看前面的函数分析,多次重复阅读,知识是螺旋式积累的。
4. ASS.startActivityUncheckedLocked()
先描述一下几个新出来的函数入参:
函数入参 |
描述 |
r |
待启动的ActivityRecord,这是在上一个函数中刚刚新建的一个ActivityRecord对象。 |
sourceRecord |
调用者,即当前还处于显示状态的ActivityRecord。本例中由于是启动HomeActivity,所以该参数为null。 |
doResume |
表示是否要将Activity推入Resume状态,从上一个函数传入进来的参数值为true。其实,该参数为flase的情况我们也已经见过了:上个函数中,在调用ASS.startActivityUncheckedLocked()之前,会调用doPendingActivityLaunchesLocked(false),表示要优先处理一些等待被启动的Activity,在函数内部也会调用startActivityUncheckedLocked(),给定doResume的值就是false。 |
上面的函数还有一些返回值没有列出,有一部分返回值是由该函数提供的:
返回值 |
描述 |
START_CLASS_NOT_FOUND(-2) |
无法找到目标Activity的类 |
START_SUCCESS(0) |
启动成功 |
START_RETURN_INTENT_TO_CALLER(1) |
当启动参数中带有START_FLAG_ONLY_IF_NEEDED标志时,如果目标Activity就是当前的调用者,则返回该值,启动结束 |
START_TASK_TO_FRONT(2) |
在已有任务中找到了目标Activity,则只需要把目标Activity挪到前台即可,启动结束 |
START_DELIVERED_TO_TOP(3) |
目标Activity位于任务栈顶,则只需要将Intent派送到栈顶的Activity即可,启动结束 |
START_RETURN_LOCK_TASK_MODE_VIOLATION(5) |
目标Activity的宿主任务处于LockTaskMode模式,且目标Activity的启动方式违背了LockTaskMode的规则,则不能启动目标Activity |
该函数逻辑庞大,笔者将其分为3个代码片段。在分析过程中,笔者会适当的省略一些不影响主干逻辑的代码。
【代码片段1】主要是Activity各种启动参数的组合修正,读者最好借助于SDK文档理解这些参数的意义。
如果暂时不理解,也可以先忽略这些细节。对于HomeActivity的启动而言,不会涉及到上述的代码逻辑。
当启动参数修正后,就需要启动Activity了:
【代码片段2】主要处理在一个新的任务中启动Activity,而且目标Activity存在的情况,这意味着Activity之前已经启动过,只需要把其找出来,重新挪到前台显示即可。目标Activity所在的宿主栈之前可能在前台(Foreground)也可能在后台(Background),如果在后台,则需要将宿主栈挪动前台。
对于HomeActivity的启动而言,显然不需要经过寻找和处理目标Activity的过程,因为开机时,HomeActivity之前还从未被启动过。
【代码片段3】主要处理目标Activity不存在的场景,即该Activity没有被启动过,或者启动过后已经销毁的场景。既然Activity没有被启动过,那就需要在宿主栈继续启动一个新的Activity,转入ActivityStack.startActivityLocked()进行处理。
启动Activity的第二个大函数startstartActivityUncheckedLocked()比前文分析的第一个大函数startstartActivityLocked()多了Unchecked关键字,意图很明显:在调用Unchecked这个函数的时候,不会再进行检查(常规包名检查、权限检查),它关注的核心逻辑是为待启动的Activity找到宿主任务,Activity最终属于哪个任务中,有很多因素影响,譬如launchMode,launchFlags,当前任务的状态等,因此,要厘清楚该函数的各种分支判断,还需要结合实际Activity启动的动态场景,仅这么静态分析代码逻辑,是很难理解作者的编码意图的。建议读者设计一些Activity启动的场景,边断点调试,边理解代码逻辑。
分析完了ASS.startActivityUncheckedLocked()的函数逻辑,我们一起来回顾一下是如何走到这一步的:
-
1 AMS.startHomeActivityLocked():在系统进程执行systemReady()的最后,会调用该函数,这个函数的功能就是为了解析出HomeActivity的信息,为后面的启动工作做准备;在这之后,Activity启动的控制权交到ASS,它主要的职能是管理多个ActivityStack,所以扮演的是“承上启下”的角色,所谓“承上”,就是应对Activity启动的请求,完成一些Activity启动的检查、参数的设置以及栈调度的工作;所谓“启下”,就是指ASS只是为Activity启动准备必要的环境,譬如为目标Activity找到宿主任务。Activity启动时的调度还是交由后面接续完成。ActivityStackSupvisor作为ActivityStack的最高管理者,后面Activity的调度,还会求助于它;
-
2 startHomeActivity(): 为了要显示Activity,就需要将其所在的ActivityStack挪到栈顶,即HomeStack的栈顶。需要注意的是,HomeStack早在系统进程启动时,就已经构建完毕,但HomeStack栈中还没有任何元素;
-
3 startActivityLocked(): 启动Activity时,会有很多参数需要设置和调整,还会有一些常规检查,譬如调用者包名是否合法,一些基本的权限检查也是在这时候完成的。如果出现检查出错,则通过返回错误码中断启动过程。待启动的ActivityRecord就是在这一步被构建出来的;
-
4 startActivityUncheckedLocked(): 检查完成之后,就要着手Activity的启动了,这时候需要对Activity的启动参数进行判定和调整,因为启动参数决定了目标Activity的存在于哪。如果能够在当前已有任务中找到目标Activity,那就看情况是否将其挪到前台,这时候就可以通过大于零的返回值完成启动过程了;如果当前未找到目标Activity,那就在宿主任务中启动一个新的Activity。
至此,HomeActivity启动的第二个大部分已经分析完毕,主要干了两件大事:其一是构建目标ActivityRecord,其二是寻找宿主TaskRecord。
5. AS.startActivityLocked()
对于HomeActivity初次启动而言,之前没有Activity被创建,所以属于目标Activity还不存在的这一类场景,所以会进入该函数调用,意味着要在目标栈中启动一个新的Activity。
按照惯例,说明一下函数入参:
函数入参 |
描述 |
r |
待启动的ActivityRecord。 |
newTask |
是否需要在新的任务中启动Activity。对于初次启动HomeActivity而言,该值为true。 |
doResume |
是否需要立即显示Activity。有些需要Pending的Activity,该值为false;对于初次启动HomeActivity而言,该值为true。 |
keeyCurTransition |
与Activity启动动画相关的参数,由上面的函数计算得到。 |
该函数返回void,代码逻辑如下:
该函数会经过以下3大块逻辑:
-
将ActivityRecord放到宿主任务的栈顶位置。注意,这里有两个栈顶位置:一个是ActivityRecord在TaskRecord中的位置,另一个是TaskRecord在ActivityStack中的位置。前一个是该函数保证的,但后一个,还得根据实际的情况决定。如果是在一个新的任务中启动Activity,即newTask为true,那么,这个新的TaskRecord就会插入ActivityStack的栈顶,初次启动HomeActivity属于此类情况;
-
将待启动的ActivityRecord绑定到了一个窗口。与此同时,Activity启动的动画也在此设置。注意,本函数的分析中,省略了部分与WMS交互的窗口操作逻辑;
-
判断doResume的值,决定是否要将ActivityRecord迁移到显示状态(Resumed)。这个功能是交由resumeTopActivitiesLocked()实现的。
6. ASS.resumeTopActivitiesLocked()
初次看到这个函数名,读者心中可能会有疑问?默认都是显示一个Activity,该函数名中为什么会有Activities这种复数出现呢?难道有很多Activity都要被显示吗?
在Android四大组件之Activity–管理方式一文中我们指出,ASS的职能就是管理多个ActivityStack,然而,为什么会有多个ActivityStack的这种设计呢?其实,在低版本的Android中,只有一个ActivityStack,它对应到手机屏幕;到了高版本的Android,支持多屏显示了,譬如可以同时在手机和电视上显示不同的Activity。这时候就有了多个ActivityStack的概念,于是乎,多个ActivityStack的管理者ASS就应运而生了。
ASS中还有很多其他类似的函数,实现逻辑都比较简单,仅仅是完成一些多ActivityStack的管理功能,真正的复杂的调度逻辑还是在ActivityStack中
该函数的代码实现如下:
该函数的逻辑可以一句话说明白,就是将所有ActivityStack栈顶的ActivityRecord迁移到显示状态,都是通过调用AS.resumeTopActivityLocked()来完成实际的操作,细细品味,调用参数的传入略有区别:
- 宿主ActivityStack,传入的参数是待启动的ActivityRecord;
- 其他ActivityStack,传入的参数的是null
在接下来的分析中,我们会解释这种区别的含义是什么。
7. AS.resumeTopActivityInnerLocked()
上一个函数中,调用的是AS.resumeTopActivityLocked(),其职能很简单,就是为了避免递归调用,本文省略之,直接进入AS.resumeTopActivityInnerLocked()进行分析。
函数入参 |
描述 |
prev |
当前正在显示的ActivityRecord |
options |
|
该函数返回true表示XXXX;返回false表示并没有将待显示的ActivityRecord推进到Resumed状态。
不知各位读者有没有发现,在上一个函数发起调用时,传入的参数名称是target,表示待启动的ActivityRecord;但这里摇身一变,参数名称却是prev,表示之前启动的ActivityRecord,即将要进入Pausing状态的那个Activity,到底意欲几何?
问题接踵而来,只有回到代码中,才能找到问题的答案:
【代码片段1】主要是一些“路障”,即便将待显示ActivityRecord已经位于栈顶,但要真正将其显示出来,即迁移到Resumed状态,这一路有很多障碍,或者说还有很多前提条件需要满足,譬如,系统要休眠时,当前启动过程要中断;当前有Activity正处于Pausing状态时,也需要等其执行完毕。
【代码片段2】 首先要将当前正在显示的Activity迁移到Pausing状态,然后要将待显示的Activity迁移到Resumed状态,然而,这时候待显示Activity的宿主进程可能还未启动,这就需要调用ASS.startSpecificActivityLocked()。
至此,HomeActivity启动的第三大部分分析完毕,完成两件大事:其一是将ActivityRecord挪到栈顶位置;其二是尝试将栈顶的ActivityRecord迁移到显示状态(Resumed)
8. ASS.startSpecificActivityLocked()
第一次启动HomeActivity时,由于桌面进程还未启动,所以便进入了该函数。
多了一个Specific关键字,表明Activity的启动操作到这一步已经明确下来,XXX
9. AMS.startProcessLocked(String processName…)
在AMS中有多个重载的startProcessLocked()函数,我们先来看其中一个,根据processName来启动进程,这时候可能processName对应的进程还没创建。
该函数的逻辑有两个关键点:
-
对于启动后台进程的处理。进程有isolated的概念,意味着这是一个隔离的进程。对于隔离的进程而言,每次启动都是独立的,不能复用已有的进程信息。如果要启动一个非隔离的进程,那么就需要区分进程是在前台启动还是后台启动,这是用户体验相关的设计。
在AMS中维护了一个badProcesses的结构体,用于保存一些“坏进程”,什么才是“坏进程”呢?如果一个进程在一分钟内连续崩溃两次,那就变成了一个“坏进程”。对于后台启动的进程而言(即启动参数中带有FLAG_FROM_BACKGROUND标识),如果进程崩溃了,会造成用户使用的困惑,因为进程崩溃时,会弹出一个对话框,而后台启动的进程是没有任何操作界面的,这时候弹一个框,用户会觉得自己什么都没干,却弹出了一个对话框。所以,后台启动一个“坏进程”时,会直接退出。
当进程是在前台启动时,即便是一个“坏进程”,那也应该宽恕这个进程以前的不良记录,因为这通常是用户通过界面主动要唤起的进程。本着用户是上帝的原则,还是得让用户达到启动进程的目的,即便这个进程可能再次崩溃。
-
创建一个新的ProcessRecord。当进程还未启动时,变量app为null,这时候会调用AMS.newProcessRecord来创建一个新的ProcessRecord,然后再调用另外一个重载的AMS.startProcessLocked()函数,来创建一个进程。
10. AMS.newProcessRecordLocked()
对于第一次启动HomeActivity而言,Home进程还不存在,所以会顺利进入到这个函数中。
函数逻辑简单明了,根据ApplicaitonInfo创建一个ProcessRecord,并将ProcessRecord加入AMS的管理范围,与此同时,与进程相关的电量统计也是在这一步被绑定上的。
11. AMS.startProcessLocked(ProcessRecord app…)
进入该函数,意味着进程的ProcessRecord已经构建好了,但进程真正的运行环境还没有准备好啊,所以该函数的职能就是为了构建应用程序真正的进程环境。
略去了一大堆进程启动参数设置相关的代码后,终于可以创建一个进程了,Process.start()的具体实现我们不在此处展开,各位读者可以参考源码。我们知道应用进程都是从Zygote进程fork出来的,这正是Process.start()函数的职能,它会给二进制程序zygote准备一些参数,然后通过系统调用fork()出一个子进程,这就是要启动的应用进程。
分析完AMS.startProcessLocked(ProcessRecord app…)函数,我们来总结一下是如何走到这一步的:
HomeActivity经过了前面4个函数,得到了结论是:HomeActivity是一个新启动的Activity,当前栈中并不存在。接下来的逻辑,就是试图要显示HomeActivity:
-
5 AS.startActivityLocked():将待显示的Activity挪到到栈顶位置,意味着待显示的ActivityRecord是TaskRecord的栈顶,并且TaskRecord是当前ActivityStack的栈顶。这里还会设置Activity的启动动画
-
6 ASS.resumeTopActivitiesLocked():ActivityRecord已经在栈顶后,就需要将其迁移到显示状态(Resumed),由于Android支持多屏幕,所有需要对多个AS的栈顶都进行这个操作,ASS作为多个AS的管理者,很自然的就承担的这个工作。
-
7 AS.resumeTopActivityInnerLocked():收到上级的指示后,AS开始将栈顶的ActivityRecord推进到显示状态。首先,需要将当前正在显示的Activity推进到Pausing状态。对于HomeActivity而言,它就是第一个要启动的Activity,所以当前并没有正在显示的。
-
8 ASS.startSpecificActivityLocked():启动Activity带上Specific关键字,表示已经所有的障碍已经排除,明确(Specific)要启动目标Activity了。然而,这时候可能宿主进程还没创建呢,HomeActivity就属于这种情况。
-
9 AMS.startProcessLocked(String processName…):该函数的主要目的是为了准备一个ProcessRecord对象,如果AMS中还没有待启动Activity所在宿主进程的ProcessRecord对象,则需要新建一个。此外,对于后台启动的进程,有一个BadProcesses相关的处理逻辑。
-
10 AMS.newProcessRecordLocked():新建一个ProcessRecord对象。在Android四大组件之Activity–管理方式一文中,介绍了ProcessRecord这个数据结构,它是用来维护进程的运行时的状态信息。AMS统一管理所有应用进程和系统进程的ProcessRecord。
-
11 AMS.startProcessLocked(ProcessRecord app…):目的是为了启动ProcessRecord所关联的进程,在准备进程启动的参数之后,就会通过Zygote孵化出一个子进程,这就是Activity的宿主应用进程。
至此,HomeActivity启动的第四大部分分析完毕,干了一件大事:创建待启动Activity的宿主应用进程。
在介绍完HomeActivity启动路径上的11个函数之后,终于把一个应用进程给启动了。想必读者内心一定是百感交集,坦白说,从代码设计的角度,这些函数无论从实现上还是命名上,笔者觉得并不优美,太多的分支条件交织在一起,往往一个函数看下来就已经摸不着头脑了。在理解这一部分代码时,一定要把握好整体与局部的关系:整体路径是Activity启动过程,而局部有很多条件约束,一些约束条件是由Activity的管理方式导致的,一些约束条件是实际的用户场景和需求导致的,读者不要陷入这些局部中,一定要在脑海中保持一条清晰的Activity启动路径。
稍微舒缓一下,再给大家带来一个噩耗,创建一个应用进程,还仅仅只是Activity启动的半途,后面的路还长着了。以上的过程,基本都在系统进程中完成,接下来的过程需要系统进程和应用进程不断进行通信,才能把Activity给真正启动起来,在Activity的启动过程(下)一文中,我们再见。
总结
本文分成四个大的部分介绍Activity的启动过程:
-
构建个HomeIntent,并将HomeTask挪到HomeStack的栈顶;涉及到AMS.startHomeActivityLocked()、ASS.startHomeActivity、 ASS.moveHomeStackTaskToTop()、AS.moveHomeStackTaskToTop()这几个函数;关键点在于HomeTask的挪动过程。
-
构建ActivityRecord,并未其寻找宿主任务TaskRecord。涉及ASS.startActivityLocked()、ASS.startActivityUncheckedLocked()两个大函数;关键点在于ActivityRecord创建前的检查,以及创建后,根据启动参数寻找宿主任务的过程。
-
挪动ActivityRecord到宿主栈顶。涉及AS.startActivityLocked()、ASS.resumeTopActivitiesLocked()、AS.resumeTopActivityInnerLocked()这几个函数;当ActivityRecord位于栈顶时,会尝试将其迁移到显示状态。
-
创建ActivityRecord所在的应用进程。涉及ASS.startSpecificActivityLocked()、AMS.startProcessLocked()、AMS.newProcessRecordLocked()这几个函数。对于HomeActivity第一次启动而言,宿主进程还未创建,所以需要先创建一个可以执行的进程环境。