前言
本篇博客主要介绍Activity的几种启动模式的区别,也许有人会不屑一顾,觉得这是基础中的基础,不就那么四种启动模式么?不过这四种启动模式中的弯弯道道还是有很多的,只知道四种启动模式是不够的,还需要知道其中的细节。
在理解四种启动模式之前你需要知道的东西
Q:如何设置Activity的启动模式
A:通过配置AndroidManifest.xml中<activity android:launchMode="" >标签,还可以通过在启动Activity的Intent中通过intent.addFlags(flags)添加启动flags(这个在最后面讨论)
Q:什么是Activity任务栈
A:Activity任务栈可以看做与用户交互的一系列 Activity。 这些 Activity 按照打开的先后顺序排列在一个栈结构中,用户按下返回键一个个回退,直到返回到桌面,任务栈中可以放置非自身的Activity,比如在自己的应用中打开浏览器,若无特殊配置,那么浏览器所在的Activity会放置在本应用的任务栈中。
Q:怎么判断当前任务栈
A:在Activity中通过getTaskId()即可获取到任务栈的id,这个id是递增的。
Q:系统怎么知道哪些Activity放置在一个任务栈中,如果我打开一个应用,然后从桌面打开另一个应用,是一个任务栈么?
A:后面一个问题的答案是:不是同一个任务栈,一般来说,如果没有特殊的设置启动模式,一个应用只有一个任务栈,"栈名"默认为包名,可以通过<activity android:taskAffinity="" >属性进行设置,当应用启动以后,系统会创建一个"栈名"为包名的任务栈,以后本应用的Activity以及被本应用启动的其他Activity均会被放置在这个任务栈中,注意:<activity android:taskAffinity="" >如果要生效,需要将Activity的启动模式设置为singleTask或者singleInstance。或者在启动Activity的Intent中加入FLAG_ACTIVITY_NEW_TASK标记即可。
四种启动模式
首先贴出官方文档上面关于四种启动模式的介绍,这也是大多数人知道的部分,就不详细介绍了。
用例 | 启动模式 | 多个实例 | 注释 |
大多数 Activity 的正常启动 | standard | 是 | 默认值。系统始终会在目标任务中创建新的 Activity 实例并向其传送 Intent。 |
singleTop | 有条件 | 如果目标任务的顶部已存在一个 Activity 实例,则系统会通过调用该实例的onNewIntent() 方法向其传送 Intent,而不是创建新的 Activity 实例。 | |
专用启动(不建议用作常规用途) | singleTask | 否 | 系统在新任务的根位置创建 Activity 并向其传送 Intent。 不过,如果已存在一个 Activity 实例,则系统会通过调用该实例的 onNewIntent() 方法向其传送 Intent,而不是创建新的 Activity 实例。 |
singleInstance | 否 | 与singleTask相同,只是系统不会将任何其他 Activity 启动到包含实例的任务中。 该 Activity 始终是其任务唯一仅有的成员。 |
表格来自:Google文档
上表中模式分为两大类,“standard”和“singleTop”Activity 为一类,“singleTask”和“singleInstance”为另一类。使用“standard”或“singleTop”启动模式的 Activity 可多次实例化。 实例可归属任何任务栈,并且可以位于 Activity 堆栈中的任何位置。 它们通常启动到名为 startActivity() 的任务之中(除非 Intent 对象包含 FLAG_ACTIVITY_NEW_TASK 指令,在此情况下会根据taskAffinity属性选择其他任务)。相比之下,“singleTask”和“singleInstance”Activity 只能启动任务。 他们始终位于任务栈的栈顶,如果栈中已经存在实例,那么会将此实例上面的所有Activity出栈。
个人理解时间
好了,上面copy了一大堆官方文档,估计如果有人看到这里就晕晕乎乎了,下面就说说个人通过写Demo得出的个人理解。
standard模式
这种模式为Activity的默认启动模式,在这种模式下,启动一次Activity,那么就会创建一个Activity实例,并放到当前的任务栈中。效果如下,多次启动,生成了多个实例。
singleTop模式
这种模式叫栈顶复用模式,如果当前要启动的Activity在栈顶,那么此Activity不会继续启动,而是调用此Activity的onNewIntent方法,如下图
如果要启动的Activity不在栈顶,那么则和standard一样,创建一个新的实例。如下图
singleTask模式
这种模式叫栈内复用模式。是指任务栈中只能存在一个此Activity,如果此Activity已经存在,那么会调用其onNewIntent方法,同时如果此Activity不在栈顶,那么将会将它前面的所有Activity出栈。
如下所示,id为9的栈中有一个MainActivity的启动模式为singleTask,然后启动一个MainActivity2,让MainActivity不在栈顶,这时在MainActivity2中启动MainActivity,不会直接创建MainActivity,而是调用了MainActivity的onNewIntent,并将MainActivity2出栈。
那么问题来了,栈内复用,到底是怎么选择哪一个栈呢?
在最上面的问题中其实已经提到了,使用singleTask模式启动Activity,首先会去找"栈名"相同的栈,然后进行栈内复用,如果找不到那就根据<activity android:taskAffinity="" >属性创建一个新任务栈,并将自己放入。
回到上图中来,MainActivity的启动模式为singleTask,但是我没有设置android:taskAffinity属性,那么默认为应用包名,当在MainActivity2中启动MainActivity的时候,会先去寻找相同"栈名"的任务栈,很明显,MainActivity与MainActivity2在同一个任务栈中,所以直接进行栈内复用,将MainActivity2出栈了。
下面来验证下找不到"同名"任务栈的情况:
如下图,将MainActivity2设置为singleTask启动模式,并且设置MainActivity2的android:taskAffinity属性为"com.other",与包名不同,当启动MainActivity2的时候,创建了一个新的任务栈用来放置MainActivity2。
通过MainActivity2启动的Activity也会放置在自己的栈中。
简单来说:singleTask指定了单独的任务栈则在单独的任务栈中启动,如果没有单独设置任务栈,则在当前任务栈显示,并且如果不在当前栈顶的话,会销毁此acivity所在栈之前的全部activity并让自己在栈顶显示。
singleInstance模式
singleInstance模式可以看做加强版的singleTask模式,不同之处在,他只允许任务栈中存在他自己。并且不设置android:taskAffinity属性,也会新创建一个任务栈。
如下图所示,MainActivity启动了MainActivity2,处理过程同singleTask模式,这时在MainActivity2中启动MainActivity,MainActivity不在MainActivity2所在的栈里,保证了MainActivity2所在的任务栈只有它自己。
通过FLAG_ACTIVITY_*标记去启动Activity
上面我介绍的四种启动模式,都是在AndroidManifest.xml中直接进行配置的,但是这样很不灵活,下面就来介绍下通过intent.addFlags(flags)配置的方法来动态设置启动模式。
根据官方文档,一共可以使用FLAG_ACTIVITY_NEW_TASK、FLAG_ACTIVITY_CLEAR_TOP、FLAG_ACTIVITY_SINGLE_TOP三种FLAG。
FLAG_ACTIVITY_NEW_TASK
在新的任务栈中启动Activity。
FLAG_ACTIVITY_SINGLE_TOP
如果待启动的Activity在栈顶,那么久不创建新的实例,而是调用其onNewIntent方法,如果不在栈顶,那么就创建一个实例。
FLAG_ACTIVITY_CLEAR_TOP
使用此标识,会造成所有Activity全部出栈,并且创建一个新的实例放在栈中。不管待Activity是否在栈顶。
参考链接:
https://developer.android.com/guide/components/tasks-and-back-stack.html
https://developer.android.com/guide/topics/manifest/activity-element.html