Activity任务栈和启动模式

Activity任务栈

一个APP通常有多个Activity,这些Activity通过栈结构保存,栈底是整个任务栈(Task)的发起者。

当一个APP启动时,如果系统中不存在该APP的任务栈,那么系统会为其创建一个任务栈,此后,这个APP启动的所有Activity都将在这个任务栈中被管理。需要注意的是,一个Task中的Activity可以来自不同的APP,同一个APP的Activity也可能存在于多个Task中。

在Task中,一个Activity启动另一个Activity时,新的Activity会被置于栈顶,并处于活动状态,而启动它的Activity依然保留在Task中,处于停止状态,当用户按返回键或者调用finish()方法时,系统会弹出栈顶的Activity,让后面的Activity从停止状态恢复到活动状态。以上是Task管理Activity的一般流程,当需要特殊处理时,就需要使用Activity的启动模式。

通过Manifest声明启动模式

可以在AndroidManifest文件中,为Activity声明启动模式,一共能声明四种启动模式:standard、singleTop、singleTask、singleInstance,下面分别进行介绍。

standard

默认的启动模式,每次启动Activity都会创建一个新的实例。在该模式下,Activity可以拥有多个实例,并且这些实例既可以位于同一个Task,也可以位于不同的Task。

singleTop

在singleTop模式下,启动一个Activity时,系统会判断当前Task中的栈顶Activity是否是要启动的Activity,如果是则不创建新的Activity,然后直接引用这个Activity,并调用其onNewIntent()方法;如果不是,则新建该Activity的实例,并将其置于栈顶。

因此,在同一个Task中,声明为singleTop模式的Activity也会存在多个实例,只不过这些实例不会相邻。

singleTask

在singleTask模式下,只允许一个包含该Activity实例的Task存在。

singleTask模式下,Activity的启动方式与android:taskAffinity相关。当B是singleTask模式时,以A启动B来说:

  • 当A和B的taskAffinity相同时:第一次创建B的实例时,并不会启动新的Task,而是直接将B添加到A所在的Task;否则,将B所在Task中位于B之上的全部Activity都销毁,然后跳转到B中,并调用其onNewIntent()方法。

  • 当A和B的taskAffinity不同时:第一次创建B的实例时,会启动新的Task,然后将B添加到新建的Task中;否则,将B所在Task中位于B之上的全部Activity都销毁,然后跳转到B中,并调用其onNewIntent()方法。

注意,当要启动的Activity在其它APP的Task中,会在返回栈(Back Stack)中将要启动Activity所在的Task置于当前Task之前,具体过程如下图所示:

singleTask启动模式

singleInstance

singleInstance模式下,任意时刻只允许存在唯一的Activity实例,而且该Activity所在的Task不能容纳除该Activity之外的其他Activity实例。其它Activity要启动该Activity,只需要打开这个分离的Task,并调用其onNewIntent()方法。

singleInstance模式经常用于需要与应用分离的界面,该模式的Activity实例即使启动其它Activity,这些Activity也会自动运行于另一个Task中。

singleInstance与singleTask一个明显区别是:singleTask所在的Task中能有其它的Activity,而singleInstance的Task中不能有其他Activity。

注意

如果ActivityA处于singleTask或singleInstance启动模式,当ActivityA通过startActivityForResult()方法启动另一个ActivityB时,将会直接返回RESULT_CANCELED,而不会等待ActivityB返回。因为Android系统不允许不同的Task之间直接传递数据,如果有这样的需要,只能通过onNewIntent()方法使用Intent传递。

通过Intent Flags声明启动模式

除了在AndroidManifest文件中声明启动模式,还可以在启动Activity时,通过Intent中声明启动模式,这种Flag存在多个,下面介绍几个常用的启动Flag。

  • Intent.FLAG_ACTIVITY_NO_HISTORY

如果一个Activity是由该模式启动,当这个Activity再去启动其它Activity后,这个Activity就会被销毁,不会保留在Task中。

  • Intent.FLAG_ACTIVITY_SINGLE_TOP

这个Flag的效果与singleTop启动模式的效果相同。

  • Intent.FLAG_ACTIVITY_NEW_TASK

这个Flag的效果会受到taskAffinity属性影响,当B声明为该模式,以A启动B来说:

当A和B的taskAffinity相同时,与standard模式效果一样,每次都在当前Task新建B的实例。

当A和B的taskAffinity不同时,如果是首次启动,会新建一个Task,并将该Activity实例添加到Task中;如果再次启动,只会把该Activity所在Task置于前台,但是不会将该Activity置于栈顶,也不会调用其onNewIntent()方法。

该Flag通常用于其它组件启动一个Activity,因为其它组件不存在任何Task中,可能需要新建一个Task管理Activity实例,比如,使用Service启动Activity的场景。不过,当系统存在一个与其taskAffinity属性相同的Task时,这时就不会新建Task,而会直接在这个Task中新建Activity实例。

  • Intent.FLAG_ACTIVITY_CLEAR_TOP

如果要启动的Activity在当前的Task中,会销毁该Activity之上的所有其它Activity,从而将其置于栈顶;否则,在当前Task新建Activity实例。

不过,当要启动的Activity同时被声明为standard模式时,该Activity实例也会跟着其它Activity从栈顶弹出,然后重新创建该Activity。之所以这样,是因为standard模式下,总是创建新实例响应Intent。

总的来说,该模式只会清空栈顶,是通过新建实例还是通过onNewIntent()响应Intent启动消息,要看该Activity的其它启动模式。

通常将FLAG_ACTIVITY_CLEAR_TOP与FLAG_ACTIVITY_NEW_TASK混合使用,来定位存在于其他Task中的Activity实例,并将其置于栈顶。

  • Intent.FLAG_ACTIVITY_CLEAR_TASK

这个Flag用来清空Task,单独使用没有什么效果,通常与FLAG_ACTIVITY_NEW_TASK混合使用,这时会受到taskAffinity属性影响,当B声明为该模式,以A启动B来说:

当A和B的taskAffinity相同时,清空当前Task,并在当前Task中新建Activity实例。

当A和B的taskAffinity不同时,如果首次启动,新建Task,并在新Task新建Activity实例;如果再次启动,则清空实例所在的Task,再新建Activity。

注意

通过Intent Flags声明的启动模式能够覆盖通过Manifest文件声明的启动模式,因此,如果它们之间有冲突时,以Flags为准。

Task相关的属性

除了用启动模式可以控制Task,还可以使用Task相关的属性控制Task,这些属性在Manifest文件的Activity节点声明,常用的有:

1
2
3
4
5
android:taskAffinity="string"
android:allowTaskReparenting=["true" | "false"]
android:alwaysRetainTaskState=["true" | "false"]
android:clearTaskOnLaunch=["true" | "false"]
android:finishOnTaskLaunch=["true" | "false"]

处理Affinity

  • taskAffinity

介绍启动模式的时候已经提到过这个属性。每个Activity都有taskAffinity属性,这个属性指出了它希望进入的Task。如果一个Activity没有显式的指明该Activity的taskAffinity,那么它的这个属性就等于Application指明的taskAffinity,如果Application也没有指明,那么该taskAffinity的值就等于包名。而Task也有自己的affinity属性,它的值等于它的根Activity的taskAffinity的值。

  • allowTaskReparenting

是否允许activity重新指定Task,默认值是false。

如果一个Activity的allowTaskReparenting设置为true,它进入后台后,当一个和它有相同taskAffinity属性的Task进入前台时,它会重新宿主,进入到该前台的Task中。

清空任务栈

一般来说,如果一个Task很长时间没有被用户使用,那么系统会清空这个Task中除了栈底Activity(Root Activity)之外的所有其它Activity,当用户重新进入这个Task时,只有Root Activity会被恢复。可以使用以下属性修改系统的这个默认行为。

  • alwaysRetainTaskState

默认值是false,如果将Root Activity的这个属性设置为true,那么该Activity所在的Task将不会被清空,即使很长时间不使用,这个Task中所有Activity的状态都会被保存。

  • clearTaskOnLaunch

默认值是false,如果将Root Activity的这个属性设置为true,不管用户什么时候离开该Activity所在的Task,系统会立刻清空这个Task中除了Root Activity之外的所有其它Activity,这个行为与alwaysRetainTaskState刚好相反。

  • finishOnTaskLaunch

默认值是false,如果某个Activity的finishOnTaskLaunch属性设置位true,只要你一离开该Activity所在的Task, 系统会立刻清除这个Activity, 不管这个Activity在Task的任何位置。

坚持原创技术分享,您的支持将鼓励我继续创作!