参考:
Android中Activity四种启动模式和taskAffinity属性详解
Activity启动模式图文详解
AndroidDeveloper任务和返回栈
《Android开发艺术探索》
虽然类似的文章很多很多,但是我希望你能认真看下去,你可能会发现一些与众不同的操作和理解。
一、了解和查看任务和返回栈
事先了解任务和返回栈的概念和查看任务栈的方式可以有助于我们深入理解启动模式带来的作用。
任务是指在执行特定作业时与用户交互的一系列 Activity。 这些 Activity 按照各自的打开顺序排列在堆栈(即返回栈)中。
我们一般任务和返回栈统称为"任务栈"。
而任务栈有前台任务栈和后台任务栈之分,目前你可以粗浅的理解为前台的App和后台的App,因为通常情况下一个App只拥有一个任务栈。
通过 adb shell dumpsys activity activities
可以查看任务栈的情况,但是这个命令的输出结果信息太多,想看各个任务栈的情况很费劲,这里就不贴出来了,你们可以试试。
我们可以通过grep命令过滤出自己想要的信息:
adb shell dumpsys activity activities "| grep '* [TH]'"
过滤如下结果:
TaskRecord就是每一个任务栈,可以很清晰的看到每一个栈中包含的Activity,这里都是按照顺序排列的,无论是任务栈的顺序还是Activity的顺序,最顶层是我写的一个Demo的任务栈,最底层则是手机系统的任务栈。
A=xxx.xxx.xxx则是该任务栈的名字,默认情况下同等于包名。
另外有一个事情需要额外提醒下,如果只使用adb shell dumpsys activity activities
命令查看,你可能在输出中看到以下信息:
不要认为这些信息就是你想要的,仔细看一下Activity的顺序,这是按照打开顺序排列的,而不是按照实际的回退栈顺序排列。
在《Android开发艺术探索》这本书中,使用的示例图片就是上图的Running activities,这用于展示操作单个任务栈的情况下是没有问题的。但是如果有多个任务栈,或者在多个App切换的的时候,就无法适用了,请各位谨记……
二、Activity四种启动模式简单说明
- Standard:默认的方式,任务栈中可以有多个并且连续的相同的Activity实例。
- SingleTop:如果Activity已存在该任务栈的栈顶,不会重复创建新的Activity实例,而是发送Intent给它的onNewIntent()方法。可以拥有多个相同的Activity实例,只要不是它连续的。
- SingleTask:如果任务栈内已经存在该Activity,不会重复创建新的Activity实例,而是将该Activity顶上的所有activity结束,并且回调他的onNewIntent()方法。一个任务栈中不会出现重复的Activity实例。
- SingleInstance:与 "singleTask" 相同,只是系统不会将任何其他 Activity 启动到包含实例的任务中。该 Activity 始终是其任务唯一仅有的成员;由此 Activity 启动的任何 Activity 均在单独的任务中打开。
三、SingleTask启动模式
在前文,我们仅仅时描述了SingleTask在没有设置taskAffinity属性的情况。如果该Activity设置了taskAffinity属性,那么正确的描述则是(三种情况):
- 如果不存在taskAffinity指定的任务栈,系统会创建新的任务栈,并实例化 Activity将他放到新任务栈的底部
- 如果存在taskAffinity指定的任务栈,但是不存在该Activity实例,系统会实例化Acitivity压入改任务栈中
- 如果存在taskAffinity指定的任务栈,并且存在该Activity实例,那么系统会结束掉该Acitivty顶上所有的其他Activity实例,并将Intent传入该Activity的onNewIntent()中。
taskAffinity是配置在清单文件中的Activity标签的一个属性,它可以指定Activity所在的任务栈。
taskAffinity以包名的方式命名,默认和包名相同。
taskAffinity必须和SingleTask启动模式一起使用,或者设置Intent的Flag_ACTIVITY_NEW_TASK才能起作用,否则还是在当前的任务栈中创建Acitivty实例。
下面我将举一个特殊的例子再次说明任务栈的特性——前台任务栈和后台任务栈
当我们App中存在两个任务栈时,必然会出现一个在前台一个在后台的情况。假设此时前台任务栈中有ActivityA和ActivityB,后台任务栈有ActivityX和ActivityY,如图:
此时Activity的回退的顺序为 A - B - X - Y,假设我们现在启动ActivityX,那么此时回退栈的顺序时什么呢?会是X - A - B - Y吗?还是说只有X - Y?
当以SingleTask方式启动另一个任务栈中的Activity的时候,系统会将整个任务栈切换到前台,而原来的任务栈则退到后台。如图:
所以,目前Acitivty的回退顺序应该是X - Y - A - B。
假设我启动的是ActivityY呢?
那么系统会结束掉ActivityX,并且将TaskB切换到前台,所以Activity的回退顺序为Y - A -B,如图:
ps:切换任务栈的时候,动画效果类似于切换App。和普通的activity跳转动画有区别的,如果应用中有两个任务栈,你可能需要处理以下Activity的跳转动画看起来更美观。
还有一个很重要的特性需要说明,不同应用的Acitivity也可以存在同一个Task中:
比如我在我的Demo中以SingleTask启动一个SecondActivity,taskAffinity设为QQ的应用包名:
没错,我们看到了什么???我们应用的Activity跑到QQ的任务栈去了!
假设我们在SecondActivity启动的时候就finish掉,那么你就会看到,我们在自己的应用中启动了QQ!
当然,这种骚操作需要QQ已经在后台运行之后才行。
四、SingleInstance启动模式
根据说明,SingleInstance会具有以下两个特点:
- 以SingleInstance模式启动的Activity,在整个Android系统中只会存在一个实例。
- 以SingleInstance模式启动的Activity,会独占一个任务栈,被该Acitivty启动的其他Activity会分配到其他任务栈中,可能是新的任务栈,也可能是已经存在的任务栈(根据被启动的Activity的taskAffinity判断)
不过如果以SingleTask模式的Activity,如果不设置taskAffinity属性,那么该任务栈的名字还是会和应用包名一致。你将会看到两个名字一样的任务栈,由此可见任务栈并不是以名字作为唯一标识的。
如图:两个任务栈的名字都为com.aitsuki.task,但是他们的编号不一样。
这会导致一个很特殊的情况出现,假设你应用中有A,B两个Activity,B是以SingleInstance方式启动的,没有设置taskAffinity,这时候出现了两个相同名字的任务栈。
当你按HOME键将应用退到后台,会出现两种特殊问题:
- 你通过最近使用列表开启应用,感觉好像没问题,显示在眼前的就是ActivityB,但是当你按返回键返回的时候,你会发现直接退到桌面了,ActivityA不见了。
- 你点击应用图标开启应用,你会发现显示的是ActivityA,而ActivityB不见了。
通过设置taskAffinity后可以一定程度上解决这两个问题,虽然你通过应用图标打开应用显示的还是ActivityA,但是你打开最近使用列表就会发现当中有两个我们的App,其中一个是ActivityA,一个是ActivityB。
同样,如果我们把taskAffinity设置成QQ的包名,那么将导致你无法在最近使用列表中看到QQ了,当点击QQ图标打开QQ后,你会发现ActivityB又消失不见了。
这个启动模式真的很少很少情况下会用到,我也不太了解具体使用场景,可能做系统UI定制方面的同学会使用到,比如Launcher。也可能是早期的一些应用锁App的解锁界面(如360应用锁)。
五、常用的几个Intent标记
FLAG_ACTIVITY_SINGLE_TOP
和清单文件中SingleTop启动模式一样的效果。FLAG_ACTIVITY_CLEAR_TOP
使用这个Flag会清除目标Activity顶上的其他Activity,但是会连目标Activity也一起清除重新创建。所以配合FLAG_ACTIVITY_SINGLE_TOP使用,不会重新创建实例,而是回调他的onNewIntent()方法。
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP)
FLAG_ACTIVITY_NEW_TASK
和SingleTask有点不一样,FLAG_ACTIVITY_NEW_TASK只是起到标记Activity可以在新的任务中启动,不具备清除任务栈中Acitvity顶上的其他Activity功能。
所以一般情况下还需要配合FLAG_ACTIVITY_CLEAR_TOP加上FLAG_ACTIVITY_SINGLE_TOP三个Flag一起使用,可以实现和SingleTask相同的作用。
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP)
一般情况下,我们可能需要一个退出App的功能,大多数实现方式可能是在BaseActivity中记录所有打开的Activity逐个结束。
但是我们可以通过设置IntentFlag跳转到根Activity,在onNewIntent中调用finish(),这样就可以完美的结束App。
也可以通过onNewIntent打开其他页面,这几乎是一个完美的跳板。
六、allowTaskReparenting
当启动 Activity 的任务接下来转至前台时,Activity 是否能从该任务转移至与其有亲和关系(Affinity)的任务 —“true”表示它可以转移,“false”表示它仍须留在启动它的任务处。
例如,如果电子邮件包含网页链接,则点击链接会调出可显示网页的 Activity。 该 Activity 由浏览器应用定义,但作为电子邮件任务的一部分启动。 如果将其父项更改为浏览器任务,它会在浏览器下一次转至前台时显示,当电子邮件任务再次转至前台时则会消失。