前言
Android 开发者几乎都知道 Activity 有四种启动模式(standard,singleTop,singleTask,singleInstance)。当然,对于刚开始接触 Android 的开发者,可能一开始不了解或不理解,但是工作个一年两年以后,肯定会接触到这个知识点的。网上有太多太多这方面的文章,但是或多或少都不那么准确(当然也有准确的,只是我看到的文章有限……)。最近把四种启动模式,taskAffinity 属性及 FLAG_ACTIVITY_CLEAR_TOP,FLAG_ACTIVITY_NEW_TASK 和 FLAG_ACTIVITY_SINGLE_TOP
这三种Flag 放在一起,仔细的研究了一下,终于弄懂了它们之间错综复杂的关系。
什么是任务(Task)
首先,我们先来简单了解一下在 Android 应用程序中,任务(Task)是个什么样的概念。我们知道,Activity 是 Android 应用程序的四大组件之一,在应用程序运行时,用户为了完成某个功能而执行的一系列操作(页面跳转)就形成了一个 Activity 序列,这个序列在 Android 应用程序中就称之为任务,它是从用户体验的角度出发,把一组相关的 Activity 组织在一起而抽象出来的概念。
Task 是一组相互关联的 Activity 的集合,它是存在于 framework 层的一个概念,控制界面的跳转和返回。这个 Task 存在于一个称为 back stack的数据结构中,也就是说,framework 是以栈的形式管理用户开启的 Activity。这个栈的基本行为是,当用户在多个 Activity 之间跳转时,执行压栈操作,当用户按返回键时,执行出栈操作。
对初学者来说,在开发 Android 应用程序时,对任务的概念可能不是那么的直观,一般我们只关注如何实现应用程序中的每一个 Activity。事实上,Android 系统中的任务更多的是体现是应用程序运行的时候,因此,它相对于 Activity 来说是动态存在的,这就是为什么我们在开发时对任务这个概念不是那么直观的原因。
不同的应用的 Activity 可以在同一个任务里,也就是说 Task 是跨应用的,这样保证了用户操作的连贯性,用户体验比较好。不同任务之间切换,界面会闪一下,不连贯。
神奇的 taskAffinity
不过,我们在开发 Android 应用程序时,可以配置 Activity 的任务属性的,即告诉系统,这个 Activity 是要在新的任务中启动呢,还是在已有的任务中启动,亦或是其它的 Activity 能不能与这个 Activity 共享同一个任务,这个神奇的属性就是 taskAffinity。
在 AndroidManifest.xml ,我们通过属性
android:taskAffinity="july.com.tempdemo.xxx"
可以为每个Activity 设置其 taskAffinity。如果不设置,默认情况下,所有的 Activity 的taskAffinity 都一样,都为app的包名。
这里关于 taskAffinity 我们记住两点就可以:
- 一个任务(Task)的 affinity 由这个任务的根 Activity(root activity)的 taskAffinity 决定;
- 当 Activity 以FLAG_ACTIVITY_NEW_TASK 标志启动时,根据 taskAffinity 来决定它会被启动到哪个任务中。
三个Flag
Intent 类里定义了很多 FLAG,这里我只说三种:
FLAG_ACTIVITY_NEW_TASK
仅仅通过intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);启动的 Activity 即使 task 里已经有实例了,依然会在栈顶创建一个新的实例。FLAG_ACTIVITY_SINGLE_TOP
仅仅通过intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);能够实现 singleTop 启动模式的效果,也就是仅在当前栈里判断实例是否处于栈顶,在栈顶就复用,否则新建一个实例。FLAG_ACTIVITY_CLEAR_TOP
清除目标 Activity上方所有的 Activity,如果目标 Activity 是 standard 的启动模式且启动时 intent 里没有设置FLAG_ACTIVITY_SINGLE_TOP这个flag,那么栈里的目标 Activity 也会被 finish 掉,重新创建一个实例。
如果目标 Activity 是非 standard 的启动模式或者启动时设置了FLAG_ACTIVITY_SINGLE_TOP,就不会清除目标 Activity,执行其 onNewIntent() 方法。
Flag 组合设置:
仅仅通过intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);这两行代码来启动的 Activity,如果栈里已有 Activity 的实例,那么清空此 Activity 及其以上的 activites,然后新建一个实例。仅仅通过intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);能够达到跟 singleTask 启动模式的效果,如果栈里已有实例,则清空其上的 activies,然后走 onNewIntent()。
Activity 的四种启动模式
我们在 AndroidManifest.xml 里为 Activity 设置 launchMode:
standard 启动模式
这是 Activity 默认的启动模式,这种模式下,每次 startActivity 都会在栈顶创建一个新的实例,在同一个任务中可以存在多个Activity 的实例。
singleTop 启动模式
栈顶复用,也就是说,要启动 singleTop 模式的 Activity,如果它恰好在当前栈顶,那么直接复用,执行其 onNewIntent 方法。否则,就重新创建一个实例入栈。
singleTask 启动模式
设置了"singleTask"启动模式的 Activity,在系统中只有一个实例,当再次启动该 Activity 时,会重用已存在的任务和实例,并且会调用这个实例的 onNewIntent()方法,将 Intent 实例传递到该实例中。
设置了"singleTask"启动模式的 Activity 的特点(划重点):
它在启动的时候,会先在系统(所有现存的tasks)中查找属性值 affinity 等于它的属性值 taskAffinity 的任务是否存在;
-
如果存在这样的任务,那么继续在这个任务中查看是否已经存在相应的 Activity 实例。
- 如果存在实例,则会复用这个任务跟实例,finish 掉它上面的所有的 activity ,使其处于栈顶,并执行其 onNewIntent() 方法。
- 如果不存在实例,则在这个任务栈顶创建一个实例。
如果不存这样的任务,那么就会以它的 taskAffinity 新创建一个任务,并在任务里创建一个实例。
因此,如果我们想要设置了 "singleTask" 启动模式的 Activity 在新的任务中启动,就要为它设置一个独立的 taskAffinity 属性值。
下面举几个例子,来体验一下singleTask 启动模式。测试 demo 下载地址:https://github.com/JulyDev/ActivityLaunchMode
ps: 这个 demo 改了很多次,想到什么情况就改一下,来验证自己的想法。因为随机组合条件太多了,不可能一一保存下来。所以最好自己写个 demo 亲自验证一下。
例子1. 有三个 Activity,MainActivity,SecondActivity, ThirdActivity, 其中SecondActivity设置为 singleTask ,其他都是standard启动模式,并且onClick事件里启动时也不加任何 Flag 或 Action。然后 MainActivity 启动SecondActivity,SecondActivity 再启动 ThirdActivity。
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".SecondActivity"
android:launchMode="singleTask"
>
</activity>
<activity
android:name=".ThirdActivity"
>
</activity>
启动 app,依次打开这三个 Activity,结果如下:
可以发现,设置为 singleTask 的 SecondActivity,是跟 MainActivity 在同一个 task 里。为什么呢?因为都没有设置 taskAffinity 啊,默认都是包名,也就是相同的,那么启动 SecondActivity 时,根据 taskAffinity ,找到了MainActivity 所在的task,这里面是没有 SecondActivity 实例的,于是创建一个实例入栈。
例子2. 现在,在例子1的基础上,给 SecondActivity 设置一个不同的 taskAffinity :
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".SecondActivity"
android:launchMode="singleTask"
android:taskAffinity="july.com.tempdemo.second"
>
</activity>
<activity
android:name=".ThirdActivity"
>
</activity>
测试结果如下:
可以看到,此时 SecondActivity 新建了一个 task,不同于 MainActivity 所在的 task。
仔细看我们还发现,由 SecondActivity 启动的ThirdActivity 跟 SecondActivity 在同一个 task 里面。
singleInstance 启动模式
总是在新的任务中开启,并且这个新的任务中有且只有这一个实例,也就是说被该实例启动的其他 Activity 会自动运行于另一个任务中。当再次启动该 Activity 的实例时,会重用已存在的任务和实例。并且会调用这个实例的onNewIntent()方法,将 Intent 实例传递到该实例中。
设置了 singleInstance 的 Activity,整个系统只有一个实例,独占一个栈,且由它启动的 Activity 根据目标 Activity 的 taskAffinity 来选择进哪个 task,若不存在对应的 task,则新建一个 task 并新建一个目标 Activity 的实例入栈。
举例:
ThirdActivity 设置为 singleInstance 模式。其他都为 standard模式。MainActivity 启动 SecondActivity,SecondActivity 启动 ThirdActivity,ThirdActivity 又启动 SecondActivity。
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".SecondActivity"
>
</activity>
<activity
android:name=".ThirdActivity"
android:launchMode="singleInstance"
>
</activity>
测试结果如下:
可以看到,ThirdActivity 单独在一个 task 里,而由 ThirdActivity(singleInstance) 启动的 SecondActivity,根据 affinity 找到了 MainActivity 所在的 task,并且直接在栈顶创建一个实例(不管栈里是否已经存在 SecondActivity 的实例)。这里跟 singleTask 模式不一样。设置了 singleTask 模式的 Activity, 启动的普通 Activity ,直接就放在当前栈顶了(见singleTask 启动模式一节的例子2)。
参考资料:
1.http://m.blog.csdn.net/luoshengyang/article/details/6714543
2.http://blog.csdn.net/zhangjg_blog/article/details/10923643