Android中的任务管理

Activity是Android负责与用户交互的实体。一个应用一般由多个Activity组成;一个Activity不仅可以打开应用内的Activity,也可以打开其他应用内的Activity,比如通过一个名为android.intent.action.VIEW的Intent事件可以打开浏览器;通过android.intent.action.DIAL可以向CALL发送拨号请求。一系列的Activity组合在一起(完成某个特定的“任务“)构成了一个任务(一个后进先出的队列,被称为back stack),而多个任务又构成了一个任务栈(后台可以有多个任务栈,但系统如需恢复内存,则会杀死部分应用,清空部分任务,以释放内存,因此长时间后应用状态会丢失)。以下图为例,图中的back stack一开始只有一个Activity1;接着在Activity1中启动Activity2,此时Activity2位于队列的顶部,而Activity1位于栈底(Activity1处于Stop状态),而Activity2又启动一个Activity3,同样,Activity3压入栈顶。如果此时通过回退键(Back)用户离开Activity3,则Activity3从栈中清除,被系统销毁,而Activity2则恢复到可见状态(resume)。

Back Stack of Activities

总的说来,Activity与任务栈之间的关系有如下几种情况:

  • 当Activity A启动ActivityB时,A处于停止(stop)状态,但是系统会保留其状态(如当前滑动位置以及输入的文本等);如果在B中用户按下回退键,此时A恢复原来的状态,而B被销毁;
  • 当用户按下HOME键离开一个任务时,当前Activity会被停止,相应的任务进入后台,系统会保留任务中所有Activity的状态。如果用户稍后通过启动界面(Launcher)的图标启动该任务,则任务恢复到前台运行,使任务顶部的Activity处于可见状态(resume);
  • 用户按下回退(back)键时,当前Activity从栈中弹出,销毁(系统不再保留次Activity状态),此时之前的Activity恢复运行;
  • 一个Activity可被实例化多次,该实例可以来自不同的任务(back stack);

那么,Android是如何来管理这些任务的了?这里有这么几个问题:

  • 刚讲到,一个Activity可能在一个任务中被实例化多次,那么如何避免这样多次实例化的开销了?
  • 怎么来控制一个Activity所属的任务(back stack)?
  • 怎么启动一个新的任务?

任务管理

Android通过启动模式(Launch Mode)来控制Activity的启动方式,也可以通过控制Activity中的某些属性来控制Activity在任务中的行为。启动模式主要有两种控制方式,一个是在manifest文件的<activity>标签来指定,一种是通过Intent中的标志位。接下来就来看下这两种控制方式。

使用manifest文件

<activity>中有一个launchMode的属性来指定Activity启动的模式:

  • “standard”: 默认的启动模式,系统创建一个新的Activity实例,并放入启动该Activity的任务中。在这种模式下,一个Activity可能被实例化多次,每个实例可能属于不同的任务,而且有可能一个任务里存在多个实例;
  • “singleTop”: 如果当前任务的顶部已存在该Activity实例,则将intent通过onNewIntent方法传给该Activity,不会再创建一个新的Activity实例;
  • “singleTask”: 创建一个新的任务,并将该Activity放入任务(back stack)的底部;但是如果其他任务栈中已存在该Activity实例,则不会创建新的任务,而是将该Activity实例所在任务放置到前台运行,intent则通过onNewIntent()方法传给该Activity。该启动模式确保任务栈中始终只有一个Activity实例存在;
  • “singleInstance”: 与singleTask相同,唯一的区别在于该模式下系统不会将其他Activity放入到该Activity所在的任务中,并且任何通过该Activity启动的Activity都会在另一个任务当中,就是说,当前Activity始终是其任务中唯一的一个成员;

使用Intent标志位

当通过startActivity()来启动Activity时,可以通过Intent的标志位来控制Activity的启动方式:

  • FLAG_ACTIVITY_NEW_TASK: 行为与launchMode中的singleTask一样;
  • FLAG_ACTIVITY_SINGLE_TOP: 行为与launchMode中的singleTop一样;
  • FLAG_ACTIVITY_CLEAR_TOP: 这个标志在launchMode中没有对应的值。如果当前任务中已存在该Activity的实例,则将所有在该Activity之上的其他Activity都销毁,该Activity置于任务的栈顶,并通过onNewIntent()方法将intent传给该实例。

处理亲和标志

affinity(亲和性)表示一个activity属于哪个任务(back stack)。Android默认在同一个进程中,所有的Activity相互之间都有同样的affinity,就是说在同一个任务中。但是,通过taskAffinity属性值我们可以修改一个Activity属于的任务,这样不同应用之前的Activity可以共有一个affinity,而同一个应用之间的Activity也可以有不同的affinity

  • 对于Activity来说,可通过标签<activity>中的属性taskAffinity来指定affinity
  • 对于一个应用来说,<application>中的taskAffinity属性默认为<manifest>中指定的程序包名;

一般有两种情况会影响到Activity的affinity

  • 启动Activity时,设置了标志位FLAG_ACTIVITY_NEW_TASK,此时如果系统没有包含该Activity实例的任务,则启动新的任务;否则该Activity从已有任务中启动;
  • <activity>中的属性值allowTaskReparenting设为true,在这种情况下,如果Activity所属affinity的任务到前台运行,则该Activity可能从启动它的任务(back stack)中移动到它所属affinity的任务当中。

清除任务

如果用户长时间离开一个任务,系统会清理除根Activity以外的所有Activity。当用户返回应用后,仅有根Activity被恢复。但可以通过如下几个<activiyt>的属性值来更改系统的这一行为:

  • alwaysRetainTaskState:如果在一个任务中的根Activity中的将其设为true,则不会任务中的任何Activity,即使长时间后,依然保留所有Activity在任务中;
  • clearTaskOnLaunch:其作用恰好与alwaysRetainTaskState相反;如果在根Activity中将该属性值设为true,则只要用户离开任务,则会将除根Activity之外的Activity都清除,就是说,每次用户返回任务时,都是一个最开始初始化的状态;
  • finishOnTaskLaunch:与clearTaskOnLaunch作用类似,但其只局限于单个Activity,而不是整个任务(back stack),它可以使任何一个Activity被清除,包括根Activity。该属性设为true时,应用只是为当前会话而保留在任务中,一旦用户离开,稍后返回任务,则不能再看到该Activity。

参考文献