一个Task 就是一组activity的集合。这些activity按照它们打开的顺序被放置于一个先进后出的栈中(back stack)。
用户点击图标打开一个app时,该app的task会被移到前台显示。如果当前没有该app的task,系统将会新建一个task并在其中运行Main activity。
一个activity(A)打开另一个activity(B),B将会被置于栈顶并显示,A仍然处于栈中,系统会保存它的状态。 如果按下返回键,当前的activity将被弹出栈并destroy掉,前一个activity被resume并重新显示(还原stop前的UI显示)。
下图表示2个activity切换过程该栈的状态。
用户可以通过返回键令task返回栈中的activity依次弹出,当最后一个activity也被弹出后,该task便不再存在。
如果HOME键被按下,从当前app回到桌面,该app的Task会被移到后台,后台的task所属的所有activity都是stop状态,且back stack依然存在——这个task其实只是失去了和用户交互的焦点。
多个task可以同时存在后台,但是系统也会停止一些activity来释放空间,导致activity的状态丢失。
Back stack中的activity不会被重新排位,如果同一个activity能被其他多个activity 启动,这个activity都会创建新实例推入栈中:
Android管理task和back stack的默认行为:activity 在同一个任务中创建并置于先进后出的栈中。如果这种默认的行为不能满足我们的app设计,如:为一个activity创建一个新任务(而不是在相同的任务中),或者启动activity时直接打开已存在的实例(而不是直接在栈顶创建新实例),又或者在用户离开这个task的时候清空除了栈顶以外的全部activity。Android提供了一些属性和flag让coder来指定管理的方式。
在manifest
launchMode
allowTaskReparenting
clearTaskOnLaunch
alwaysRetainTaskState
finishOnTaskLaunch
Intent 也有相关的flag:
FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_SINGLE_TOP
android建议一般的app都不要干涉系统按照默认的方式管理activity和task。如果coder必须指定非默认的管理的方式,最好确定这种效果能符合用户的预期。
对
Use Cases | Launch Mode |
Multiple Instances? |
Comments |
Normal launches for most activities |
"standard" |
Yes |
默认行为。每次启动一个activity,系统都会在目标task新建一个实例。 |
Normal launches for most activities |
"singleTop" |
Conditionally |
如果目标activity的实例已经存在于目标task的栈顶,系统会直接使用该实例,并调用该activity的onNewIntent()(不会重新create) |
Specialized launches (not recommended for general use) |
"singleTask" |
No |
在一个新任务的栈顶创建activity的实例。如果实例已经存在,系统会直接使用该实例,并调用该activity的onNewIntent()(不会重新create) |
Specialized launches (not recommended for general use) |
"singleInstance" |
No |
和"singleTask"类似,但在目标activity的task中不会再运行其他的activity,在那个task中永远只有一个activity。 |
SingleTask的例子:浏览器的browser activity设置了SingleTask只运行在它自己的task中,如果Browser的task现在正在后台当中(task B),而我们的app(task A)的正要打开这个activity,这个task就会被直接移到前台接收我们的intent。
返回键只会将界面返回到当前task的下一个activity,所以Task B回到前台后,返回键会先作用在Task B中,直到最后一个activity被弹出,才会回到我们的Task A栈顶的activity。
注意:launchMode能被Intent 的flag覆盖。
FLAG_ACTIVITY_NEW_TASK: 等同于 singleTask
FLAG_ACTIVITY_SINGLE_TOP: 等同singleTop
FLAG_ACTIVITY_CLEAR_TOP: 如果该activity已经运行在当前task中,intent指定启动这个activity时,task中在它上面的activity都会被destroy,直到指定的activity位于栈顶,然后它的onNewIntent()被调用。
affinity用于指定activity所属的task。默认状态下,一个app中的所有activity都有相同的affinity,所以它们会运行在同一个task。而通过
taskAffinity要用
taskAffinity在以下2种情况中发生作用:
如果用户离开一个task很长时间,系统会清理栈顶以下的activity,这样task被从新打开时,栈顶activity就被还原了。coder同样可以通过
alwaysRetatinTaskState: 如果当前栈顶的activity设置此属性为true,task中的所有activity都会被保留状态。
clearTaskOnLaunch:如果当前栈顶的activity设置此属性为true,行为则与alwaysRetatinTaskState相反,每次离开并重新该task,栈顶下的所有activity都会被清除,用户返回task时永远都是activity初始化的状态。
finishOnTaskLaunch: 与clearTaskOnLaunch 相似,不过只作用于单个activity,不影响整个task。即使是栈顶的activity,也会生效。
*******************************************************************
这两天做推送,下午看了一下午的文档,有写了一下demo,现在做下总结,以免下次又忘了。应该说推送本身是比较简单的,用的是jpush。问题在于接收到推送以后,单击通知栏中推送项目后的跳转问题。嗯,,需求是当浏览完广告页时,按back键要求返回程序栈,当然如果程序此时未启动,则需要启动程序。同样,如果程序正在前台运行则不做任何处理。这里的程序指的就是我们的项目。
这个看来的慢慢说了,应该说是照着文档慢慢说了。这里的文档指的是developer中的Tasks and Back Stack章节。有点长,需要一点耐心。
标准的activity启动流程很简单,不需再做介绍。要讨论的是一些特定的程序需求。这里参考了stackoverflow上的一个需求,比如一个栈中有四个活动,分别是A,B,C,B。其中活动B可以接收用户进行参数设置。然后一个用户在栈顶的B中对程序进行了设置,然后他按back返回了C,再back返回了B。他发现此时B中显示的状态并非他刚才设置好的状态,没错,此时我们需要将活动B做成一个"单例",使用户对B的操作能够及时"同步"。其实,刚开始看载入模式(launch mode)时,只是关注他的特性,没有结合名字来理解。现在想想,其实几种launch mode的名字已经将其功能表述的比较清楚了。下面开始说说正式的内容。
在android中有两种控制activity和当前task关系的方式,一种是在清单文件中为activity设置launchmode属性,另外一种是通过为intent添加flag来实现的。这里需要注意的是,当活动A使用了flag启动了活动B,同时活动B在清单文件中有设置了launchmode。那么活动A的intent的flag的优先级高于活动B在清单文件中的定义。还有就是在清单文件中使用的launchmode在intent的flag中不一定有,同样,在intent中的flag在清单文件中也不一定有。下面先说说launchmode。
standard:标准载入模式,没什么好说的。
singleTop:如果要启动的activity已经存在于栈顶,则会将intent传递给该activity的onNewIntent()方法。注意,不会创建新的activity实例对象。如果栈顶不是要启动的activity,则会创建该activity的新实例。即singleTop只检查栈顶元素,这样栈中可能包含多个该activity的实例对象。
singleTask:系统会创建一个新的任务(task)并将目标activity作为这个任务的根元素。注意,如果目标对象已经存在于一个独立的栈中,系统会将intent对象转发(route)到目标activity的onNewIntent()方法中,而不会新建任务。注意,这个情况在notification中是很有用的。
singleInstance:为目标activity新起一个task,并且该task只存放目标activity一个元素(于其这么说,还不如直接说将目标activity放到一个单独的task)。所有由目标activity激发的activity将被放到一个单独的栈中(这句话还没测试过)
那么这里有一个重点:就是singleTask,假如你想要恢复一个运行在后台的栈(就如文章开头说的那样,从notification中恢复一个后台栈)。那么你可以将目标actiivty(即后台栈中的某一个activity)的launchmode设置为singleTask。从singleTask的字面意思可以看出"单一的任务",即只存在一个task。那么此时系统会去后台栈中查找是否有包含目标activity的栈,如果有就将整个站恢复到前台,并置于当前activity之上。注意是整个栈哦,不是单单一个目标activity。如:后台栈中有A、B(B在A上面),前台有C。在C中要调用B,使用singleTask,当前栈就会变成C、A、B。另外,如果没有包含B的后台栈存在,那么C仅仅启动一个B活动,没有启动整个栈,,这里又要牵涉到后面的一个allowTaskReparenting属性了。等下讲到时,再细说,先来看看intent的flag
FLAG_ACTIVITY_NEW_TASK:和singleTask是一样的。一样吗?文档上是这么说的,但是我试了下,还是有点不一样的。假如后台运行的是A、B(B在A上面),前台有活动C(比如从推送到得到一个notification,单击启动的广告页)。那么如果在C中启动A,则使用FLAG_ACTIVITY_NEW_TASK的话,当前栈会变成CABA(C在最下面),而使用single_instance的话得到的是CA。为什么会这样呢?因为FLAG_ACTIVITY_NEW_TASK在启动活动时确实是会对后台栈进行检索,但是这种检索是和affinity相关联的。默认同一个任务栈中的activity的affinity相同,但是现在的确存在两个任务栈,那么的affinity是不同的。
FLAG_ACTIVITY_SINGLE_TOP:和singleTop一样。
FLAG_ACTIVITY_CLEAR_TOP:假如要启动的活动已经运行在当前的task中了,那么程序不会新起一个activity的实例,而是将目标activity上面的活动都destory掉。同时将intent传递给目标activity的onNewIntent()。
现在再来说说推送时遇到的问题,当用户下拉通知栏,然后再单击通知栏中的通知项时,会跳转到一个广告页。然后再广告页中单击back会"返回"程序(其实此时程序可能根本就没启动)。这样的需求在美团中也是这样的效果。
具体实现:
1、在接收推送的Reciver中启动一个广告页,注意必须是FLAG_ACTIVITY_NEW_TASK的标记,这是由BroadcastReceiver的特性决定的。
2、在启动的广告页中监听back事件,如:dispatchKeyEvent。然后跳转到程序主页,这里就是问题的关键,程序主页activity在清单文件中的launchmode属性应该是singleTask的,这样如果程序已经启动,那么就会从后台恢复栈。如果程序没有启动,就会启动程序。