Activity LaunchMode
LaunchMode
Android中Activity的启动模式有四种:
- standard(默认选项)
- singleTop
- singleTask
- singleInstance
可以在AndroidManifest.xml
中通过指定activity
标签的android:launchMode
属性来改变:
<activity android:name=".SecondActivity" android:launchMode="singleTop"/>
如果不指定launchMode
属性,那么activity会使用默认(standard)模式来运行
standard
standard
是Activity的默认属性,Android在启动standard
的Activity时遵循以下规则:
- 如果是点击图标进行启动,则会新创建一个
task
,然后将这个Activity压入这个task
中 - 如果是使用
startActivty
进行启动,则会将这个Activity压入启动它的那个Task中 - 默认情况下,Activity是多例模式,即一个Activity可以同时存在多个实例,也可以同时存在于多个
task
中
详细说明
Activity的standard
模式在自己App内部通常表示没有特殊的式样要求,遵循系统默认情况即可, 但是在多个App之间使用的时候需要注意, 它会被压入启动他的那个App的task中, 例如: 在电话
App中选择添加联系人
操作, 此时启动的是联系人
App的添加联系人
Activity,此时添加联系人
Activity会被压入到电话
App中,在用户看来,就好像是这个页面是属于电话
App的.
因为从整个业务流程来看, 用户此时的心理预期是:
整个操作都属于
电话
App的流程, 而与联系人
app无关
此时Android系统的表现为:
添加联系人
页面的入场动画为Activity进入的动画
,而不是task切换的动画
(未指定动画的情况下)- 当用户点击
多任务
键时, 在后台也看不到联系人
App, 只能看到电话
app - 在
添加联系人
页面中点击返回键,页面会回退到电话
App的页面 - 如果此时用户手动打开了
联系人
App, 并再次添加联系人, 会得到一个全新的添加联系人
页面, 并且与电话
App中的`添加联系人页面没有冲突
singleTop
singleTop整体的行为与standard一致,区别如下:
- 当Activity在入栈时,singleTop会检测当前task的栈顶Activity,如果当前的栈顶Activity与要压入的Activity是同一个Activity时,那么就不会创建Activity新的实例,而是会直接使用当前的栈顶Activity,并且会调用Activity的
onNewIntent
方法,将新的Intent传入
singleTop只会检测栈顶
的Activity,保证Activity在栈顶唯一
,但是Activity还是可以以多例存在的
例如:
AActivity被设置为singleTop
,AActivity启动自己,或其它应用继续启动AActivity,此时:
-
AActivity在栈顶, 则不会创建AActivity,而是会调用AActivity的
onNewIntent
方法
-
AActivity不在栈顶, 则会重新创建一个AActivity的实例, 并执行其
onCreate
方法
singleTask
被标记为singleTask的Activity可以让该Activity在被别的Activity启动时,不会进入启动他的Task中,而是会在属于它自己的Task里创建,并放在栈顶, 然后把整个Task一起拿过来,压在启动Task上.
是否在当前Task中启动Activity,主要看taskAffinity:
- 如果新的Activity的taskAffinity和当前Task相同,就继续在当前Task启动
- 如果不同,就换到别的Task,如果有则利用那个task,如果没有就创建一个,找到对应task后:
- 如果目标Task里没有目标Activity,创建Activity,入栈
- 如果目标Task中已有目标Activity,那么就将这个栈中在目标Activity上面的Activity全部销毁,让目标Activity出现在栈顶,然后调用他的onNewIntent()方法
singleInstance
整体行为与singleTask
类似,但是有以下区别:
- 启动singleInstance的Activity的时候,不管新Activity和当前Task的taskAffinity是否一样,都会创建他独有的Task
- 在singleInstance的Activity上启动其他Activity时,无论二者的taskAffinity是否一样,都不允许其他Activity进入当前task
即singleTask
的Activity只会单独存在在一个task中, 并且这个task中不能有其他的Activity,强调其独占
性
taskAffinity
taskAffinity代表的是task的相关性
,可以认为是Android想要将同一个逻辑链条
中的Activity放入统一个Task中,在Android中,不同task的taskAffinity是可以相同的
指定TaskAffinity
可以在AndroidManifest中通过android:taskAffinity
属性指定Activity所在task的taskAffinity
- 如果Activity没有指定taskAffinity,那么就会使用application的taskAffinity
- 如果Application没有指定taskAffinity, 那么就会使用应用的包名作为taskAffinity
最近任务
按下Android中的多任务键,会进入多任务页面,多任务页面中显示的实际上是所有的task,并不是应用,当有多个task具有相同的taskAffinity是,最近任务列表里只会显示最近新展示过的一个,所以如果标记了singleInstanse的Activity,完全有可能不显示在多任务页面中
例如:
AActivity标记为singleInstance,AActivity启动了本应用的BActivity,此时AActivity和BActivity就会分别处于不同的task栈中,但是这两个栈的taskAffinity
都是应用的包名(默认情况下),此时点击多任务页面,只能看到BActivity的task,AActivity的task由于taskAffinity
与BActivity的taskAffinity
相同而被隐藏起来了
Task要不要在最近任务列表里显示
上述的显示规则,也可以被FLAG改变:
- FLAG_ACTIVITY_NEW_DOCUMENT: 添加了这个Flag的Activity,每个都会独立显示在最近任务里,但是关闭后,不会在最近任务里保留残影
- FLAG_ACTIVITY_RETAIN_IN_RECENTS: 可以修改上面的规则,让Activity在最近任务里保留残影
- FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:这个Activity从一开始就不会显示在最近任务里,切走之后就再也切不回来了
返回键
当点击Android中的返回键时,Android会按照以下逻辑进行执行:
- 对当前task执行出栈的操作,显示出当前task的上一个Activity
- 如果当前task执行出栈后,没有其他Activity,则销毁这个task,但是并不会销毁其在多任务页面的快照
- 当前task销毁后,如果有其他前台task,则会切换到上一个前台task
- 当前task销毁后,如果没有其他前台task,则会直接显示Home
所谓的前台task,指的是再Activity运行过程中,使用startActivity
的方式开启了其他的task,这个时候这些新启动的task
就会以栈的形式组织起来,当一个task销毁后,就可以显示上一个task
但是,如果用户按下了home键,或者多任务键后, 这些以栈形式组织起来的task就会被打散,用户是无法通过返回键再回到上一个task中.
例如:
- 在便签应用中点击网址,此时会启动浏览器,这个时候前台的task有两个:便签App的task和浏览器的task,每个task中只有一个Activity此时如果按下返回键,会将浏览器的Task销毁,然后页面回到了便签App
- 在便签应用中点击网址,此时会启动浏览器,此时按下多任务键,然后再回到浏览器,此时,前台的task就只剩下浏览器App一个了,这个时候按下返回键,Android会销毁浏览器的Task,然后回到Home上
更完整的场景分类
Activity的启动,可以有很多分类方式:
打开的时候放进哪个Task
-
默认情况下(standard)以及singleTop,是属于当前所属的逻辑链条,即直接放入当前的task,而不去检查taskAffinity
-
如果是singleTask,就需要判断当前Activity和目标Activity的taskAffinity是否相同,相同就会放在当前task,不同就放入别的Task,而且在放进别的Task的时候,也会先尝试找到一个和目标Activity的taskAffinity匹配的Task,找到就进入这个Task,找不到才会创建新的Task
总结起来,singleTask其实是个很简单的规则: 查找taskAffinity相同的Task,找到就让Activity进入,找不到就创建新的Task
而更本质上, 其实Activity启动的时候,所有的launchMode都会 穿换成对应的Intent的
Flag
(0个或多个都有可能),其中 singleTask会被转换成好几个Flag的叠加,其中一个就是Intent.FLAG_ACTIVITY_NEW_TASK
,如果在startActivity 的时候,在Intent里添加上这个Flag:Intent intent = new Intent(this,MainActivity.class) ; intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent);
那么新Activity启动时的入栈规则就会和配置了singleTask的 Activity一样.
但是我们实际上也可以跳过这个搜索过程,如果在Intent里除了
FLAG_ACTIVITY_NEW_TASK
之外,再配置上FLAG_ACTIVITY_MULTIPLE_TASK
:Intent intent = new Intent(this,MainActivity.class) ; intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); startActivity(intent);
那么Activity就会跳过搜索,直接创建一个新的Task来吧这个 Activity放在栈底
-
如果launchMode是singleInstance,并且目标Activity和当前的Activity不同,就放进新的单独的Task;
- 另外,如果当前的Activity已经是singleInstance,那不管目标Activity是什么,只要二者不是同一个Activity,就会把目标Activity放进别的Task
打开的时候要不要清掉顶部的Activity
前面提到FLAG_ACTIVITY_NEW_TASK
的入栈规则和singleTask一样,同时singleTask会被转换成多个Flag,而不只是FLAG_ACTIVITY_NEW_TASK,singleTask的行为规则除了入栈方面,还有清理顶部: 如果目标Activity已经在目标Task,但是不在栈顶,就会把上面的Activity全部清理掉.而这个清理顶部也是singleTask所对应的Flag之一:FLAG_ACTIVITY_CLEAR_TOP
,需要将FLAG_ACTIVITY_NEW_TASK
和FLAG_ACTIVITY_CLEAR_TOP
配合在一起使用,才能达到类似singleTask那样的清理顶部的效果
,否则只有FLAG_ACTIVITY_CLEAR_TOP
的话,Activity入栈时永远会在栈顶,就没有顶部
可以清理了
打开时要不要复用已有的Activity
在singleTask的规则里, 如果目标Task里已经有了目标Activity,目标Activity顶部的其他Activiy会被清理掉,让activity回到栈顶;而Intent.FLAG_ACTIVITY_CLEAR_TOP
做的也是清理顶部的活,看起来好像一样, 但是singleTask除了清理顶部,还会复用栈内的目标Activity,调用它的onNewIntent
方法,这点和singleTop是一样的,而Intent.FLAG_ACTIVITY_CLEAR_TOP
并不会复用Activity,他的默认规则是:将目标Activity顶部的其他Activity,以及目标Activity一起,全部清理掉,然后重新启动目标Activity到栈顶.
如果想在清理其他Activity的时候,别把目标Activity一起清掉,需要加上另一个flag:
FLAG_ACTIVITY_SINGLE_TOP
这个flag就是singleTop,所对应的唯一flag.
而singleTask其实对应了3个flag:FLAG_ACTIVITY_NEW_TASK+FLAG_ACTIVITY_CLEAR_TOP+FLAG_ACTIVITY_SINGLE_TOP,所以singleTask实际上就是这三个flag共同作用的规则:
- 让Activity寻找和自己taskAffinity相同的Task(FLAG_ACTIVITY_NEW_TASK)
- 清理掉目标Activity顶部的其他Activity(FLAG_ACTIVITY_CLEAR_TOP)
- 复用栈中已有的目标Activity(FLAG_ACTIVITY_SINGLE_TOP)
实战选择
- standard和singleTop多用于App内部
- singleInstance:多用于开放给外部App来共享使用
- singleTask:内部交互和外部交互都会用得上
- 启动Activiy尽量不要设置singleTask,因为这样用户每次点击图标的时候,都会将task中其他的Activiy全部杀死.即使配置了
clearTackOnLaunch = false
也不能解决