本文摘要
|---Saving Activity State
|---Managing Tasks
|---Defining launch modes
|---Handling affinities
|---Clearing the back stack
|---Starting a task
应用程序(也就是我们日常接触到的“App”)中往往包含着很多activity。每个activity都应该围绕着某种具体功能来设计,用户不仅可以使用该功能,而且可以通过这个activity去开启其他activity。比如,邮件App中有一个activity是专门用来展示未读邮件列表的,当用户选中其中一封邮件时,一个新的activity就会被开启用以阅读这封邮件。
一个应用程序中的activity可以开启另一个应用程序中的activity。当然了,前提是这两个应用程序同在一台设备上。举个例子,如果我们的应用程序需要发送一封邮件,那么我们可以定义一个intent来调用其它应用程序中的activity帮我们实现发送的功能。另外,如果有需要,还可以用这个intent携带一些数据,如收件人地址和要发送的消息内容等。这条intent发出后,另一个应用程序中声明自己可以处理此类intent的activity就会被开启。在我们这个例子中,我们定义的这条intent想要做的是发送一封邮件,所以邮箱App中负责撰写邮件的activity开启了(如果同一设备上存在多个activity支持同一类型的intent,那么系统会提示用户从中选择一个)。当邮件发送完毕,邮箱App中负责撰写邮件的activity就会关闭,然后我们的activity会重新展现在屏幕上。尽管负责发送邮件的activity来自另一个应用程序,但给人的感觉好像它就是我们的应用程序的一部分。这两个并不存在于相同应用程序中的activity之所以能够流畅的调用,就是因为在安卓将它们放置在同一个任务栈中。
任务栈就是用户在完成某一事情时要与之交互的一系列activity的集合。这些activity被有序地放置在回收栈中,排列的顺序就是其被打开的先后顺序——先打开者置于栈底,后打开者依次向上罗列。不理解?没关系,请看下面这张图:
【小贴士】平时摆弄手机App时,相信你有此体会:通常情况下,开了几个页面(activity)之后,点击手机上的返回键,就可以逐层后退。就比如上图中的例子,当我们操作到3号activity时,按一下返回键就可以退到2号activity界面,再按一下返回键,1号activity就又出现在屏幕上了,如果再按返回键,就退回到手机桌面了。其实这就是因为任务栈在帮我们保留所有操作过的activity,而且是按照我们操作的顺序保留的。最主要的是,任务栈帮我们保留了每一个操作过的activity的状态,比如屏幕滚动的位置、我们在可以编辑文本的地方输入的内容等等,这使得我们返回到操作过的页面时,看到页面还是我们刚刚操作时的样子。其实我们也可以通过一些方法让activity在开启时不被放置入任务栈(后面会有介绍),如果一个activity在开启时没有被放入任务栈,那么用户在按下后退按钮或者点击返回按键时,就永远不会再返回到这个activity页面了哦。
通常,设备的Home屏是任务栈开始的地方。因为当用户在Home屏上点击某个应用程序的快捷方式(或者在应用程序启动器中点击它的App图标)时,该应用程序的任务栈就会来到前台。如果此时该应用程序不存在任务栈——即该程序近期没有被打开过,那么一个新的任务栈就会被创建,并且该应用程序中充当主界面的那个activity开启,这个activity就作为这个任务栈的根activity。
【小贴士】什么是application launcher?请见下图:
当一个正在活动的activity开启另一个activity时,新开启的activity(被开启者)会被置于任务栈的栈顶,并且会获得用户焦点。而之前那个activity(开启者)仍被保持在栈中,但它已处于停止状态。 当activity停止时,它与用户的交互状态会被系统保持住。当用户点击返回按钮时,处于前台的activity(被开启者)被系统从栈顶拽出来销毁掉,于是,之前的那个activity(开启者)带着其被停止时的UI状态重新展现在屏幕上了(这个activity的UI状态是被重置的)。无论栈中有多少个activity,它们都不会被重新排序,对于Activity来说,它只会被放进栈中或从栈中被取出(activity被开启时会被放在栈顶;当用户采用点击“返回”按钮的方式离开activity时,它会从栈顶被拽出来并销毁掉)。由此可见,回收栈采用的是一种“后进先出”的存取机制。那么,每当一个新的activity开启时,任务栈里时什么状态?每当用户点击后退按钮时,任务栈又会发生什么变化?下面这张图用时间轴的形式向我们展现每当开启新的activity时以及每当用户点击后退按钮时任务栈的变化:
通过上面这张图,我们可以看到,每当新的activity开启时,它们都是如何被放入回收栈的。当用户点击后退按钮时,当前显示在屏幕上的activity(也就是位于栈顶的activity)会被销毁,它之前的那个activity会重新回到屏幕上(也就是重新成为栈顶)。
如果用户继续点击后退键,那么,每点击一次,系统就会到回收栈中“拽”出位于栈顶的那个activity并将其销。当回收栈里的activity全部被消灭了之后,屏幕上显示的是桌面的Home屏(当然,也有可能是任务栈开启后的首个activity)。注意:如果所有activity都从回收站中拽出来销毁掉了,那么由这些activity组成的任务栈也就不复存在了。
如果用户正在操作某个应用程序时按了HOME键,或者用户在当前应用程序还处于前台的情况下开启了另一个应用程序,那么用户要离开的应用程序所在的任务栈(包含着一个或多个activity)可以整体转移到后台。如果任务栈整体转移到后台,其中所包含的所有activity都将处于停止状态。虽然如此,回收栈会将这个任务栈以及栈中每个activity被停止时的状态都完好无损地保持住。也就是说,对于该任务栈来说,它仅仅是失去了焦点而已,就像下面这张图所展现的:
整体移到后台的任务栈当然也是可以整体重回前台的,这种机制使得用户可以回到之前离开的页面上继续操作。举个例子,假设当前任务栈(任务栈A)中有三个activity,即当前活动的activity下面还压着另外两个activity。用户按下Home键,然后从应用程序启动器(application launcher)中开启了一个新的应用程序,当Home屏显示在屏幕上时,任务栈A就已经移到后台了;当新的应用程序启动时,系统会为这个新的应用程序创建一个任务栈——也就是任务栈B——用于放置新应用程序的activity。用户对这个应用程序完成了操作之后,又回到Home界面,然后选择了之前开启任务栈A的应用程序。这时,任务栈A来到了前台,这个栈中的三个activity都完好无损,位于栈顶的那个activity被重新展现在屏幕上。这时,用户仍然可以通过Home键回到Home屏,然后点击开启了任务栈B的那个应用程序的图标使它再次回到前台。这个例子实际上讲的就是安卓平台的多任务处理。
【小贴士】关于让开启任务栈B的应用程序回到前台的方法,除了通过Home屏之外,还有一种方法,就是通过一个类似于电脑中的任务管理器一样的东西——overview screen——来选择已经开启的应用程序,一般来说,长按Home键可以调出这个界面,但有的手机会单独设置一个用于打开overview screen的按钮。
【注意】在后台可以同时保持多个任务栈。然而,如果用户开了多个应用程序,那么这些应用程序所对应的任务栈都会保持在后台。此时,系统很有可能会开始对后台的activity进行销毁,目的是为了回收内存资源。而这会直接导致activity状态的丢失。
由于回收栈中的activity不会被重新排序,所以如果一个activity已经被打开了,(也就是说它的实例已经存在于回收栈中了,注意,回收栈和任务栈中存放的都是activity的实例哦)那么如果需要再次开启这个activity,已经存在于回收栈中的实例是不会被挪动的。系统会再创建一个该activity的实例并将它置于栈顶。举个例子,用户打开了A, B, C三个activity,顺序是A-B-C。也就是说,activityC位于栈顶,activityA位于栈底。如果用户想要再次打开activityB,会有一个新的B的实例被创建出来并置于栈顶,原来的那个activityB的实例还在回收栈里原地不动,此时,回收栈中是这个样子的:(栈底)A-B-C-B(栈顶)。由此可见,应用程序中的activity不仅有可能被实例化多次,甚至可以在不同的任务栈中被实例化多次。如图所示
所以,如果用户点击后退按钮返回的话,回收栈中的每个activity实例都会按照它们被打开的顺序的逆序展现,而且它们都保持着自己的UI状态。当然,我们也可以让某个activity不被多次实例化。具体方法会在后面的【任务栈的管理】章节中进行讨论。
下面我们来总结一下activity和任务栈的默认行为准则:
* 当ActivityA开启了ActivityB,A就被停止了。但是系统会保留与它相关的状态以及数据(比如屏幕滚动的位置,以及表单中输入的文本)。如果用户在B处于活动状态时点击了后退按钮,那么A就会重新回到前台。而且A再次呈现在用户眼前时的状态,与刚刚被停止时的状态是一样的。
* 当用户按下Home按钮,当前activity就会被停止。同时,它所在的任务栈也被整体移到后台。而该任务栈中的每一个activity的状态和数据都会被系统完好的保存下来。这样,如果过了一会儿用户再次点击该应用程序的图标,这个被整体移到后台的任务栈就会整体回到前台,且位于栈顶的那个activity会被重新显示在屏幕上。
* 如果用户按下了返回键,那么当前的activity就会被系统从回收栈的栈顶拽走并销毁。那么原本位于次顶层的那个activity就变成了栈顶,这就意味着它被显示在屏幕上了。如果一个activity被系统销毁了,系统也就不会再保留它的状态和数据了。
* Activity可以被多次实例化。也就是说,它既可以在同一个回收栈中被实例化多次,也可以在不同的回收栈中多次实例化。
Activity状态数据的保存
--------------------------------------------------------------------------------------------------------------------
正如前面介绍的那样:activity被停止时的状态以及数据都会被系统自动保留。正是因为这样,当用户点击后退按钮回到之前操作过的activity页面时,该activity还保持着用户离开时的样子。然而,我们还是应该主动使用回调方法来保留activity的状态信息,尤其是在activity被销毁或者需要被重新创建(recreate)的情况下——当系统停止某个activity时(比如一个新的Activity开启了或任务栈移到了后台),如果系统需要回收内存资源,那么它就有可能完全销毁这个activity。那么,该activity的状态数据也就随之消失了。虽然activity本身被销毁了,但它在回收栈中所占的位置仍然保留着。但是当这个被销毁的activity所在的位置回到栈顶时,它必须要被重建(是recreate,不是resume哦!)为了避免丢失用户的操作数据,我们必须在activity的类文件中通过实现onSaveInstanceState()回调方法来预先保存这个activity的状态数据。
任务栈的管理
--------------------------------------------------------------------------------------------------------------------
前面已经说过,安卓管理任务栈和回收栈的方式是将activity按照其被开启的先后顺序放在同一个任务栈里,同时也将这些activity放在回收栈里。回收栈的存取模式为“后进先出”,这种存取模式很好地支撑着绝大多数应用程序的activity的运行。而且我们既不用操心activity是如何与任务栈相关联的,也不用管它们是如何在回收栈中存在的。尽管如此,任务栈和回收栈的这种默认行为还是可以人为改变的。比如,我们编写的应用程序中会包含若干个activity,其中某个activity在被开启时需要放在一个新开启的任务栈中(不可以被放入原有的任务栈);再比如,当开启某个activity时,我们可以不让它的新实例在栈顶被创建出来,而是使用该activity的已经存在的实例;再再比如,当用户离开任务栈的时候,我们可以让回收栈中只有根activity(任务栈开启时放进来的第一个activity),而不放置任何其他的activity。想要做到这些甚至更多,有两种方法:1-对manifest文件中
1-可以供我们使用的
taskAffinity
launchMode
allowTaskReparenting
clearTaskOnLaunch
alwaysRetainTaskState
finishOnTaskLaunch
2-可以使用的intent flag主要有:
FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_SINGLE_TOP
【提示】一般情况下,不应该改变应用程序中的activity和任务栈的默认行为模式。但如果真的有必要修改,那么一定要给予提示并对activity在启动过程中的可用性做测试,另外还要保证通过返回键可以从其他activity和任务栈能够返回到这个被改变了默认行为模式的activity。后退按钮的导向轨迹可能会与用户期望的轨迹产生冲突,一定要做好测试工作。
Defining launch modes
Handling affinities
Clearing the back stack
Starting a task
本文总结
所有activity都属于任务栈;任务栈中包含着一系列activity,这些activity在任务栈中的排列顺序取决于用户打开它们的先后顺序——最先打开的位于栈底,后打开的依次向上罗列;任务栈可以移到后台并为其中的每一个activity保持其停止时的状态,目的是为了让用户在操作其他任务栈的同时不丢失他们曾经操作过的activity。