转自我2015年在博客园发布的文章Android Task 相关
前言
在日常开发过程中,只要涉及到activity,那么对task相关的东西总会或多或少的接触到,不过对task相关的一些配置的作用理解的还不是很透彻,官方文档在细节上说的也不够清楚,要透彻理解还是得自己写demo实践检验,所以便有了这篇总结。
task的概念
查看设备当前task的方法
AndroidManifest中activity标签下和task有关的属性
谷歌现在对activity标签的文档出了中文版,阅读更加方便了。
taskAffinity
- 此属性用来标记activity应该属于哪个task。
- 拥有相同affinity的activity从理论上属于同一个task(在用户的角度看来好像这些activity属于同一个应用),一个task的affinity是由其根activity的taskAffinity取值决定的。
- affinity决定了两件事。
- 一个是在使用allowTaskReparenting修饰activity时,activity要重新宿主到哪个task。
- 另一个是使用FLAG_ACTIVITY_NEW_TASK启动activity时,activity要放入哪个task。
- 如果没有给activity设置taskAffinity,默认都会读取application标签下的taskAffinity属性值,如果application标签下也没有设置taskAffinity,那taskAffinity默认值就是manifest标签下设置的包名。
- 不仅可以给同一个应用的不同activity设置不同的affinity,也可以给不同应用的activity设置相同的affinity,使它们在用户角度看来好像属于同一个应用。
launchMode
launchMode有四种取值,与Intent里以FLAG_ACTIVITY_
开头的flag结合,可以对activity的启动达到各种不同的效果。
standard
activity默认的启动模式,每次启动一个standard模式的activity时,都新建一个实例。
singleTop
当前task栈顶存在本activity的实例,直接使用该实例,调用该activity的onNewIntent(),否则新建一个activity的实例入栈。
singleTask
当启动一个singleTask模式的activity时,首先会检查是否存在与该activity的taskAffinity相同的task。
- 如果存在,那么检查该task栈里是否存在该activity实例。
- 如果存在,则将该task调入前台,销毁在该activity以上的activity,并调用该activity的onNewIntent()。
- 如果不存在,则新建一个该activity实例,并入栈。
- 如果不存在,则新建一个task,再新建该activity实例并放入新建的task中。
- 从该activity再启动其他activity,允许其他activity跟自己处于同一个task栈中,也允许其他activity重新宿主到本activity。
例如有四个activity叫A、B、C、D,其中A、B具有相同的affinity,现在taskA里有A、B,其中A和B是standard。
- 从B启动C,C是singleTask,C的affinity和A、B相同,C会进入taskA栈顶。
- 从C启动D,D是standard或singleTop,不论D的affinity是什么,D会进入taskA栈顶。
- 从D启动C,D出栈被销毁,C接收到onNewIntent()
- 从C启动D,D是singleTask,D的affinity和A、B、C相同,D会进入taskA栈顶。
- 从D启动C,D出栈被销毁,C接收到onNewIntent()
- 从C启动D,D是singleTask,D的affinity和A、B、C不同,D会进入新建的taskB中。
- 从D启动C,taskA调入前台,放在taskB的上面,C接收到onNewIntent()
- 从C启动D,D是singleInstance,D的affinity不论是什么,D会进入新建的taskB中,taskB的affinity为D的affinity。
- 从D启动C,taskA调入前台,放在taskB的上面,C接收到onNewIntent()
- 从C启动D,D是standard或singleTop,不论D的affinity是什么,D会进入taskA栈顶。
- 从B启动C,C是singleTask,C的affinity和A、B不同,C会进入新建的taskB中。
- 从C启动D,D是standard或singleTop,不论D的affinity是什么,D会进入taskB栈顶。
- 从D启动C,D出栈被销毁,C接收到onNewIntent()
- 从C启动D,D是singleTask,D的affinity和A、B相同,D会进入taskA栈顶。
- 从D启动C,taskB调入前台,放在taskA的上面,C接收到onNewIntent()
- 从C启动D,D是singleTask,D的affinity和A、B不同但与C相同,D会进入新建的taskB栈顶。
- 从D启动C,D出栈被销毁,C接收到onNewIntent()
- 从C启动D,D是singleTask,D的affinity和A、B不同且与C也不同,D会进入新建的taskC中。
- 从D启动C,taskB调入前台,放在taskC的上面,C接收到onNewIntent()
- 从C启动D,D是singleInstance,D的affinity不论是什么,D会进入新建的taskC中,taskC的affinity为D的affinity。
- 从D启动C,taskB调入前台,放在taskC的上面,C接收到onNewIntent()
- 从C启动D,D是standard或singleTop,不论D的affinity是什么,D会进入taskB栈顶。
singleInstance
当启动一个singleInstance的activity时,首先会检查是否存在与该activity的taskAffinity相同的task。
- 如果存在,检查这个task中是否存在该activity的实例。
- 如果存在,则将该task调入前台,并调用该activity实例的onNewIntent()。
- 如果不存在,则新建一个task,再新建该activity实例放入新建的task中,系统允许多个相同affinity的task同时存在。
- 如果不存在,则新建一个task,再新建该activity实例并放入新建的task中。
- 从该activity再启动其他任何activity,都会放到其他task中(新建task或者寻找已存在的task,即使要启动的activity与该activity具有相同的affinity),也不允许其他activity宿主到本task,该activity是task中唯一的activity。
例如有四个activity叫A、B、C、D,其中A、B具有相同的affinity,现在taskA里有A、B,其中A和B是standard
- 从B中启动C,C是singleInstance,C的affinity和A、B相同,C会放入新建的taskB中,taskA和taskB的affinity相同,因为两个task的根activity的affinity相同。
- 从C中启动D,D的affinity和A、B、C相同。
- D是standard、singleTop、singleTask时,D会放入taskA中,taskA调入前台,放在taskB上面。
- D是singleInstance,D会进入新建的taskC中,taskC和taskA、taskB的affinity相同,因为三个task的根activity的affinity相同。
- 从C中启动D,D的affinity和A、B、C不同,不论D是何种launchMode,D都会进入新建的taskC中,taskC的affinity是D的affinity。
- 从C中启动D,D的affinity和A、B、C相同。
- 从B中启动C,C是singleInstance,C的affinity和A、B不同,C会放入新建的taskB中,taskA和taskB的affinity不同,因为两个task的根activity的affinity不同。
- 从C中启动D,D的affinity和A、B相同。
- D是standard、singleTop、singleTask时,D都会进入taskA中,taskA调入前台,放在taskB上面。
- D是singleInstance,D会进入新建的taskC中,taskC和taskA的affinity相同,因为两个task的根activity的affinity相同。
- 从C中启动D,D的affinity和A、B不同,不论和C是否相同,D都会进入新建的taskC中,因为C所在的task不允许其他activity的存在,taskC的affinity为D的affinity。
- 从C中启动D,D的affinity和A、B相同。
singleInstance
使用场景举例:
- 闹钟应用的响铃页面
- 应用锁应用的锁屏页面
这两个页面从概念上理解,可以认作是独立的页面,跟其他页面无直接关系,应该单独放在一个task。
如果我们对这两个页面使用singleTask
的话,就会产生一些逻辑问题,假设这两个页面现在都使用了用singleTask
:
如果响铃页面activity的affinity跟闹钟其他activity的affinity相同,那么先打开闹钟应用,产生taskA,再按home键打开其他应用,产生taskB,再等闹铃响起进入响铃页面,响铃页面的activity也会入栈taskA,同时taskA移动到taskB上面,这个时候关闭闹铃即关闭响铃页面后,返回的不是其他应用(taskB)而是按home键前闹钟应用里打开的最后一个页面,这跟预期的结果不一致。但如果响铃页面使用singleInstance
,闹钟响起时,会产生taskC,关闭响铃页面后会返回其他应用(taskB)。
如果锁屏页面activity的affinity跟应用锁应用里其他activity的affinity不同,那么先打开应用锁应用,产生taskA,再按home键打开要上锁的应用,产生了taskB,然后后台服务检测要上锁,启动了锁屏页面,产生了taskC,整个task栈由底到顶依次是taskA->taskB->taskC,此时点击锁屏页面中的“忘记密码”的功能,打开忘记密码页面:
- 忘记密码页面是standard或singleTop,不论它是什么affinity,都入栈taskC,不会产生什么问题。
- 忘记密码页面是singleTask
- 跟应用锁应用里的其他activity是相同的affinity,则入栈taskA,同时taskA移动到taskC上面,这时候按返回键返回的是taskA中最后一个activity,回不到锁屏页面了。如果锁屏页面使用
singleInstance
,倒没什么改变。 - 跟锁屏页面是相同的affinity,则入栈taskC,此时如果按home键,再打开应用锁管理页面,进入taskA,整个task栈由底到顶依次是taskB->taskC->taskA,点击忘记密码,调用taskC中忘记密码页面activity的onNewIntent(),整个task栈由底到顶依次是taskB->taskA->taskC,再按返回键,回到的是锁屏页,回不到应用锁管理页面了。如果锁屏页面使用
singleInstance
,则入栈taskD,不会产生什么问题。 - 是独一无二的affinity,则入栈taskD,不会产生什么问题。
- 跟应用锁应用里的其他activity是相同的affinity,则入栈taskA,同时taskA移动到taskC上面,这时候按返回键返回的是taskA中最后一个activity,回不到锁屏页面了。如果锁屏页面使用
- 忘记密码页面是singleInstance,不论它是什么affinity,都入栈taskD,不会产生什么问题。
从上面的分析看来,从简单的角度还是直接设置这种独立的页面为singleInstance
比较方便,不用考虑那么多问题。
allowTaskReparenting
此属性为true的activity被启动后,若有和此activity相同affinity的task转入前台,则此activity会从启动它的task移动到具有相同affinity的这个task。
例如,现在有两个应用分别为appA和appB,appA中有三个activity分别为activityA1、activityA2、activityA3,其中activityA1、activityA2的taskAffinity为taskA,activityA3的taskAffinity为taskB,appB中有一个activity为activityB1,其taskAffinity为taskB。所有activity都是standard模式。
启动appA,默认启动activityA1,再依次启动activityA2、activityA3,此时这三个activity都属于taskA。
按home键回到launcher,此时这三个activity扔都属于taskA。
- 此时若点击appA的图标启动appA,看到的是activityA2,activityA3会进入新建的affinity为taskB的task中。此时所有task的顺序由前到后依次为taskA、Launcher所在的task、taskB。
- 按home键回到launcher,点击appB的图标启动appB,taskB调入前台显示,看到的是activityA3,而不是activityB1。此时所有task的顺序由前到后依次为taskB、Launcher所在的task、taskA。
- 此时若点击appB的图标启动appB,看到的是activityA3,activityA3进入新建的affinity为taskB的task中。此时所有task的顺序由前到后依次为taskB、Launcher所在的task、taskA。taskB中还有activityB1在栈底部,在activityA3中按返回键可以回到activityB1。taskA中仅剩activityA1、activityA2。
alwaysRetainTaskState
如果用户离开一个task已经很久了,系统会在某个时刻清理掉这个task中除了根activity外所有的activity。当用户再次回到这个task,只有根activity被恢复。这样做是因为长期离开一个task,用户很有可能已经放弃了他之前所做的事情,转而要开始做新的事情,所以只保留根activity。
若根activity上的alwaysRetainTaskState为true,强制保留本task中的所有activity,即使过了很长时间,也不让系统清理task。
例如浏览器打开了很多个tab页,长时间不操作后也要保证再次回来时还是上次浏览的页面。
clearTaskOnLaunch
与alwaysRetainTaskState相反,若根activity上的clearTaskOnLaunch为true,不论何时用户再次从Launcher回到这个task时,除了根activity以外的其他activity都销毁。
finishOnTaskLaunch
此属性为true的activity,不论何时用户再次回到这个activity所属的task时,此activity会被销毁。此属性优先级优于allowTaskReparenting。
Intent中和task有关的部分flag
FLAG_ACTIVITY_NEW_TASK
和launchMode的属性值singleTask等效。如果一个Intent中包含此flag,尝试将要启动的activity放在一个新的task中,如果已经有一个task栈里存在目标activity的实例,将此task从后台调到前台来,调用已存在的activity实例的onNewIntent()方法。此flag不能用于startActivityForResult()。
FLAG_ACTIVITY_SINGLE_TOP
和launchMode的属性值singleTop等效。如果一个Intent中包含此属性,并且要启动的Activity就是当前的Activity(当前task栈顶activity),直接调用该activity的onNewIntent(),否则新建一个activity实例。
FLAG_ACTIVITY_CLEAR_TOP
如果一个Intent中包含此属性,并且当前task栈存存在目标activity的实例,清除该实例上面的所有的activity。
如果目标activity的launcherMode为standard,且Intent没有添加FLAG_ACTIVITY_SINGLE_TOP标记,则会销毁目标activity再重新创建,否则会重用该实例,调用onNewIntent()。
FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
如果一个Intent中包含此属性,则它转向的那个Activity以及在那个Activity其上的所有Activity都会在task重置时被清除出task,这只发生在task重置的时候,而从Launcher中点击应用图标启动应用的时候会发生task重置(从Launcher启动应用会在Intent中附带一个FLAG_ACTIVITY_RESET_TASK_IF_NEEDED标记)。
网友评论