Android四大组件之Activity--管理方式
1. 概览
Activity的管理有静态和动态两层涵义:
静态是指Activity的代码组织结构,即Application中声明的Activity的集合,这些Activity被组织在一个APK中,有特定的包名。 在编写应用程序时,Activity对应到用户界面,它定义了用户界面的布局、交互行为、启动方式等,最重要的,是Activity的生命周期函数。 在应用进程看来,只需要按照Android定义的规范,实现生命周期函数的具体逻辑即可,所有的用户界面都遵循同一个规范。 编写完一个应用程序的所有用户界面,就算是完成了Activity的静态管理。
动态是指Activity的运行调度方式,即Activity生命周期的执行过程中,内部数据结构的变化,Android对所有Activity进行统一管理。 在一个应用程序安装时,系统会解析出APK中所有Activity的信息,当显示APK中的用户界面时,就需要调度Activity的生命周期函数了。 系统进程(system_process)中维护了所有Activity的状态,管理中枢就是ActivityManagerService,Android为此做了精密的设计,采用 栈 作为基本的数据结构。
本文分析的Activity管理方式属于动态这个层面,也就是系统进程中针对Activity的调度机制。
2. Activity管理的基础
Activity的管理离不开基础的数据结构以及它们之间的相互关联, 所以,笔者会从基础的数据结构出发,分析类的属性和行为,并结合一些场景进行源码分析; 进一步,会分析各个类之间关联关系的构建过程。 这样一来,整个Activity管理运转的模型就清楚了,这个模型承载的很多业务,本文不会具体展开, 在Android四大组件之Activity–启动过程一文中,笔者会再详细介绍一种典型的业务。
2.1 数据结构
Activity管理相关的数据结构包括:
这些数据结构都是Java类,它们都属于系统进程的范畴,即对象的构建和销毁都在系统进程中完成,笔者将从类的属性和行为这两个角度来分析类的职能。 Android有一些约定俗成的函数命名方式,与Activity管理相关很多函数都会带有Locked后缀,表示这些函数需要进行多线程同步操作(synchronized),它们会读/写一些多线程共享的数据,读者在分析代码的时候可以适当关注。
先上一张数据结构的概览图:
图中的方框可以理解为一个中包含关系:譬如一个TaskRecord中包含多个ActivityRecord; 图中的连接线可以理解为等价关系,譬如同一个ActivityRecord会被TaskRecord和ProcessRecord引用,两者是从不同维度来管理ActivityRecord。
- ActivityRecord是Activity管理的最小单位,它对应着一个用户界面;
- TaskRecord也是一个栈式管理结构,每一个TaskRecord都可能存在一个或多个ActivityRecord,栈顶的ActivityRecord表示当前可见的界面;
- ActivityStack是一个栈式管理结构,每一个ActivityStack都可能存在一个或多个TaskRecord,栈顶的TaskRecord表示当前可见的任务;
- ActivityStackSupervisor管理着多个ActivityStack,但当前只会有一个获取焦点(Focused)的ActivityStack;
- ProcessRecord记录着属于一个进程的所有ActivityRecord,运行在不同TaskRecord中的ActivityRecord可能是属于同一个 ProcessRecord。
ActivityRecord
ActivityRecord是AMS调度Activity的基本单位,它需要记录AndroidManifest.xml中所定义Activity的静态特征,同时, 也需要记录Activity在被调度时的状态变化,因此ActivityRecord这个类的属性比较多。
属性 | 描述 |
---|---|
ActivityInfo | 从<activity>标签中解析出来的信息,包含launchMode,permission,taskAffinity等 |
mActivityType | Activity的类型有三种:APPLICATION_ACTIVITY_TYPE(应用)、HOME_ACTIVITY_TYPE(桌面)、RECENTS_ACTIVITY_TYPE(最近使用) |
appToken | 当前ActivityRecord的标识 |
packageName | 当前所属的包名,这是由<activity>静态定义的 |
processName | 当前所属的进程名,大部分情况都是由<activity>静态定义的,但也有例外 |
taskAffinity | 相同taskAffinity的Activity会被分配到同一个任务栈中 |
intent | 启动当前Activity的Intent |
launchedFromUid | 启动当前Activity的UID,即发起者的UID |
launchedFromPackage | 启动当前Activity的包名,即发起者的包名 |
resultTo | 在当前ActivityRecord看来,resultTo表示上一个启动它的ActivityRecord,当需要启动另一个ActivityRecord,会把自己作为resultTo,传递给下一个ActivityRecord |
state | ActivityRecord所处的状态,初始值是ActivityState.INITIALIZING |
app | ActivityRecord的宿主进程 |
task | ActivityRecord的宿主任务 |
inHistory | 标识当前的ActivityRecord是否已经置入任务栈中 |
frontOfTask | 标识当前的ActivityRecord是否处于任务栈的根部,即是否为进入任务栈的第一个ActivityRecord |
newIntents | Intent数组,用于暂存还没有调度到应用进程Activity的Intent |
由于ActivityRecord是一个最基本的数据结构,所以其行为相对较少,大都是一些用于判定和更新当前ActivityRecord状态的函数:
行为 | 描述 |
---|---|
putInHistory(), takeFromHistory(), isInHistory() | 基于inHistory属性,来判定和更新ActivityRecord是否在任务栈的状态值 |
isHomeActivity(), isRecentsActivity(), isApplicationActivity() | 基于mActivityType属性,判定Activity的类型 |
setTask() | 设置ActivityRecord的宿主任务 |
deliverNewIntentLocked() | 向当前ActivityRecord继续派发Intent。在一些场景下,位于任务栈顶的ActivityRecord会继续接受新的Intent(譬如以singleTop方式启动的同一个Activity),这时候,会触发调度Activity.onNewIntent()函数 |
addNewIntentLocked() | 如果Intent没有派发到应用进程,则通过该函数往newIntents数组中添加一个元素。 |
要理解ActivityRecord这个数据结构,可以从其构造函数出发,理解其属性在什么场景下会发生变化。 每次需要启动一个新的Activity时,都会构建一个ActivityRecord对象,这在ActivityStackSupervisor.startActivityLocked()函数中完成,构造一个ActivityRecord要传入的参数是相当多的:
ActivityRecord(ActivityManagerService _service, ProcessRecord _caller,
int _launchedFromUid, String _launchedFromPackage, Intent _intent, String _resolvedType,
ActivityInfo aInfo, Configuration _configuration,
ActivityRecord _resultTo, String _resultWho, int _reqCode,
boolean _componentSpecified, ActivityStackSupervisor supervisor,
ActivityContainer container, Bundle options) {
service = _service; // AMS对象
appToken = new Token(this); //appToken可以进行跨进程传递,标识一个AR对象
info = aInfo; //从AndroidManifest.xml中解析得到的Activity信息
launchedFromUid = _launchedFromUid; //譬如从浏览器启动当前AR,那么该属性记录的就是浏览器的UID
launchedFromPackage = _launchedFromPackage;
userId = UserHandle.getUserId(aInfo.applicationInfo.uid);
intent = _intent; //启动当前AR的Intent
shortComponentName = _intent.getComponent().flattenToShortString();
resolvedType = _resolvedType;
componentSpecified = _componentSpecified;
configuration = _configuration;
resultTo = _resultTo; //记录上一个AR对象
resultWho = _resultWho; //reslutTo的字符串标识
requestCode = _reqCode; //上一个AR对象设定的Request Code
state = ActivityState.INITIALIZING; //AR的状态,Activity调度时发生改变
frontOfTask = false; //是否处于Task的根部,调整任务栈中AR顺序时,可能发生改变
launchFailed = false;
stopped = false; //pause操作完成状态位
delayedResume = false;
finishing = false; //stoped到finished之间的过渡状态位
configDestroy = false;
keysPaused = false; //如果置为true,则当前AR不再接受用户输入
inHistory = false; //将AR压入任务栈后,该状态位被置为true
visible = true;
waitingVisible = false;
nowVisible = false;
idle = false;
hasBeenLaunched = false;
mStackSupervisor = supervisor;
mInitialActivityContainer = container;
if (options != null) {
pendingOptions = new ActivityOptions(options);
mLaunchTaskBehind = pendingOptions.getLaunchTaskBehind();
}
haveState = true;
if (aInfo != null) {
//根据aInfo给realActivity, taskAffinity, processName等属性赋值
...
} else {
//没有aInfo的情况下,赋予默认值
realActivity = null;
taskAffinity = null;
stateNotNeeded = false;
appInfo = null;
processName = null;
packageName = null;
fullscreen = true;
noDisplay = false;
mActivityType = APPLICATION_ACTIVITY_TYPE;
immersive = false;
}
}
TaskRecord
TaskRecord的职责是管理多个ActivityRecord,本文所述的任务、任务栈指的就是TaskRecord。 启动Activity时,需要找到Activity的宿主任务,如果不存在,则需要新建一个,也就是说所有的ActivityRecord都必须有宿主。 TaskRecord与ActivityRecord是一对多的关系,TaskRecord的属性中包含了ActivityRecord的数组; 同时,TaskRecord还需要维护任务栈本身的状态。
属性 | 描述 |
---|---|
taskid | TaskRecord的唯一标识 |
taskType | 任务栈的类型,等同于ActivityRecord的类型,是由任务栈的第一个ActivityRecord决定的 |
intent | 在当前任务栈中启动的第一个Activity的Intent将会被记录下来,后续如果有相同的Intent时,会与已有任务栈的Intent进行匹配,如果匹配上了,就不需要再新建一个TaskRecord了 |
realActivity, origActivity | 启动任务栈的Activity,这两个属性是用包名(CompentName)表示的,real和orig是为了区分Activity有无别名(alias)的情况,如果AndroidManifest.xml中定义的Activity是一个alias,则此处real表示Activity的别名,orig表示真实的Activity |
affinity | TaskRecord把Activity的affinity记录下来,后续启动Activity时,会从已有的任务栈中匹配affinity,如果匹配上了,则不需要新建TaskRecord |
rootAffinity | 记录任务栈中最底部Activity的affinity,一经设定后就不再改变 |
mActivities | 这是TaskRecord最重要的一个属性,TaskRecord是一个栈结构,栈的元素是ActivityRecord,其内部实现是一个数组mActivities |
stack | 当前TaskRecord所在的ActivityStack |
TaskRecord的行为侧重在TaskRecord本身的管理:增/删/改/查任务栈中的元素。
行为 | 描述 |
---|---|
getRootActivity(), getTopActivity() | 任务栈有根部(Root)和顶部(Top),可以通过这两个函数分别获取到根部和顶部的ActivityRecord。获取的过程就是对TaskRecord.mActivities进行遍历,如果ActivityRecord的状态不是finishing,就认为是有效的ActivityRecord |
topRunningActivityLocked() | 虽然也是从顶至底对任务栈进行遍历获取顶部的ActivityRecord,但这个函数同getTopActivity()有区别:输入参数notTop,表示在遍历的过程中需要排除notTop这个ActivityRecord; |
addActivityToTop(), addActivityAtBottom() | 将ActivityRecord添加到任务栈的顶部或底部 |
moveActivityToFrontLocked() | 该函数将一个ActivityRecord移至TaskRecord的顶部,实现方法就是先删除已有的,再在栈顶添加一个新的 |
setFrontOfTask() | ActivityRecord有一个属性是frontOfTask,表示ActivityRecord是否为TaskRecord的根Activity。该函数设置TaskRecord中所有ActivityRecord的frontOfTask属性,从栈底往上开始遍历,第一个不处于finishing状态的ActivityRecord的frontOfTask属性置成true,其他都为false |
performClearTaskLocked() | 清除TaskRecord中的ActivityRecord。当启动Activity时,用了Intent.FLAG_ACTIVITY_CLEAR_TOP参数,那么在宿主任务中,待启动ActivityRecord之上的其他ActivityRecord都会被清除 |
仅仅把类的属性和行为罗列出来,当然不足以理解TaskRecord的工作原理。 接下来,将深入部分函数的代码,分析TaskRecord在一些场景下的具体执行逻辑。
场景 1
启动一个Activity时,通常需要将ActivityRecord压入任务栈顶,addActivityToTop()就是为此功能设计:
void addActivityToTop(ActivityRecord r) {
addActivityAtIndex(mActivities.size(), r);
}
将ActivityRecord压入栈顶,其实就是在mActivities数组末尾添加一个元素,所以,实际压入栈顶的操作是由addActivityAtIndex()完成:
void addActivityAtIndex(int index, ActivityRecord r) {
// 1. 移除已有的ActivityRecord对象
if (!mActivities.remove(r) && r.fullscreen) {
numFullscreen++;
}
// 2. 根据任务栈是否为空,设置状态
if (mActivities.isEmpty()) {
taskType = r.mActivityType;
isPersistable = r.isPersistable();
mCallingUid = r.launchedFromUid;
mCallingPackage = r.launchedFromPackage;
maxRecents = Math.min(Math.max(r.info.maxRecents, 1),
ActivityManager.getMaxAppRecentsLimitStatic());
} else {
r.mActivityType = taskType;
}
// 3. 在指定的位置添加ActivityRecord
mActivities.add(index, r);
// 4. 更新任务栈关联的Intent
updateEffectiveIntent();
...
}
该函数会经过以下处理过程:
移除任务栈中已有的ActivityRecord对象,即任务栈中不会出现两个同样的ActivityRecord对象。此处需要注意,两次启动同一个Activity,是会产生两个不同的ActivityRecord对象的;
如果任务栈为空,则设置任务栈的初始状态,否则,设置ActivityRecord的类型为任务栈的类型。由此可见,同一个任务栈中,所有ActivityRecord的类型都是一样的,而且是由任务栈的第一个ActivityRecord的类型决定的;
此处的位置就是任务栈顶,也就是mActivities属性的末尾;
任务栈中元素发生了变化,所以需要更新任务栈关联的Intent,这是通过调用updateEffectiveIntent()实现的,函数的具体逻辑,在
场景 3
中再行分析。
场景 2
当待显示的Activity压入任务栈后,就需要设置栈顶ActivityRecord的状态,这时候,会调用topRunningActivityLocked()函数来获取栈顶的元素,为了更好的分析topRunningActivityLocked()的使用场景,笔者把另一个与其功能相似的函数getTopActivity()也列出来:
ActivityRecord getTopActivity() {
for (int i = mActivities.size() - 1; i >= 0; --i) {
final ActivityRecord r = mActivities.get(i);
if (r.finishing) {
continue;
}
return r;
}
return null;
}
ActivityRecord topRunningActivityLocked(ActivityRecord notTop) {
for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
ActivityRecord r = mActivities.get(activityNdx);
// 除了要求ActivityRecord不是finishing状态以外,还要求不是当前给定输入的ActivityRecord
if (!r.finishing && r != notTop && stack.okToShowLocked(r)) {
return r;
}
}
}
两者是从顶到底对任务栈进行遍历,但实现逻辑不同,topRunningActivityLocked()接受一个输入参数notTop,在寻找时,要求排除notTop指定的ActivityRecord,通常,传入的notTop都是null,隐含的意思就是栈顶的ActivityRecord还没有被销毁。从函数命名topRunning,也可以看出其与getTop的区别:getTop不管栈顶的死活,拿到就好; topRunning要求拿到的一定是活的栈顶。
另外,topRunningActivityLocked()还有一个限制条件: ActivityRecord是可以被显示的(okToShow),这是通过ActivityStack.okToShowLocked()来判定的,主要是为了应多多用户或锁屏显示的Activity,一般情况下,函数返回值都为true。
场景 3
假定在启动一个Activity时,设置了Intent的FLAG_ACTIVITY_REORDER_TO_FRONT,表示要将Activity重排序到任务栈顶。 如果目标的Activity在任务栈中已经启动过,则需要将其挪至栈顶。譬如目标任务栈从底到顶是 A - B - C
, 然后,又以FLAG_ACTIVITY_REORDER_TO_FRONT启动了 A,那最终任务栈会变化为 B - C - A
。 这就会调用到moveActivityToFrontLocked()函数:
final void moveActivityToFrontLocked(ActivityRecord newTop) {
mActivities.remove(newTop);
mActivities.add(newTop);
updateEffectiveIntent();
setFrontOfTask();
}
该函数需要调整任务栈中ActivityRecord的顺序,延用上述例子, A 将作为参数newTop。 首先,会将 A 从任务栈中移除; 然后,再将 A 添加到任务栈顶; 接着,会调用updateEffectiveIntent()函数来更新任务栈关联的Intent:
void updateEffectiveIntent() {
final int effectiveRootIndex = findEffectiveRootIndex();
final ActivityRecord r = mActivities.get(effectiveRootIndex);
setIntent(r);
}
该函数会找到一个有效的Root Index,即任务栈底部的索引,根据这个索引值取出对应的ActivityRecord。 延续上述例子,B 会被调整为任务栈的根部ActivityRecord,通过调用setIntent()来设置当前任务栈关联的Intent为启动 B 的Intent,然而,这里可不止改变TaskRecord.intent这一个属性这个简单,与Taskrecord的发起者相关的属性值都要更改, 譬如mCallingUid,mCallingPackage都得更改为 B 的发起者:
void setIntent(ActivityRecord r) {
setIntent(r.intent, r.info);
mCallingUid = r.launchedFromUid;
mCallingPackage = r.launchedFromPackage;
}
这里还有一个重载的setIntent()函数,不再展开分析了,只需要知道诸如affinity, realActivity等属性都会被重置即可。
更新完TaskRecord的Intent,再回到moveActivityToFrontLocked()函数中,需要继续更新任务栈的Front。 之前任务栈的Front是 A,在发生变化后, Front需要更新为 B,然而,TaskRecord并没有一个属性用来记录当前的Front, 它是根据任务栈中每一个ActivityRecord的frontOfTask属性来判定的:
final void setFrontOfTask() {
boolean foundFront = false;
final int numActivities = mActivities.size();
for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
// 从栈底往上遍历
final ActivityRecord r = mActivities.get(activityNdx);
if (foundFront || r.finishing) {
// 其他ActivityRecord的这个属性都置为false
r.frontOfTask = false;
} else {
// 不为finishing状态,表示已经找到了front的ActivityRecord
r.frontOfTask = true;
foundFront = true;
}
if (!foundFront && numActivities > 0) {
mActivities.get(0).frontOfTask = true;
}
}
该函数从底到顶对任务栈进行遍历,找到的第一个未结束(finishing = faulse)的ActivityRecord, 将其frontOfTask属性设置成true;其他所有ActivtyRecord的frontOfTask属性设置为false。
ActivityStack
ActivityStack的职责是管理多个任务栈(TaskRecord),它是一个栈式结构,栈中的元素是TaskRecord。 每个Activity在特定的时刻都会有一个状态,譬如显示、销毁等, 在应用进程看来,这些状态的变化就是在执行Activity的生命周期函数; 在系统进程看来,这些状态的变化都需要经过ActivityStack来驱动。 Activity的状态是通过ActivityState这个枚举类来定义的:
enum ActivityState {
INITIALIZING,
RESUMED,
PAUSING,
PAUSED,
STOPPING,
STOPPED,
FINISHING,
DESTROYING,
DESTROYED
}
从INITIALIZING到DESTROYED,所定义状态值示意了Activity生命周期的走向。
属性 | 描述 |
---|---|
stackId | 每一个ActivityStack都有一个编号,从0开始递增。编号为0,表示桌面(Launcher)所在的ActivityStack,叫做Home Stack |
mTaskHistory | TaskRecord数组,ActivityStack栈就是通过这个数组实现的 |
mPausingActivity | 在发生Activity切换时,正处于Pausing状态的Activity |
mResumedActivity | 当前处于Resumed状态的ActivityRecord |
mStacks | ActivityStack会绑定到一个显示设备上,譬如手机屏幕、投影仪等,在AMS中,通过ActivityDisplay这个类来抽象表示一个显示设备,ActivityDisplay.mStacks表示当前已经绑定到显示设备的所有ActivityStack。当执行一次绑定操作时,就会将ActivityStack.mStacks这个属性赋值成ActivityDisplay.mStacks,否则,ActivityStack.mStacks就为null。简而言之,当mStacks不为null时,表示当前ActivityStack已经绑定到了一个显示设备 |
Activity状态的变迁,不仅仅是给ActivityRecord.state赋一个状态值那么简单,ActivityStack要对栈进行调整:之前的Activity要销毁或者挪到后台,待显示的Activity要挪到栈顶,这一调整,涉及到的工作就多了。
行为 | 描述 |
---|---|
findTaskLocked() | 该函数的功能是找到目标ActivityRecord(target)所在的任务栈(TaskRecord),如果找到,则返回栈顶的ActivityRecord,否则,返回null |
findActivityLocked() | 根据Intent和ActivityInfo这两个参数可以获取一个Activity的包名,该函数会从栈顶至栈底遍历ActivityStack中的所有Activity,如果包名匹配成功,就返回 |
moveToFront) | 该函数用于将当前的ActivityStack挪到前台,执行时,调用ActivityStack中的其他一些判定函数 |
isAttached() | 用于判定当前ActivityStack是否已经绑定到显示设备 |
isOnHomeDisplay() | 用于判定当前是否为默认的显示设备(Display.DEFAULT_DISPLAY),通常,默认的显示设备就是手机屏幕 |
isHomeStack() | 用于判定当前ActivityStack是否为Home Stack,即判定当前显示的是否为桌面(Launcher) |
moveTaskToFrontLocked() | 该函数用于将指定的任务栈挪到当前ActivityStack的最前面。在Activity状态变化时,需要对已有的ActivityStack中的任务栈进行调整,待显示Activity的宿主任务需要挪到前台 |
insertTaskAtTop() | 将任务插入ActivityStack栈顶 |
ActivityStack还有很多与迁移Activity状态相关的行为: startActivityLocked(), resumeTopActivityLocked(), completeResumeLocked(), startPausingLocked(), completePauseLocked(), stopActivityLocked(), activityPausedLocked(), finishActivityLocked(), activityDestroyedLocked(), 它们与Activity的生命周期调度息息相关,在Android四大组件之Activity–启动过程一文中,会再详细分析这几个函数的实现逻辑,本文还是通过一个简单的场景来分析ActivityStack的行为。
场景 1
以singleTask的方式启动一个处于后台的Activity,那么,就需要将Activity挪到前台。怎么挪呢?
第一步,findTaskLocked(): 找到Activity所在TaskRecord;
ActivityRecord findTaskLocked(ActivityRecord target) {
...
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
...
final ActivityRecord r = task.getTopActivity();
...
final Intent taskIntent = task.intent;
final Intent affinityIntent = task.affinityIntent;
...
if (!isDocument && !taskIsDocument && task.rootAffinity != null) {
if (task.rootAffinity.equals(target.taskAffinity)) {
return r;
}
} else if (taskIntent != null && taskIntent.getComponent() != null &&
taskIntent.getComponent().compareTo(cls) == 0 &&
Objects.equals(documentData, taskDocumentData)) {
return r;
} else if if (affinityIntent != null && affinityIntent.getComponent() != null &&
affinityIntent.getComponent().compareTo(cls) == 0 &&
Objects.equals(documentData, taskDocumentData)) {
return r
}
...
}
return null;
}
该函数的功能是找到target ActivityRecord所在的Task,如果找到,则返回Task栈顶的ActivityRecord,否则,返回null。 主体逻辑是对ActivityStack中的所有Task进行遍历,以下几种情况表示找到了ActivityRecord的宿主task:
- Affinity相同。rootAffinity表示第一次启动该task时affinity值,如果一个ActivityRecord的taskAffinity属性与其相等, 那么这个task自然是ActivityRecord的宿主;
- Intent的包名相同。
- Affinity Intent的包名相同。
第二步,moveToFront(): 将TaskRecord所在的ActivityStack挪到前台;
final void moveToFront(String reason) {
if (isAttached()) {
if (isOnHomeDisplay()) {
mStackSupervisor.moveHomeStack(isHomeStack(), reason);
}
mStacks.remove(this);
mStacks.add(this);
final TaskRecord task = topTask();
if (task != null) {
mWindowManager.moveTaskToTop(task.taskId);
}
}
}
首先,会有一些判定:
- isAttached(): 只有在当前ActivityStack绑定到显示设备的情况下,才需要挪到;
- isOnHomeDisplay(): 如果当前是默认的显示设备,则对HomeStack(桌面)进行挪动, 这涉及到对多个ActivityStack的操作,所以需要通过ActivityStackSupervisor完成;
- isHomeStack(): 如果当前是HomeStack,则将其挪到前后; 否则,将HomeStack挪到后台
然后,就是对mStacks这个属性进行操作:在mStacks数组中,删除已有的ActivityStack对象,并添加一个新的,这样做其实达到了一个目的,前台的ActivityStacks处于mStacks末尾。
最后,调用WMS.moveTaskToTop()通知窗口的进行变化调整。
第三步, moveTaskToFrontLocked(): 将TaskRecord挪到ActivityStack的栈顶;
final void moveTaskToFrontLocked(TaskRecord tr, ActivityRecord source, Bundle options,
String reason) {
final int numTasks = mTaskHistory.size();
final int index = mTaskHistory.indexOf(tr);
// 判定ActivityStack是否需要挪动任务栈
if (numTasks == 0 || index < 0) {
...
return;
}
// 调整ActivityStack
insertTaskAtTop(tr);
moveToFront(reason);
...
// 将栈顶的Activity置为Resumed状态
mStackSupervisor.resumeTopActivitiesLocked();
}
首先,会经过判定:如果当前的ActivityStack为空,或者不存在要挪动的任务,则不需要对当前ActivityStack进行调整;
然后,确定目标任务在当前ActivityStack中,则对ActivityStack进行调整,将目标任务插入ActivityStack栈顶。
- insertTaskAtTop(),会先将已有的目标任务删除,再重新添加到栈顶位置;
- moveToFront(),在第二步中执行过一次,因为在某些场景下,只会调用moveToFront(),不会调用moveTaskToFrontLocked(); 一旦要将任务挪到ActivityStack栈顶,意味着ActivityStack也一定要挪到前台;
最后,将任务栈顶的Activity置为Resumed状态,这里是通过ActivityStackSupervisor完成的。因为可能同时存在多个显示设备,所以需要对多个ActivityStack进行操作。
Activity管理中有两个栈顶:一是ActivityStack的栈顶,它对应到要显示的TaskRecord; 二是TaskRecord的栈顶,它对应到要显示的Activity。简单来说,当前显示的Activity一定是位于其所属的TaskRecord的栈顶,TaskRecord也一定位于其所属的ActivityStack的栈顶。
ActivityDisplay
Android支持多屏显示,在不同的显示设备上可以有不同的ActivityStack。
笔者一直在重述:所有的ActivityStack都是通过ActivityStackSupervisor管理起来的。 在ActivityStackSupervisor内部,设计了ActivityDisplay这个内部类,它对应到一个显示设备,默认的显示设备是手机屏幕。 ActivityStackSupervisor间接通过ActivityDisplay来维护多个ActivityStack的状态。 ActivityStack有一个属性是mStacks,当mStacks不为空时,表示ActivityStack已经绑定到了显示设备, 其实ActivityStack.mStacks只是一个副本,真正的对象在ActivityDisplay中。
属性 | 描述 |
---|---|
mDisplayId | 显示设备的唯一标识 |
mDisplay | 获取显示设备信息的工具类, |
mDisplayInfo | 显示设备信息的数据结构,包括类型、大小、分辨率等 |
mStacks | 绑定到显示设备上的ActivityStack |
行为 | 描述 |
---|---|
attachActivities() | 将一个ActivityStack绑定到显示设备 |
setVisibleBehindActivity() | 设置后台显示的Activity |
moveHomeStack() | 移动HomeStack |
ActivityContainer
在ActivityStackSupervisor中,还设计了名为ActivityContainer的内部类。 该类是对ActivityStack的封装,相当于开了一个后门,可以通过adb shell am
命令对ActivityStack进行读写操作,方便开发和调试。
ActivityStackSupervisor
ActivityStackSupervisor的职责是管理多个ActivityStack。
属性 | 描述 |
---|---|
mHomeStack | 主屏(桌面)所在ActivityStack |
mFocusedStack | 表示焦点ActivityStack,它能够获取用户输入 |
mLastFocusedStack | 上一个焦点ActivityStack |
mActivityDisplays | 表示当前的显示设备,ActivityDisplay中绑定了若干ActivityStack。通过该属性就能间接获取所有ActivityStack的信息 |
行为 | 描述 |
---|---|
setFocusedStack() | 设置当前的焦点ActivityStack |
adjustStackFocus() | |
startHomeActivity() | 启动桌面 |
ActivityStackSupervisor有很多与ActivityStack功能类似的行为,不过都是针对多个ActivityStack进行操作。 譬如findTaskLocked(), findActivityLocked(), topRunningActivityLocked(), ensureActivitiesVisibleLocked()等,
场景 1
在启动一个新的Activity时,需要设置当前的焦点,通过AMS.setFocusedActivityLocked()函数,就能设置一个 ActivityRecord为当前的焦点Activity:
final void setFocusedActivityLocked(ActivityRecord r, String reason) {
if (mFocusedActivity != r) {
mFocusedActivity = r;
...
mStackSupervisor.setFocusedStack(r, reason + " setFocusedActivity");
if (r != null) {
mWindowManager.setFocusedApp(r.appToken, true);
}
applyUpdateLockStateLocked(r);
}
...
}
该函数的逻辑很简单,如果当前的焦点(mFocusedActivity)不是待显示的(r),则需要更新焦点; 然后,就发起了其他函数调用。 这里,需要通过ActivityStackSupervisor完成对ActivityStack的管理,通过调用setFocusedStack()来设置当前的焦点Stack, 焦点Stack就是焦点Activity所属的ActivityStack。
void setFocusedStack(ActivityRecord r, String reason) {
if (r != null) {
final TaskRecord task = r.task;
// 判断输入的ActivityRecord是否为HomeActivity
boolean isHomeActivity = !r.isApplicationActivity();
if (!isHomeActivity && task != null) {
isHomeActivity = !task.isApplicationTask();
}
if (!isHomeActivity && task != null) {
final ActivityRecord parent = task.stack.mActivityContainer.mParentActivity;
isHomeActivity = parent != null && parent.isHomeActivity();
}
moveHomeStack(isHomeActivity, reason);
}
}
只有前台的ActivityStack才能获取焦点,所以,该函数的功能就是要将待显示的Activity所在的ActivityStack挪到前台。 很重要的一个处理逻辑就是判定待显示的ActivityRecord的类型是否为HomeActivity,判定细节此处不表。结果是: 如果待显示的ActivityRecord类型为HomeActivity,则需要将HomeStack挪到前台; 否则,意味着要将HomeStack挪到后台。 挪动HomeStack,是通过moveHomeStack()这个函数实现的:
void moveHomeStack(boolean toFront, String reason) {
// 1. 获取当前的Top Stack
ArrayList<ActivityStack> stacks = mHomeStack.mStacks;
final int topNdx = stacks.size() - 1;
if (topNdx <= 0) {
return;
}
ActivityStack topStack = stacks.get(topNdx);
// 2. 判定HomeStack是否需要挪动
final boolean homeInFront = topStack == mHomeStack;
if (homeInFront != toFront) {
mLastFocusedStack = topStack;
stacks.remove(mHomeStack);
stacks.add(toFront ? topNdx : 0, mHomeStack);
mFocusedStack = stacks.get(topNdx);
}
...
// 3. 判定当前AMS是否完成启动
if (mService.mBooting || !mService.mBooted) {
final ActivityRecord r = topRunningActivityLocked();
if (r != null && r.idle) {
checkFinishBootingLocked();
}
}
}
获取当前的Top Stack,其实就是获取mStacks这个数组最后的元素。mStacks这个属性在ActivityStack和ActivityDisplay中都见过,它们是同一个东西,ActivityStackSupervisor要管理的就是这个东西;
- 判定当前HomeStack是否需要挪动。有四种情况:
- homeInFront = true, toFront = false: 表示HomeStack在前台,要将其挪到后台,则需要将HomeStack挪到mStacks的0号位置;
- homeInFront = true, toFront = true: 表示HomeStack在前台,要将其挪到前台,则不需要对mStacks进行调整;
- homeInFront = false, toFront = true: 表示HomeStack在后台,要将其挪到前台,则需要将HomeStack挪到mStacks的末尾;
- homeInFront = false, toFront = false: 表示HomeStack在后台,要将其挪到后台,则不需要对mStacks进行调整。
- 判断当前AMS是否完成启动。如果当前是刚开机,AMS都还未启动完成,需要显示的Activity还处于idle状态,则需要发起一次是否启动完成的检查
ProcessRecord
AMS采用ProcessRecord这个数据结构来维护进程运行时的状态信息,当创建系统进程(system_process)或应用进程的时候,就会通过AMS初始化一个ProcessRecord。
属性 | 描述 |
---|---|
BatteryStats | 电量统计的接口 |
ApplicationInfo | 系统进程的ApplicationInfo是从android包中解析出来的数据; 应用程序的ApplicationInfo是从AndroidManifest.xml中解析出来的数据 |
Process Name | 进程名称 |
UID | 进程的UID。系统进程的UID是1000(Process.SYSTEM_UID); 应用进程的UID是从10000(Process.FIRST_APPLICATION_UID)开始分配的 |
maxAdj, curAdj, setAdj | 各种不同的OOM Adjustment值 |
lastPss, lastPssTime | 物理内存(PSS)相关,进程中有对象创建或销毁时,PSS相关的属性会被更新。 |
activities, services, receivers | 进程中的Android组件,随着进程的运行,这些信息都可能需要更新。譬如Activity的启动时,ProcessRecord.activies会增加一个实例; 销毁时,对将对应的实例从activities删除 |
pkgList | 进程中运行的包 |
thread | 该属性是IApplicationThread类型的对象 |
ProcessRecord有“激活(Active)”和“非激活(Inactive)”两种状态,只有将ProcessRecord绑定到一个实际进程的时候,才是激活状态。 绑定成功后,thread属性就被赋值,表示ProcessRecord已经激活。 激活后,AMS就可以通过这个接口完成对应用进程的管理,譬如启动Activity、派发广播等。 将ProcessRecord绑定到应用进程的过程在Android四大组件之Activity–应用进程与系统进程的通信一文中有详细的分析。
行为 | 描述 |
---|---|
makeActive() | 将ProcessRecord置成激活状态 |
makeInactive() | 将ProcessRecord置成非激活状态 |
addPackage() | 向ProcessRecord添加包 |
2.2 关联关系
- AMS运行在SystemServer进程中。SystemServer进程启动时,会通过SystemServer.startBootstrapServices()来创建一个AMS的对象;
private void startBootstrapServices() {
...
mActivityManagerService = mSystemServiceManager.startService(
ActivityManagerService.Lifecycle.class).getService();
...
}
- AMS通过ActivityStackSupervisor来管理Activity。AMS对象只会存在一个,在初始化的时候,会创建一个唯一的ActivityStackSupervisor对象;
public ActivityManagerService(Context systemContext) {
...
mStackSupervisor = new ActivityStackSupervisor(this);
...
}
- ActivityStackSupervisor中维护了显示设备的信息。当有新的显示设备添加时,会创建一个新的ActivityDisplay对象;
public void handleDisplayAddedLocked(int displayId) {
...
newDisplay = mActivityDisplays.get(displayId) == null;
if (newDisplay) {
ActivityDisplay activityDisplay = new ActivityDisplay(displayId);
...
}
...
}
- ActivityStack与显示设备的绑定。当需要创建一个ActivityStack时,需要将其绑定到具体的显示设备。 ActivityStackSupervisor通过ActivityContainer这个内部类对ActivityStack进行了一层封装, 所以,会首先创建一个ActivityContainer对象;然后,通过ActivityContainer.attachToDisplayLocked()函数进行具体的绑定操作;
private int createStackOnDisplay(int stackId, int displayId) {
...
ActivityContainer activityContainer = new ActivityContainer(stackId);
mActivityContainers.put(stackId, activityContainer);
activityContainer.attachToDisplayLocked(activityDisplay);
return stackId;
}
- AMS维护了所有进程的信息ProcessRecord。当需要创建一个新的进程时, 会通过AMS.newProcessRecordLocked()函数来创建一个ProcessRecord对象, ProcessRecord对象都保存在AMS.mPidsSelfLocked这个属性中;
final ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess,
boolean isolated, int isolatedUid) {
...
return new ProcessRecord(stats, info, proc, uid);
}
- 通过ActivityStackSupervisor来创建ActivityRecord。当SystemServer进程收到来自应用进程的启动Activity请求时, 会通过ActivityStackSupervisor来创建一个ActivityRecord对象;
final int startActivityLocked(IApplicationThread caller, ...) {
...
ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage,
intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho,
requestCode, componentSpecified, this, container, options);
...
}
- 在ActivityStack上创建TaskRecord。当需要创建新的任务栈时,就会通过ActivityStack对象来创建一个TaskRecord对象, 这样就建立了ActivityStack和TaskRecord的关联;
TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
boolean toTop) {
TaskRecord task = new TaskRecord(mService, taskId, info, intent, voiceSession,
voiceInteractor);
addTask(task, toTop, false);
return task;
}
- ActivityRecord的宿主TaskRecord。每一个ActivityRecord都需要找到自己的宿主TaskRecord,通过ActivityRecord.setTask()函数 就能建立ActivityRecord和TaskRecord的关联;
void setTask(TaskRecord newTask, TaskRecord taskToAffiliateWith) {
...
task = newTask;
setTaskToAffiliateWith(taskToAffiliateWith);
}
- 进程中运行的Activity信息。Activity在应用进程中运行,AMS中记录了进程中所有运行的Activity的信息,在ActivityRecord创建后, 会通过ProcessRecord.addPackage()函数,在ProcessRecord中登记ActivityRecord的信息
void startSpecificActivityLocked(ActivityRecord r,
boolean andResume, boolean checkConfig) {
ProcessRecord app = mService.getProcessRecordLocked(r.processName,
r.info.applicationInfo.uid, true);
...
app.addPackage(r.info.packageName, r.info.applicationInfo.versionCode,
...
}
3. Activity管理的延伸
在分析完Activity管理的基础数据结构及关联关系后,想必各位读者已经感受到了Activity管理的复杂性。 如此庞大而精密的数据结构设计,是在什么背景下产生的呢?从已有的Activity设计中, 能否窥探出以后Android在Activity相关特性的发展方向呢?譬如多屏幕、多窗口的Activity显示。
笔者一直认为,研究Android源码不仅仅是理解Android的内部运行机制,更重要的是体会出其背后的设计思想, 总结出一套解决同类问题的方法论,然后再到具体的软件开发中进行实践,哪怕不在Android平台下开发, 提炼出来的方法论仍然是受用的。
Android四大组件之Activity--管理方式的更多相关文章
- Android四大组件之Activity(活动)及其布局的创建与加载布局
Android四大组件之Activity(活动)及其布局的创建与加载布局 什么是Activity ? 活动(Activity)是包含用户界面的组件,主要用于和用户进行交互的,一个应用程序中可以包含零个 ...
- Android四大组件之——Activity的生命周期(图文详解)
转载请在文章开头处注明本博客网址:http://www.cnblogs.com/JohnTsai 联系方式:JohnTsai.Work@gmail.com [Andro ...
- Android四大组件之——Activity的开启:StartActivity()和StartActivityForResult()(图文详解)
如需转载请在文章开头处注明本博客网址:http://www.cnblogs.com/JohnTsai 联系方式:JohnTsai.Work@gmail.com ...
- 【Android开发日记】之入门篇(四)——Android四大组件之Activity
在Android中,无论是开发者还是用户,接触最多的就算是Activity.它是Android中最复杂.最核心的组件.Activity组件是负责与用户进行交互的组件,它的设计理念在很多方面都和Web页 ...
- Android 四大组件之 Activity(二)
1.综述 Activity是Android四大组件(Application Components)之一,简单来说Activity就是平常所见到的用户界面,一般情况下,一个Activity所占的窗口是满 ...
- Android四大组件及activity的四大启动模式
Android四大组件 1. 广播接收者的两种类型: (1)系统广播接收者,就是继承BroadcastReceiver这个类,然后还要在清单文件中注册,注册之后给他一个action.当系统发生了这个a ...
- Android四大组件之——Activity(一)定义、状态和后退栈(图文详解)
什么是Activity 关键字:应用组件.四大组件.用户界面,交互. An Activity is an application component that provides a screen wi ...
- Android 四大组件之 Activity(一)
1.Activity的定义及作用: Android系统中的四大组件之一,可以用于显示View.Activity是一个与用户交互的系统模块,几乎所有的Activity都是和用户进行交互的一个应用程序的组 ...
- Android 四大组件之Activity(续2)
1.生命周期 关于生命周期,在详细讲解下: 上图是从android官网获取的生命周期. 正常的流程,很多文章都讨论过了,我们讨论几个特殊的情况. 1)OnResume->OnPause-> ...
随机推荐
- IOS的UIWebView中JS点击事件,需要加入cursor:pointer;属性才可以
IOS的UIWebView中JS点击事件,需要加入cursor:pointer;属性才可以. Android的WebView可以支持外链样式,js文件:IOS则需要改为内嵌样式和JS文件.
- Day1:注释
一.注释方法 1.单行注释用#,本行#号后的内容为注释内容,不执行 2.多行用三个单引号或三个双引号标注,中间内容为注释,不执行 二.其他相关内容 三个引号中的内容还可以当作字符串赋值给变量,可以同时 ...
- [React Intl] Format Numbers with Separators and Currency Symbols using react-intl FormattedNumber
Using a react-intl FormattedNumber component, we'll pass a Number and a few additional props in orde ...
- App.js和App.css(用于移动应用的js和css)
App.js和App.css(用于移动应用的js和css) 一.App.js和App.css(用于移动应用的js和css) App.js 是一个轻量级的 JavaScript UI 库,用来创建移动的 ...
- linux终端下一些“风骚”的按键操作及Linux终端命令
linux终端下一些"风骚"的按键操作 <backspace> 删除 <ctrl-l> 清空屏幕, 相当于clear tab ...
- 【z12】&&【b092】hankson的趣味问题
描述 Hanks 博士是 BT (Bio-Tech,生物技术) 领域的知名专家,他的儿子名叫 Hankson.现 在,刚刚放学回家的 Hankson 正在思考一个有趣的问题. 今天在课堂上,老师讲解了 ...
- ORACLE中的Net Configuration Assistant 点击后无反应, sqlplus登录数据库提示Oracle11g ORA-12560: TNS: 协议适配器错误
首先是对于点击无反应问题: 如果是客户端下的Net Configuration Assistant可用,而服务器端的Net Configuration Assistant等工具不可用的原因如下. 环境 ...
- POJ 1251 Jungle Roads (zoj 1406) MST
传送门: http://poj.org/problem?id=1251 http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=406 P ...
- 使用JQuery将前端form表单数据转换为JSON字符串传递到后台处理
一般地,我们在处理表单(form表单哦)数据时,传输对象或字符串到后台,Spring MVC或SpringBoot的Controller接收时使用一个对象作为参数就可以被正常接收并封装到对象中.这种方 ...
- [Angular] How to styling ng-content
Let's say you are builing a reuseable component. The style structure like this: div > input If yo ...