activity启动模式是android开发中的基础知识,面试中也经常被问到,不过刚入门的时候看到网上众多介绍activity启动模式的文章,看过一遍感觉都懂了,实际碰到具体的场景有时候还是有点懵逼。
现在对这块的知识用的多了,也算有了一些心得,尝试着用简单的方式总结备忘下。
这篇文章主要是对于activity启动模式的相关知识的一个规律总结,基础的知识点会介绍的比较简略,适合已经大致了解了activity的启动模式,但对于具体场景分析还不太熟练的同学,看完这篇文章你应该就能按照一个既定的套路去分析activity的启动模式了
基础知识点
Task
- 一个后进先出的栈结构,用来保存调度activity
启动模式
standard
默认的启动模式
- 默认只在当前task启动
- 启动时会新建一个activity实例放到栈顶
singleTop
栈顶复用模式
- 默认只在当前task启动
- 当当前task的栈顶的已经有一个相同的Activity在栈顶了,则不会创建新的activity,而是会调用栈顶activity的
onNewIntent()
方法 - 栈顶不存在相同Activity时,会新建一个activity实例放到栈顶
singleTask
- 默认可以在其他task启动,但是到底会不会在其他task启动需要依赖taskAffinity具体分析
- 当当前task中已经存在一个相同的Activity时,会将task中位于该Activity实例上方的activity出栈直到该Activity实例位于栈顶(类似Intent中添加FLAG_ACTIVITY_CLEAR_TOP的效果)
- 对一个singleTask的Activity类,系统中只会存在一个实例
singleInstance
独享task
- 默认新建task,并独占该task
- singleInstance的Activity中启动的task会在其他task
- 系统中同样只会存在一个实例
taskAffinity
个人简单理解为根activity的taskAffinity可以决定task的“名字”,activity在新task启动时和re-parent时需要根据taskAffinity来确定该activity会出现在哪个task
FLAG
除了在AndroidManifest.xml中指定launchMode之外,在设置Intent的FLAG同样会影响到activity的启动,这里只介绍下FLAG_ACTIVITY_NEW_TASK
,其他的FLAG文章中暂时不会涉及,以后有空再补
FLAG_ACTIVITY_NEW_TASK
网上很多文章说在Intent中设置了FLAG_ACTIVITY_NEW_TASK
相当于为Activity指定了singleTask模式,但是实际上singleTask模式具有三个特点
- 可以在其他task启动
- 会复用task中已有的Activity实例,并将其上的activity出栈
- singleTask的activity系统中只会存在一个实例
设置FLAG_ACTIVITY_NEW_TASK
只是让一个activity具有了在其他task启动的能力,并没有后面两个特点,所以上面的说法其实是不准确的
FLAG_ACTIVITY_NEW_TASK
- 可以让standard/singleTop的activity具备在其他task启动的能力
- 不会改变singleTask/singleInstance的activity行为
- 使用非Activity的
context.startActivity()
,需要在Intent中设置FLAG_ACTIVITY_NEW_TASK
,否则会报错
本文涉及到的基础知识就简单介绍完了,下面开始进入正题
activity启动规律总结
为了表述方便,后面统一用CurrentActivity和TargetActivity表示一个activity启动另一个activity的场景
把singleInstance拉出来单独说一下
- 当CurrentActivity的启动模式是singleInstance时,因为它独占了当前task,TargetActivity不论启动模式是什么都会在其他task启动
- 当TargetActivity是singleInstance时,它会在其他task中启动并独占这个task
- 后面启动规律说明中都不包含CurrentActivity的启动模式是singleInstance这个特殊的情况,这个大家注意下
首先判断TargetActivity是否只能在当前task启动
先列个表格
TargetActivity是否可以在新task中启动 | standard | singleTop | singleTask | singleInstance |
---|---|---|---|---|
设置FLAG_ACTIVITY_NEW_TASK | true | true | true | true |
不设置FLAG_ACTIVITY_NEW_TASK | false | false | true | true |
一句话总结:singleTask/singleInstance的activity本身具有在新task中启动的能力,standard/singleTop的activity要想拥有在新task中启动的能力,需要在设置Intent.FLAG_ACTIVITY_NEW_TASK
第二步判断TargetActivity所在的task
总共三种情况:
- 在当前task启动
- 新建一个task并创建activity
- 将已有的task切换到前台并启动activity
对于不具有在新task中启动能力的activity
未指定Intent.FLAG_ACTIVITY_NEW_TASK
standard/singleTop的activity,对不起,你没有这个能力,只能老老实实在当前task中启动。
当然如果当前应用一个task都没有,那么这个activity启动时会创建一个task,这个activity的实例作为task的根activity。
对于具有在新task中启动能力的activity
singleTask/singleInstance以及在Intent中设置了FLAG_ACTIVITY_NEW_TASK
的standard/singleTop具有在新task中启动能力。
至于是否在一个新task中启动,还要受其他条件的限制,这个条件可以先简单的认为是taskAffinity
- 当前task根activity的taskAffinity和TargetActivity的taskAffinity相同时TargetActivity会在当前task中启动
- 前一种情况不满足时,如果已经存在了一个task的根activity的taskAffinity和TargetActivity的taskAffinity相同,则将这个task切到前台并启动TargetActivity
- 以上两种情况都不满足,会新建一个task,TargetActivity的实例作为根activity,这个task的taskAffinity由TargetActivity决定
总结一下就是找一个taskAffinity的task去启动,找不到就新建一个(这里会忽略了singleInstance独占的task)
默认的taskAffinity
不指定taskAffinity的话默认是应用的包名,也可以为在application中指定所有的activity的taskAffinity。
优先级是activity中指定的taskAffinity>application中指定的taskAffinity>默认的包名
这里需要特别注意下,不少文章中说singleTask的TargetActivity想要在新的task中启动需要设置taskAffinity,其实真正原因是不设置的话,所有activity都用的默认的,也就是同一个应用中taskAffinity不指定的话默认都是相同的,按照上面的分析singleTask的TargetActivity肯定会在当前task启动的。
如果设置MainActivity或者说当前task的根activity的taskAffinity为一个不同的值话,不指定taskAffinity的singleTask的TargetActivity同样会在新的task中启动。
最后第三步根据TargetActivity的启动模式判断会如何启动
- 新建task的场景简单,创建TargetActivity实例作为根activity
- standard:在栈顶创建TargetActivity实例
- singleTop:task栈顶是一个已经存在的TargetActivity的实例的话,复用这个activity,不创建新的实例;否则在栈顶创建新的TargetActivity的实例
- singleTask:如果task中已经存在TargetActivity的实例,那么会将其上的所有activity出栈,并复用这个activity,不创建新的实例;反之则创建新的TargetActivity的实例
套路总结
- singleInstance独享task,单独拎出来看
- 剩下的分为两类:只能在当前task中启动的/可以在其他task中启动的activity
- 对于可以在其他task中启动的activity,找根activity的taskAffinity与之相同的task,找到就确定TargetActivity会出现在这个task中,找不到就新建一个task,创建TargetActivity实例作为根activity
- 确定了task,再根据不同启动模式的特点分析这个task中activity的情况
返回的情况
返回的情况暂时只考虑按返回键的场景,而不考虑在系统的最近任务的中切换后的情况(以后有空单独写篇文章)
只考虑按返回键的场景其实比较简单,只需要了解总是
- 先将前台的task栈顶的activity出栈
- 直到前台task被清空再将后台的task切换到前台继续第一步操作
异常的场景
按照上面的思路分析已经可以覆盖到工作中大部分场景了,不过我在实践中还是碰到了一些异常的情况,也记录下,说不定对碰到同样问题的同学会有帮助
标记了FLAG_ACTIVITY_NEW_TASK的standard/singleTop的activity成为根activity的情况
异常描述
- 一个标记了FLAG_ACTIVITY_NEW_TASK的standard/singleTop的Activity实例成为某个task的根activity
- 要在这个task中再启动相同Activity,使用FLAG_ACTIVITY_NEW_TASK,新的实例不会被创建,task不发生任何变化,根activity中的onNewIntent也不会被回调
- 不使用FLAG_ACTIVITY_NEW_TASK的话一切正常
- 如果这个task在后台,那么要启动标记了FLAG_ACTIVITY_NEW_TASK的相同Activity,仅仅会将这个task切到前台,新的实例不会被创建,task中activity不发生任何变化
场景描述
上面说的可能大家有点一头雾水,那么我构建一个还算比较常见的场景
- 在通知栏上弹出两条通知
- 设置的Intent都是打开同一个TargetActivity,它的启动模式是standard,因为通知栏和应用的activity没有联系,所以系统在通知点击启动TargetActivity会自动加上FLAG_ACTIVITY_NEW_TASK
- 如果应用有activity存在的话(TargetActivity不是根activity),点击两条通知会创建两个TargetActivity的实例,这是正常的情况
- 而当应用的所有activity全部销毁了(弹出通知后按返回键结束activity),点击第一条通知会打开TargetActivity(TargetActivity成为了根activity),点击第二条就没有任何反应
具体的验证代码参见NotificationActivity.kt
建议
对于在Service,Notification等必须加FLAG_ACTIVITY_NEW_TASK才能启动activity的地方需要特别谨慎
- 确保要启动的TargetActivity不会成为一个task的根activity
- 如果1不能保证,那么应当确保这些地方不会多次启动同一个Activity
- 如果1,2都不能保证,那么或许要启动的TargetActivity应该是singleTask的,至少singleTask多次启动时onNewIntent会被调用
- 如果singleTask真的适应不了业务场景,那么应当考虑用一个中转的Activity,先启动中转Activity,再在中转Activity中启动真正的TargetActivity
startActivityForResult的异常
先扔两个结论:
- 使用startActivityForResult启动一个activity的时候,如果不是在当前task中启动的,onActivityResult会立刻回调,resultCode 为RESULT_CANCELED
- 使用startActivityForResult启动一个activity的时候,如果intent中设置了FLAG_ACTIVITY_NEW_TASK,同样会立刻回调onActivityResult
5.0之前
如果CurrentActivity为singleInstance
或者TargetActivity为singleTask/singleInstance
,则会在intent中加入FLAG_ACTIVITY_NEW_TASK标志
所以有如下表格
5.0及以上版本的情况
对于android 5.0以上的系统,其实自己也总结了一个规律:如果使用startActivityForResult,Intent中没有主动设置FLAG_ACTIVITY_NEW_TASK
,那么在启动TargetActivity时会忽略activity的启动模式,在当前task的栈顶新建一个TargetActivity实例,以保证startActivityForResult可以正常工作。
上面的图看上去很美好,所有的情况都可以覆盖到,但是实际上如果真的用startActivityForResult启动singleTask/singleInstance
的activity,会出现了很多预期之外的结果
举个例子
上图从上到下依次全部使用startActivityForResult启动,可以看出明显task中的activity状态不是很符合自己的预期
- SingleTaskActivity2/SingleInstanceActivity2分别都有两个实例,而正常情况下
singleTask/singleInstance
启动模式的activity应该系统中只会存在一个实例才对 - singleInstance的activity本来应该独占task现在也和其他的activty共享了task
事实上,5.0以上系统使用startActivityForResult启动activity可以制造出出很多task中activity不符合预期的场景,基于这些task再用startActivity或者设置FLAG_ACTIVITY_NEW_TASK
去启动activity又会衍生出更多的不符合预期的场景,这样的复杂程度老实说自己现在还hold不住,总结不出什么规律,只能具体场景用代码具体实践具体分析
一些关于startActivityForResult的建议
- 一定不要加
FLAG_ACTIVITY_NEW_TASK
,这个和startActivityForResult是冲突的 - 因为不可能不兼容5.0以下的版本,所以CurrentActivity不能是
singleInstance
TargetActivity不能是singleTask/singleInstance
- 如果确实要用startActivityForResult启动
singleTask/singleInstance
的TargetActivity,得同时考虑好5.0以下版本的兼容方式以及5.0以上版本中可能的异常现象
最后
相关的验证代码放在BasicPractice