6.3.2 AMS的startActivityAndWait函数分析
startActivityAndWait函数有很多参数,下面先来认识一下它们。
[—>ActivityManagerService.java:startActivityAndWait原型]
public final WaitResult startActivityAndWait(
/*
在绝大多数情况下,一个Acitivity的启动是由一个应用进程发起的,IApplicationThread是
应用进程和AMS交互的通道,也可算是调用进程的标识,在本例中,am并非一个应用进程,所以
传递的caller为null
*/
IApplicationThread caller,
//Intent及resolvedType,在本例中,resolvedType为null
Intent intent, String resolvedType,
//grantedUriPermissions和granteMode两个参数和授权有关,读者可参考第3章对Clipboard
Service分析时介绍的授权知识
Uri[]grantedUriPermissions,//在本例中为null
int grantedMode,//在本例中为0
IBinder resultTo,//在本例中为null,用于接收startActivityForResult的结果
String resultWho,//在本例中为null
//requestCode在本例中为0,该值的具体意义由调用者解释。如果该值大于等于0,则AMS内部保存该值,
//并通过onActivityResult返回给调用者
int requestCode,
boolean onlyIfNeeded,//本例为false
boolean debug,//是否调试目标进程
//下面3个参数和性能统计有关
String profileFile,
ParcelFileDescriptor profileFd, boolean autoStopProfiler)
关于以上代码中一些参数的具体作用,以后碰到时会再作分析。建议读者先阅读SDK文档中关于Activity类定义的几个函数,如startActivity、startActivityForResult及onActivity-Result等。
startActivityAndWait的代码如下:
[—>ActivityManagerService.java:startActivityAndWait]
public final WaitResult startActivityAndWait(IApplicationThread caller,
Intent intent, String resolvedType, Uri[]grantedUriPermissions,
int grantedMode, IBinder resultTo, String resultWho, int requestCode,
boolean onlyIfNeeded, boolean debug, String profileFile,
ParcelFileDescriptor profileFd, boolean autoStopProfiler){
//创建WaitResult对象用于存储处理结果
WaitResult res=new WaitResult();
//mMainStack为ActivityStack类型,调用它的startActivityMayWait函数
mMainStack.startActivityMayWait(caller,-1,intent, resolvedType,
grantedUriPermissions, grantedMode, resultTo, resultWho,
requestCode, onlyIfNeeded, debug, profileFile, profileFd,
autoStopProfiler, res, null);//最后一个参数为Configuration,
//在本例中为null
return res;
}
mMainStack为AMS的成员变量,类型为ActivityStack,该类是Activity调度的核心角色。正式分析它之前,有必要先介绍一下相关的基础知识。
1.Task、Back Stack、ActivityStack及Launch mode
(1)关于Task及Back Stack的介绍
先来看图6-10所示,其中列出了用户在Android系统上想干的三件事情,分别用A、B、C表示,将每一件事情称为一个Task。一个Task还可细分为多个子步骤,即Activity。
图 6-10 用户想干的事
提示 为什么叫Activity?读者可参考Merrian-Webster词典对Activity的解释[1]:“an organi-zational unit for performing a specific function”,也就是说,它是一个有组织的单元,用于完成某项指定功能。
由图6-10可知,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了。另外,通过重用Activity,也可为用户提供一致的界面和体验。
了解了Android设计理念后,我们来看看Android是如何组织Task及它所包含的Activity的。此处有一个简单的例子,如图6-11所示。
由图6-11可知:
本例中的Task包含4个Activity。用户可单击按钮跳转到下一个Activity。同时,通过返回键可回到上一个Activity。
虚线下方是这些Activity的组织方式。Android采用了Stack的方法管理这3个Activity。例如在Activity 1启动Activity 2后,Activity 2入栈成为栈顶成员,Activity 1成为栈底成员,而界面显示的是栈顶成员的内容。当按返回键时,Activity 3出栈,这时候Activity 2成为栈顶,界面显示也相应变成了Activity 2。
图 6-11 Task及Back Stack示例
以上是一个Task的情况。那么,多个Task又会是何种情况呢?如图6-12所示。
图 6-12 多个Task的情况
由图6-12可知:对多Task的情况来说,系统只支持一个处于前台的Task,即用户当前看到的Activity所属的Task的,其余的Task均处于后台,这些后台Task内部的Activity保持顺序不变。用户可以一次将整个Task挪到后台或者置为前台。
提示 用过Android手机的读者应该知道,长按Home键,系统会弹出近期Task列表,使用户能快速在多个Task间切换。
以上内容从抽象角度介绍了什么是Task,以及Android如何分解Task和管理Activity,那么在实际代码中,是如何考虑并设计的呢?
(2)关于ActivityStack的介绍
通过上述分析,我们对Android的设计有了一定了解,那么如何用代码来实现这一设计呢?此处有两点需要考虑:
Task内部Activity的组织方式。由图6-11可知,Android通过先入后出的方式来组织Activity。数据结构中的Stack即以这种方式工作。
图 6-13 ActivityStack及相关成员
多个Task的组织及管理方式。
Android设计了一个ActivityStack类来负责上述工作,它的组成如图6-13所示。
由图6-13可知:
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 a single stack of activities”),但不知何故,现在的代码实现中将所有Task的ActivityRecord都放到mHistory中了,并且依然保留mMainStack。
ActivityStack中没有成员用于保存TaskRecord。
由上述内容可知,ActivityStack采用数组的方式保存所有Task的ActivityRecord,并且没有成员保存TaskRecord。这种实现方式有优点亦有缺点:
优点是少了TaskRecord一级的管理,直接以ActivityRecord为管理单元。这种做法能降低管理方面的开销。
缺点是弱化了Task的概念,结构不够清晰。
下面来看ActivityStack中几个常用的搜索ActivityRecord的函数,代码如下:
[—>ActivityStack.java:topRunningActivityLocked]
/*topRunningActivityLocked:
找到栈中第一个与notTop不同的,并且不处于finishing状态的ActivityRecord。当notTop为
null时,该函数即返回栈中第一个需要显示的ActivityRecord。提醒读者,栈的出入口只能是栈顶。
虽然mHistory是一个数组,但是查找均从数组末端开始,所以其行为也大体符合Stack的定义
*/
final ActivityRecord topRunningActivityLocked(ActivityRecord notTop){
int i=mHistory.size()-1;
while(i>=0){
ActivityRecord r=mHistory.get(i);
if(!r.finishing&&r!=notTop)return r;
i—;
}
return null;
}
类似的函数还有:
[—>ActivityStack.java:topRunningNonDelayedActivityLocked]
/*topRunningNonDelayedActivityLocked
与topRunningActivityLocked类似,但ActivityRecord要求增加一项,即delayeResume为
false
*/
final ActivityRecord topRunningNonDelayedActivityLocked(ActivityRecord notTop){
int i=mHistory.size()-1;
while(i>=0){
ActivityRecord r=mHistory.get(i);
//delayedResume变量控制是否暂缓resume Activity
if(!r.finishing&&!r.delayedResume&&r!=notTop)return r;
i—;
}
return null;
}
ActivityStack还提供findActivityLocked函数以根据Intent及ActivityInfo来查找匹配的ActivityRecord,同样,查找也是从mHistory尾端开始,相关代码如下:
[—>ActivityStack.java:findActivityLocked]
private ActivityRecord findActivityLocked(Intent intent, ActivityInfo info){
ComponentName cls=intent.getComponent();
if(info.targetActivity!=null)
cls=new ComponentName(info.packageName, info.targetActivity);
final int N=mHistory.size();
for(int i=(N-1);i>=0;i—){
ActivityRecord r=mHistory.get(i);
if(!r.finishing)
if(r.intent.getComponent().equals(cls))return r;
}
return null;
}
另一个findTaskLocked函数的返回值是ActivityRecord,其代码如下:
[ActivityStack.java:findTaskLocked]
private ActivityRecord findTaskLocked(Intent intent, ActivityInfo info){
ComponentName cls=intent.getComponent();
if(info.targetActivity!=null)
cls=new ComponentName(info.packageName, info.targetActivity);
TaskRecord cp=null;
final int N=mHistory.size();
for(int i=(N-1);i>=0;i—){
ActivityRecord r=mHistory.get(i);
//r.task!=cp,表示不搜索属于同一个Task的ActivityRecord
if(!r.finishing&&r.task!=cp
&&r.launchMode!=ActivityInfo.LAUNCH_SINGLE_INSTANCE){
cp=r.task;
//如果Task的affinity相同,则返回这条ActivityRecord
if(r.task.affinity!=null){
if(r.task.affinity.equals(info.taskAffinity))return r;
}else if(r.task.intent!=null
&&r.task.intent.getComponent().equals(cls)){
//如果Task Intent的ComponentName相同
return r;
}else if(r.task.affinityIntent!=null
&&r.task.affinityIntent.getComponent().equals(cls)){
return r;
}//if(r.task.affinity!=null)判断结束
}//if(!r.finishing&&r.task!=cp……)判断结束
}//for循环结束
return null;
}
其实,findTaskLocked是根据mHistory中ActivityRecord所属的Task的情况来进行相应的查找工作。
以上这4个函数均是ActivityStack中常用的函数,如果不需要逐项(case by case)地研究AMS,那么读者仅需了解这几个函数的作用即可。
(3)关于Launch Mode的介绍
Launch Mode用于描述Activity的启动模式,目前一共有4种模式,分别是standard、singleTop、singleTask和singleInstance。初看它们,较难理解,实际上不过是Android玩的一个“小把戏“而已。启动模式就是用于控制Activity和Task关系的。
standard:一个Task中可以有多个相同类型的Activity。注意,此处是相同类型的Activity,而不是同一个Activity对象。例如在Task中有A、B、C、D等4个Activity,如果再启动A类Activity, Task就会变成A、B、C、D、A。最后一个A和第一个A是同一类型,却并非同一对象。另外,多个Task中也可以有同类型的Activity。
singleTop:当某Task中有A、B、C、D等4个Activity时,如果D想再启动一个D类型的Activity,那么Task将是什么样子呢?在singleTop模式下,Task中仍然是A、B、C、D,只不过D的onNewIntent函数将被调用,而在standard模式下,Task将变成A、B、C、D、D,最后的D为新创建的D类型Activity对象。在singleTop模式下,只有目标Acitivity当前正好在栈顶时才有效,例如只有启动处于栈顶的D时才有用,如果启动不处于栈顶的A、B、C等,则无效。
singleTask:在这种启动模式下,该Activity只存在一个实例,并且将和一个Task绑定。当需要此Activity时,系统会以onNewIntent方式启动它,而不会新建Task和Activity。注意,该Activity虽只有一个实例,但是在Task中除了它之外,还可以有其他的Activity。
singleInstance:它是singleTask的加强版,即一个Task只能有这么一个设置了singleInstance的Activity,不能再有别的Activity。而在singleTask模式中,Task还可以有其他的Activity。
注意,Android建议一般的应用开发者不要轻易使用最后两种启动模式。因为这些模式虽然名义上为Launch Mode,但是它们也会影响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、D等4个Activity,要启动B,应直接把C、D“干掉”,而不是新建一个B。
提示 这些启动模式和标志,在笔者看来很像洗扑克牌时的手法,因此可以称之为小把戏。虽是小把戏,但是相关代码的逻辑及分支却异常繁杂,我们应从更高的角度来看待它们。
介绍完上面的知识后,下面来分析ActivityStack的startActivityMayWait函数。
2.ActivityStack的startActivityMayWait函数分析
startActivityMayWait函数的目标是启动com.dfp.test.TestActivity,假设系统之前没有启动过该Activity,本例最终的结果将是:
由于在am中设置了FLAG_ACTIVITY_NEW_TASK标志,因此除了会创建一个新的ActivityRecord外,还会新创建一个TaskRecord。
还需要启动一个新的应用进程以加载并运行com.dfp.test.TestActivity的一个实例。
如果TestActivity不是Home,还需要停止当前正在显示的Activity。
我们可将这个函数分三部分进行介绍,下面先来分析第一部分。
(1)startActivityMayWait分析之一
这部分的代码具体如下:
[—>ActivityStack.java:startActivityMayWait]
final int startActivityMayWait(IApplicationThread caller, int callingUid,
Intent intent, String resolvedType, Uri[]grantedUriPermissions,
int grantedMode, IBinder resultTo,
String resultWho, int requestCode, boolean onlyIfNeeded,
boolean debug, String profileFile, ParcelFileDescriptor profileFd,
boolean autoStopProfiler, WaitResult outResult, Configuration config){
……
//在本例中,已经指明了Component,这样可省去为Intent匹配搜索之苦
boolean componentSpecified=intent.getComponent()!=null;
//创建一个新的Intent,防止客户传入的Intent被修改
intent=new Intent(intent);
//查询满足条件的ActivityInfo,在resolveActivity内部和PKMS交互,读者不妨自己
//尝试分析该函数
ActivityInfo aInfo=resolveActivity(intent, resolvedType, debug,
profileFile, profileFd, autoStopProfiler);
synchronized(mService){
int callingPid;
if(callingUid>=0){
callingPid=-1;
}else if(caller==null){//本例中,caller为null
callingPid=Binder.getCallingPid();//取出调用进程的Pid
//取出调用进程的Uid。在本例中,调用进程是am,它由shell启动
callingUid=Binder.getCallingUid();
}else{
callingPid=callingUid=-1;
}//if(callingUid>=0)判断结束
//在本例中config为null
mConfigWillChange=config!=null
&&mService.mConfiguration.diff(config)!=0;
final long origId=Binder.clearCallingIdentity();
if(mMainStack&&aInfo!=null&&(aInfo.applicationInfo.flags&
ApplicationInfo.FLAG_CANT_SAVE_STATE)!=0){
/*
AndroidManifest.xml中的Application标签可以声明一个cantSaveState
属性,设置了该属性的Application将不享受系统提供的状态保存/恢复功能。
当一个Application退到后台时,系统会为它保存状态,当调度其到前台运行时,
将恢复它之前的状态,以保证用户体验的连续性。声明了该属性的Application被称为
“heavy weight process”。可惜系统目前不支持该属性,因为PackageParser
将不解析该属性。详情请见PackageParser.java parseApplication函数
*/
}
……//待续
startActivityMayWait第一阶段的工作内容相对较简单,主要包括以下几方面:
首先需要通过PKMS查找匹配该Intent的ActivityInfo。
处理FLAG_CANT_SAVE_STATE的情况,但系统目前不支持此情况。
另外,获取调用者的pid和uid。由于本例的caller为null,故所得到的pid和uid均为am所在进程的uid和pid。
下面介绍startActivityMayWait第二阶段的工作。
(2)startActivityMayWait分析之二
这部分的代码具体如下:
[—>ActivityStack.java:startActivityMayWait]
//调用此函数启动Activity,将返回值保存到res
int res=startActivityLocked(caller, intent, resolvedType,
grantedUriPermissions, grantedMode, aInfo,
resultTo, resultWho, requestCode, callingPid, callingUid,
onlyIfNeeded, componentSpecified, null);
//如果configuration发生变化,则调用AMS的updateConfigurationLocked
//进行处理。关于这部分内容,读者学完本章后可自行分析
if(mConfigWillChange&&mMainStack){
mService.enforceCallingPermission(
android.Manifest.permission.CHANGE_CONFIGURATION,
"updateConfiguration()");
mConfigWillChange=false;
mService.updateConfigurationLocked(config, null, false);
}
此处,启动Activity的核心函数是startActivityLocked,该函数异常复杂,后面将用一节专门分析。下面先继续分析startActivityMayWait第三阶段的工作。
(3)startActivityMayWait分析之三
这部分的代码具体如下:
[—>ActivityStack.java:startActivityMayWait]
if(outResult!=null){
outResult.result=res;//设置启动结果
if(res==IActivityManager.START_SUCCESS){
//将该结果加到mWaitingActivityLaunched中保存
mWaitingActivityLaunched.add(outResult);
do{
try{
mService.wait();//等待启动结果
}
}while(!outResult.timeout&&outResult.who==null);
}else if(res==IActivityManager.START_TASK_TO_FRONT){
……//处理START_TASK_TO_FRONT结果,读者可自行分析
}
}//if(outResult!=null)结束
return res;
}
}
第三阶段的工作就是根据返回值做一些处理,那么res返回成功(即res==IActivityManager.START_SUCCESS的时候)后为何还需要等待呢?
这是因为目标Activity要运行在一个新的应用进程中,就必须等待那个应用进程正常启动并处理相关请求。注意,只有am设置了-W选项,才会进入wait状态。
[1]http://www.merriam-webster.com/dictionary/activity中第六条解释。