任务和回退栈

时间:2021-10-31 02:20:16

任务和回退栈

 

任务(task)是为了完成某种工作而有序地组合在一起的Activity集合。这些Activity可能来自一个应用,也可能来自多个应用。这些Activity按照被打开的顺序被保存在回退栈(back stack)中。

任务通常开始于桌面,当桌面的应用图标被点击时,应用任务来到前台。如果对应的应用的任务还没有,给此应用创建一个新的任务,并且将“main”Activity作为回退栈的根Activity。

当一个Activity打开另一个时,新的Activity将被压入回退栈的栈顶并且获得焦点,老的Activity将保存在回退栈中并且已成stopped状态。当Activity为Stopped状态时,Android系统会保留它UI状态,当点击回退键后,当前Activity会被移除回退栈并销毁,停止状态的Activity便可以恢复到原来的状态。在回退栈中的Activity永远不会重新排序,只有压入、弹出栈。当前的Activity开启一个新的Activity时,新Activity被压入栈;当用户按返回键时,栈顶Activity被弹出栈。回退栈是个“后进先出”数据结构。

上图以时间顺序展示了Activity与回退栈在各个时间点的状态,以及压入栈和弹出栈的过程。

 

如果用户一直点击返回键,回退栈中的Activity不断被弹出直到用户返回到开启此任务的地方。当回退栈中没有任何Activity时,任务便不存在了。

任务是一个粘性的单元,当用户按home键或者开启了一个新的任务时,整个被搬到后台。当任务被放到后台后,任务已经停止但是任务中的回退栈没有被破坏,只是此任务失去了焦点另一个获得了焦点。任务是可以从后台再次回到前台的。例如,当一个任务A中包含三个Activity,此时返回到桌面,然后又开启一个任务B(此时任务A在后台)。当再次回到任务A时,任务A的回退栈最顶部的Activity回复状态。当然还可以将任务A放到后台,然后在桌面点击开启任务B的图标使任务B重新回到前台。这个只是Android多任务处理的一个例子。

注意:同一时间后台可以有多个任务。但是当用户运行太多的任务时,为了回收内存系统可能会销毁那些后台运行的Activity,导致Activity的状态丢失。

因为Activity在回退栈中不能被重新排序,如应用中某Activity可以在几处地方被开启,所以此Activity的将会被多次创建并压入栈顶(不是把之前的对象挪到栈顶)。当用户点击返回键时,此Activity的实例将会按照被打开的反序依此被移除销毁。在接下来的内容中将会提到如何避免被多次实例化的问题。

 

Activity和任务的默认行为:

1.             当ActivityA打开ActivityB时,ActivityA停止但是系统依然保留着它的状态(滚动位置、输入内容等)。当在ActivityB下按返回键回退到ActivityA时,ActivityA又恢复到原先状态。

2.             当用户按home键回到离开一个任务时,当前的Activity停止并且它的宿主任务进入后台。系统将会保存任务中的每一个Activity的状态。如果稍后用户又点击了开启此任务的图标,任务返回到前台并且恢复回退栈顶部的Activity。

3.             Activity可以被实例化多次,甚至在多个任务中。

 

管理任务

       使用<activity>中的属性管理

·        taskAffinity

·        launchMode

·        allowTaskReparenting

·        clearTaskOnLaunch

·        alwaysRetainTaskState

·        finishOnTaskLaunch

 

         使用Intent中的标志管理

·        FLAG_ACTIVITY_NEW_TASK

·        FLAG_ACTIVITY_CLEAR_TOP

·        FLAG_ACTIVITY_SINGLE_TOP

定义启动模式

       启动模式允许应用决定将被实例化的Activity与当前任务的关系。

有种方式:

1.      用清单文件定义

2.      用Intent标志定义

ActivityA开启ActivityB时,ActivityB可以在它的清单文件定义启动模式;在ActivityA中开启B的Intent设置flag设置启动模式。如果在清单文件和Intent中都有启动模式,以Intent中的为准(Intent中的启动模式的优先级高于清单文件的)。

 

a.使用清单文件

         使用清单文件<activity>元素中的launchMode指定启动模式(指定Activity与任务关系)。四种模式:

                   1."standard"(默认模式)

Activity可以被创建多次,(多个)实例可能在多个任务中,一个任务中也可以有多个实例。

2."singleTop"

当有实例在当前任务的栈顶,系统不会创建新的实例而是通过onNewIntent()将Intent传递给栈顶实例。Activity可以被创建多次,(多个)实例可能在多个任务中,一个任务可能包括多个实例。例如一个任务的回退栈中有A-B-C-D四个Activity(D在最顶端),此时有一个开启D的Intent。如果D为"standard"的启动模式,启动后栈中就变为A-B-C-D-D了;如果D为"singleTop"

启动模式,Intent会传递给ActivityD的onNewIntent()而不会创建新的D,启动后栈中依然为A-B-C-D。如果此时一个开启B的Intent,即使B是"singleTop",ActivityB的新实例依然会被创建。

注意:如果Activity是被创建出来的,用户可以通过按返回键回到之前的Activity;但是如果是用onNewIntent()复用Activity,按返回键不能返回到之前的状态。

        3. "singleTask"

系统为此Activity创建一个新的任务并且将其放到栈顶。在一个单独的任务中已经存在此Activity的实例,系统不再为其创建新的实例而是通过onNewIntent()复用。同一时刻内系统中最多只有一个实例。注意:尽管Activity是在一个单独的任务中被打开的,点击返回键仍然可以返回之前的Activity。

4."singleInstance"

   包含此Activity的任务不能包含其他的Activity,这是与singleTaskd的唯一区别。任务中有且仅有一个此Activity的实例;所有通过此Activity打开的Activity都在一个单独的栈中。

 

Android浏览器应用指定浏览网页的Activity为singleTask启动模式,声明此Activity在它自己的任务中打开。这意味着,你的应用打开打开Android浏览器时,对应的Activity并没有在你的任务中而是在浏览器的任务中。当浏览器没有启动时,系统会为浏览器开启一个新的任务;当浏览器启动但是在后台,此时会将浏览器任务调到前台来。

    无论新开启的Activity是在一个新的任务中还是在已有的任务中,按返回键总是能返回到之前的Activity。当开启的Activity启动模式是singleTask并且在后台的某任务中已有实例,此时会将后台的整个任务搬到前台来并且回退栈中包含了此任务中所有的Activity(开启的Activity在栈顶)。

 

任务和回退栈

         上图展示了singleTask启动模式的Activity是怎样被加到回退栈的。如果Activity是后台任务的一部分并且有自己的回退栈,开启Activity时会将整个回退栈移到当前任务中,并且其放到栈顶。注意:Intent中用flag指定的启动模式可以覆盖掉清单文件中的启动模式。

 

b.使用Intent标志(flag)

         使用Intent的flag可以改变将要开启的Activity与当前任务的默认关系(启动模式的真正含意就是Activity与开启它的任务关系)。

标志:

         FLAG_ACTIVITY_NEW_TASK

与在清单文件中的singleTask启动模式表现一致。系统给Activity新建一个新的任务;如果将要被打开的Activity在后台任务中,系统会将宿主任务移到前台来并且通过onNewIntent()开启Activity。

FLAG_ACTIVITY_SINGLE_TOP

与在清单文件中的singleTop启动模式表现一致。如果在回退栈的最顶部,系统不会创建新的Activity实例;如果没有则创建新的。

FLAG_ACTIVITY_CLEAR_TOP

如果被启动的Activity在当前的任务中,系统不会创建新的实例,而是将在栈中在此Activity之上的Activity全部移除并销毁,通过onNewIntent()恢复Activity(此时被开启的Activity在栈顶)。清单文件launchMode属性没有对应的值。

这几个标志可以组合使用。如,FLAG_ACTIVITY_CLEAR_TOP和 FLAG_ACTIVITY_NEW_TASK经常被组合在一起。

 

Affinity(家族)

Affinity指示一个Activity属于哪一个任务。默认情况下,一个应用中所有的Activity拥有一个相同的affinity,所以所有的Activity都在同一个任务中。Activity的默认affinity是可以更改的。不同应用的Activity可以使用相同的affinity,同一个应用中的不同Activity也可以是不同的。通过修改清单文件<activity>元素的taskAffinity属性变可以更改Activity的affinity。

如果设置taskAffinity的属性时,不要与报名使用相同的名字。因为默认属性值就是包名,如果设置为包名就无法区别了。

 

两种应用场景:

1.使用带有FLAG_ACTIVITY_NEW_TASK的Intent打开Activity时。

通常情况下,被打开的Activity会被压入打开者所在的栈顶。如果通过带有FLAG_ACTIVITY_NEW_TASK的Intent打开Activity,Android系统会为Activity寻找一个新的宿主任务。如果已有一个与Activity有相同affinity的任务存在,Activity会被归为此任务;如果没有则为Activity创建一个新任务。

2.如果Activity的allowTaskReparenting属性设置为true。

         在此情况下,当与它有相同affinity的任务回到前台时,它可以从打开它的任务中回到有相同affinity的任务中。例如,一个旅游应用中有一个预报天气的Activity,它的affinity与应用中其他的Activity的affinity相同(应用默认affinity),并且它的allowTaskReparenting属性设置为true。当你的应用打开此Activity时,它与你的应用在同一个任务中。如果旅游应用重新回到前台时,Activity便会回到旅游应用的任务并显示出来。

         Ps:可以使用taskAffinity给一个应用中的不同Activity设置不同的属性值,达到用户安装一个应用感觉安装了几个应用的效果。

 

清理回退栈

当用离开一个任务较长时后,系统会清理任务中除根Activity的其他的Activity。当用户再此回到任务时,只有根Activity可以恢复。系统之所以这么做,是因为一段时间后用户可能开启了新的任务而不再回到之前的任务。

Activity几个可以改变此默认行为的属性:

1.       alwaysRetainTaskState

如果任务根Activity属性值设置为true,系统会长时间保存任务中的其他Activity状态。

2.       clearTaskOnLaunch

alwaysRetainTaskState正好相反,如果任务的根Activity设置此属性为true,只要任务一离开系统会清除掉除根Activity的所有Activity。

3.       finishOnTaskLaunch

clearTaskOnLaunch类型,不做此属性只作用在单个Activity而不是整个任务。此属性既可影响根Activity也可以影响其他Activity。如果此属性设置为true,Activity只存留在本任务中。用户离开任务再回到任务时,Activity已经不在存在。