一个应用中包含了多个Activity实例,每个Activity都有各自的action,每个Activity也可以启动其他Activity,如一个Email应用程序应包含一个显示Email信息列表的Activity。当用户点击列表中的某一项时,显示详细内容的Activity将被启动。
本文将介绍Activity的栈和后退栈(Tasks and Back Stack)的相关知识,您需访问官方原文,您可以点击这个链接:《Tasks and Back Stack》。
一个Activity甚至可以启动其他应用程序中的Activity。例如您打算启动一个发送邮件的Activity,那么使用Intent隐式来启动,该Intent中应包含值为“send”的action、并包含一些data数据,比如邮件地址和信息内容。这样,启动的Email的Activity就好像是本应用的一部分。Android使用同一任务栈来使得Activity之间实现无缝切换的(Android maintains this seamless user experience by keeping both activities in the same task)。
任务栈和返回栈(Tasks and Back Stack)
任务栈是一个存放Activity集合的地方。通过一个任务栈,用户可以完成一项操作(如发送短信的操作、照相的操作 等)(A task is a collection of activities that users interact with when performing a certain job)。
Home键是多数任务栈启动的地方(The device Home screen is the starting place for most tasks)。当用户点击某个应用的图标时,这个应用程序的任务栈就被切换至前台;如果任务栈不存在(即用户还从未启动过该应用),那么系统会新建一个任务栈,这个应用的主Activity将被压至任务栈的栈底(a new task is created and the “main” activity for that application opens as the root activity in the stack)。
当Activity A 启动Activity B时,Activity B会被压入任务栈的栈顶,并获得焦点。而Activity A将处于Activity的下面,且处于stop状态,处于stop状态的Activity仍持有与用户交互的信息。当用户点击了back键时,Activity B将从栈中弹出,并被destroy,Activity A恢复resume状态。Activity在栈中的顺序永远不会重新排列(Activities in the stack are never rearranged),Activity只能从栈中弹出或是压入(only pushed and popped from the stack)。任务栈遵循后进先出的原则(last in, first out)。如下图所示:
用户点击back键时,栈中的Activity将依次被弹出,直到退回到桌面。当所有Activity都被弹出后,任务栈将不再存在(When all activities are removed from the stack, the task no longer exists)。
任务栈是一组紧密结合的单元:当用户启动了一个Activity或点击Home键退回桌面时,Activity所属的任务栈将整体地移至前台或移至后台。移至后台的任务栈中的所有Activity将处于stop状态。但栈中的内容将保持完整,它们只是失去了焦点而已,如下图所示,处于后台的task A中的Activity仅处于stop状态,它们仍保留了Activity的所有信息:
Android可以在后台一次性处理多个任务栈。然而,如果在后台管理太多的任务栈,系统可能会destroy一些Activity以腾出内存,导致Activity的状态丢失。
由于在任务栈中,Activity的顺序不能改变,默认情况下,若在一个应用程序中需要启动另一个应用程序的某个Activity多次,那么该Activity每次在被启动时都会被重新实例化,如下图所示:
当然,您可也可以改变这种默认规则,让一个任务栈中的Activity实例不能重复。这将在下面介绍。
下面对Activity和任务栈的默认规则做一总结:
- 当Activity A启动Activity B 时,Activity A处于stop状态,但Activity A中的信息被保留,如用户键入的信息或滑动的位置。若用户在Activity B中点击了back键,Activity A将恢复状态。
- 当用户点击了Home键,Activity与其所属的任务栈将整体转入后台,并处于stop状态。栈中所有Activity的状态均被保留。若用户通过点击应用图标恢复了该应用程序,则该任务栈又被切换至前台,Activity将又处于栈顶。
- Activity可以被实例化多次。
保存Activity状态(Saving Activity State)
如上所述,不在栈顶的Activity将由系统保存其状态,当Activity重新处于栈顶时,Activity中的内容将被恢复。事实上,开发者应通过Activity中的回调方法主动地保存Activity中的状态信息。
当Activity处于stop状态时,系统会优先回收其实例以腾出内存。这样的话,Activity的状态信息就会丢失。但是任务栈仍然记录了该Activity 在栈中的位置,当它恢复至前台时,Activity的实例将重新创建(而不是resume),其内容将丢失。为了避免内容丢失,您应当重写Activity的回调方法
onSaveInstanceState()
。
管理任务栈(Managing Tasks)
上面介绍的都是任务栈的默认行为,这种默认行为已经可以处理大多数情况,但有些时候,您希望打破这种默认行为,如启动应用中的某个Activity时新建一个任务栈;或者启动一个已存在的Activity时,希望直接将其调至前台,而不是再新创建一个Activity实例;或者退出任务栈时,您希望除了栈底的主Activity之外,其余Activity均出栈 等。
为了实现上述操作,您需要在manifest文件的
<activity>
标签中配置,或在starActivity()
方法中传入的Intent参数中添加flag。
其中
<activity>
标签中关于任务栈的主要属性如下:
taskAffinity
launchMode
allowTaskReparenting
clearTaskOnLaunch
alwaysRetainTaskState
finishOnTaskLaunch
常用的Intent的flag如下:
!请注意:许多应用程序禁止改变其Activity和任务栈的默认行为。若确实需要改变其其行为,需要测试back键、home键等操作,以防与默认行为产生冲突。
定义启动模式(Defining launch modes)
启动模式指定了新启动的Activity与当前任务栈中关联模式。定义启动模式的方式有两种:
- 在manifest中配置(Using the manifest file);
- 使用intent的flag参数(Using Intent flags);
举例来说,Activity A 启动Activity B,可以在Activity B 的manifest中指定Activity B如何与当前任务栈管理,也可以通过Activity A的intent指定Activity B如何与当前任务栈管理。若程序中两种方式都配置了,那么以intent的指定方式为准(Activity A’s request (as defined in the intent) is honored over Activity B’s request (as defined in its manifest))。
!请注意:某些启动模式只能在manifest中配置,而不能在intent的flag中指定;还有一些启动模式只能在intent的flag中指定而不能在manifest中配置。
使用manifest文件配置启动模式(Using the manifest file)
在
<activity>
标签的launchMode
属性中配置;该属性指定了Activity在任务栈中以何种方式被启动,有四种方式:
standard
(默认):Activity可以被多次实例化,每个实例可以存在于不同任务栈中,每个任务栈可以包含多个相同的Activity实例。singleTop
:若Activity实例已处于栈顶,该Activity只是回调其onNewIntent()
方法而不再创建新的实例。该Activity可以被实例化多次,每个实例可以存在于不同任务栈中,每个任务栈可以包含多个相同的Activity实例,但处于栈顶的实例除外。singleTask
:系统将创建一个新的任务栈,并将该Activity放入该任务栈的栈底。若该Activity实例在其他的任务栈中已存在,系统将不会再创建该Activity实例,而是回调onNewIntent()
方法。一个Activity实例在同一时刻只能有一个。singleInstance
:与singleTask
模式类似,唯一不同的是:若是将启动的Activity压入新创建的栈中(即启动的Activity实例不存在),那么该栈中有且仅有一个Activity实例,不能再有其他实例。
举个例子,Android浏览器应用程序中的每个Activity都被指定为
singleTask
启动模式。这就意味着如果您的Activity打算启动Android浏览器应用程序中的Activity,那么启动的Activity将不会出现在您的应用程序中,而是要么压入新创建的任务栈中,要么将要启动的Activity所在的任务栈切至前台(后一种情况表明要启动的Activity已经被启动过,只是在后台而已)。
无论上述那种情况,当您按下back键的时候,系统总是切至前台任务栈中Activity。如果将某个Activity的启动模式指定为singleTask
,且该实例已存在,那么系统会将该Activity所属的任务栈整体切换至前台。如下图所示:
使用Intent中包含的flags参数配置启动模式(Using Intent flags)
除了使用在manifest中配置Activity的启动模式,还可以在代码中使用Intent包含的flags参数,并最终将该Intent传入
startActivity()
方法来配置启动模式。常用的flag参数如下:
FLAG_ACTIVITY_NEW_TASK
:创建一个新的任务栈,并将该Activity压入栈底。若启动的Activity已经存在于内存中,则系统将该Activity所在的任务栈整体切至前台,并回调该Activity实例的onNewIntent()
方法。这相当于在manifest中配置了singleTask
启动模式。FLAG_ACTIVITY_SINGLE_TOP
:若启动的Activity位于栈顶,则不再创建一个Activity实例,而只是回调其onNewIntent()
方法。这相当于在manifest中配置了singleTop
启动模式。FLAG_ACTIVITY_CLEAR_TOP
:如果要启动的Activity已经在当前任务栈中存在实例,那么所有该Activity上面的Activity实例均destroy,这时该Activity实例将位于栈顶并将intent回传至该Activity的onNewIntent()
方法中。这在manifest中没有与之匹配的启动模式。FLAG_ACTIVITY_CLEAR_TOP
经常与FLAG_ACTIVITY_NEW_TASK
联合使用。
处理affinities(Handling affinities)
affinities表示一个Activity“期望”属于哪一个任务栈。默认情况下,同一应用的所有Activity的affinity相同。也就是,默认情况下,同一应用的所有Activity都期望属于同一个任务栈。不过,您可以修改默认设置。比如,来自不同应用的Activity可以拥有同一个affinity,或者同一应用的不同Activity具有不同的affinity。
您可以在activity标签中配置
taskAffinity
属性来改变Activity的affinity。taskAffinity
属性是一个字符串,您必须保证在<manifest>
标签中指定的包名唯一,因为Android将包名作为任务栈affinity缺省属性。
affinity会在下面两中情形下产生作用:
- 当启动的intent包含
FLAG_ACTIVITY_NEW_TASK
的flag时。这表明启动的Activity总是“希望”将自己压入一个新栈中。然而,如果系统中存在一个任务栈的affinity属性与该Activity相同,则该Activity将被压入到这个任务栈中。如果没有这样的任务栈,则系统会新建一个任务栈并将该Activity压入栈底。若该flag使得启动的Activity压入了新创建的任务栈,并且用户点击了Home键,这就需要用某种方法再将该Activity切换至前台。某些实体(如notification manager)通常将Activity启动至一个新栈的栈底中。由于这些启动的notification不属于本应用,所以在intent中通常加入
FLAG_ACTIVITY_NEW_TASK
的flag。如果您的Activity可以被这种外部实体所启动,那么需当心,用户还可以使用一种独特的方式启动它(如在intent filter中加入CATEGORY_LAUNCHER
属性,那么点击启动图标,所有位于栈底的Activity所在的任务栈都符合隐式启动条件)。
- 当Activity的
allowTaskReparenting
属性为true
时。这时,启动Activity时,该Activity属于默认的任务栈,当Activity所属的应用切至前台时,该Activity又回到了这个应用任务栈中。比如说,将天气预报应用中某个Activity的
allowTaskReparenting
属性设为true
时,您的应用启动这个Activity,那么该Activity最初属于您的应用的任务栈,但当天气预报应用的任务栈切至前台时,这个Activity将被压至天气预报应用的任务栈中。
清除后退栈(Clearing the back stack)
若用户长时间未将任务栈切至前台,那么该任务栈中的Activity将被destroy(不包含栈底的启动Activity)。当用户将该任务栈切至前台时,只有栈底的Activity被恢复。下面列出一些属性来修改这一默认行为:
alwaysRetainTaskState
:若将栈底的Activity的alwaysRetainTaskState
属性设为true,那么即便将该Activity所属的任务栈放在后台很长一段时间,任务栈中所有的Activity的状态信息都会得到保留。clearTaskOnLaunch
:若将栈底的Activity的clearTaskOnLaunch
属性设为true,那么即便用户将该任务栈切至后台的很短时间,任务栈中所有的Activity的状态信息都会被清除,再次将该任务栈切至前台时,任务栈将是初始状态。也就是说,这个属性与alwaysRetainTaskState
属性完全相反。finishOnTaskLaunch
:该属性与clearTaskOnLaunch
类似,只是它针对的是单个Activity。
启动任务栈(Starting a task)
您可以按照如下intent-filter来配置任务栈的启动Activity:
<activity ... >
<intent-filter ... >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
...
</activity>
用户必须能够随时退出任务栈并随时通过启动图标将任务栈切至前台
( Users must be able to leave a task and then come back to it later using this activity launcher)。所以,使用"singleTask"、 "singleInstance"
这两种启动模式的的Activity必须具有ACTION_MAIN 和 CATEGORY_LAUNCHER
的intent-filter。比如说,用户启动了一个具有singleTask
模式的Activity,该Activity被压入了一个新的任务栈的栈底,当用户在该Activity中做了一些编辑工作后,点击home键,将该任务栈切至后台,那么当用户希望再次启动该Activity时,若该Activity为配置ACTION_MAIN 和 CATEGORY_LAUNCHER
,那么用户将无法将启动该Activity!若您确实希望该Activity切至后台以后,用户不能再将它切至前台,可以在该activity标签中将finishOnTaskLaunch
属性设为true
。