一个应用程序通常包含多个 activity。 每个 activity 在设计时都应该以执行某个用户发起的 action 作为核心目标,并且它能启动其它 activity。 比如,一个 email 应用可能会用一个 activity 来列出所有的新 email,当用户选中一封 email 时,再打开一个新的 activity 来显示这封 email。
一个 activity 甚至可能会启动另一个应用中的 activity。 比如,如果你的应用需要发送 email,你可以定义一个 intent 来执行“send” action,其中包含一些数据,如 email 地址、正文等。 然后会打开一个其它应用中已声明能够处理这类 intent 的 activity。 这里是一个发送 email 的 intent,所以会打开一个 email 应用的“新建邮件”activity(如果有多个 activity 都支持同一个 intent,则系统或让用户选择一个打开)。 email 发送完毕后,你的 activity 将会恢复,看起来 email activity 就像是你的应用中的一部分一样。 虽然这两个 activity 可能来自不同的应用,通过把它们放入同一个task,Android 保证了无缝的用户体验。
task 是多个 activity 的集合,用户进行操作时将与这些 activity 进行交互。 这些 activity 按照启动顺序排队存入一个栈(即“back stack”)。
大部分 task 都启动自 Home 屏幕。当用户触摸 application launcher 中的图标(或 Home 屏幕上的快捷图标)时,应用程序的 task 就进入前台。 如果该应用不存在 task(最近没有使用过此应用),则会新建一个 task,该应用的“main”activity 作为栈的根 activity 被打开。
当用户返回到 home屏幕执行另一个 task 时,一个 task 被移动到后台执行,此时它的返回栈(back stack)也被保存在后台, 同时 android 为新 task 创建一个新的返回栈(back stack),当它被再次运行从而返回前台时,它的返回栈(back stack)被移到前台,并恢复其之前执行的activity,如下图所示。 如果后台有太多运行 task ,系统将会杀死一些 task 释放内存。
如果当前 activity 启动了另一个 activity,则新的 activity 被压入栈顶并获得焦点。 前一个 activity 仍保存在栈中,但是被停止。activity 停止时,系统会保存用户界面的当前状态。 当用户按下返回键,则当前 activity 将从栈顶弹出(被销毁),前一个 activity 将被恢复(之前的用户界面状态被恢复)。 activity 在栈中的顺序永远不会改变,只会压入和弹出——被当前 activity 启动时压入栈顶,用户用返回键离开时弹出。 这样,back stack 以“后进先出”的方式运行。图1 以时间线的方式展示了多个 activity 切换时对应当前时间点的 back stack 状态。
管理tasks
Android管理任务和后退栈的方式,如前面文章所述—通过把所有接连启动的activity放在同一个任务中并且是同一个后进先出的栈中—在大多数应用中工作得很好并且你无需关心你的activity如何与任务相关连或如何在后退栈中存在.然而,你可能决定要打破这种正常的行为.可能你想在你应用的activity启动时开始一个新的任务(而不是放置到当前栈中);或者,当你启动一个activity,你想把已经运行的它的一个实例提到前台来(而不是创建一个新的实例放在后退栈的顶端);或者,你希望当用户离开任务时,你的后退栈清除了根activity以外的所有activity。
可以做这些事情,甚至更多事情,通过设置manifest中的属性和传到startActivity()的intent的flag.
在这一点上,你可以设置的最重要的属性有:
taskAffinity
launchMode
allowTaskReparenting
clearTaskOnLaunch
alwaysRetainTaskState
finishOnTaskLaunch
可以使用的最重要的intent flag:
FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_SINGLE_TOP
注意:大多数应用不应改变activity和任务的默认行为.如果你确定必须要改变默认行为,你必需小心并且保证测试了activity在启动时和后退导航到它时的可用性.确保测试了与用户习惯相冲突的导航行为.
定义启动模式
启动模式使你可以定义新的activity如何与当前的任务相关联.有两种方法来定义不同的启动模式:
1.使用manifest文件
当你在你的manifest文件中声明一个activity时,你可以指定activity在启动时如何与任务相关联.
2.使用Intent的flag
当你调用startActivity()时,你可以在Intent中包含指明新的activity如何(或是否)与当前栈关联的flag.
同样的,如果ActivityA启动ActivityB,ActivityB可以在它的manifest中定义如何与当前的任务关联(如果真的存在)并且ActivityA也可以请求让ActivityB如何与当前的任务关联.如果两个activity都定义了ActivityB如何与任务关联,那么ActivityA的请求(在intent中定义)优先于ActivityB的请求(在它的manifest中定义).
注:一些启动模式可以用在manifest中但不能用在intent的flag上,同样的,一些启动模式可以用在intent的flag上但不能用在manifest中.
使用manifest文件
当在你的manifest文件中声明一个activity时,你可以使用元素的launchMode属性指定activity如何与一个任务关联.
launchMode属性指明了activity如何启动到一个任务中去.有四种不同的启动模式你可以用于指定给launchMode属性:
“standard”(默认模式)
默认.系统创建一个新的activity的实例到启动它的任务中.activity可以被多次实例化,每个实例可以属于不同的任务,也可以属于同一个任务.
“singleTop”
如果一个activity的实例已经存在于当前任务的栈的顶端,系统通过调用它的onNewIntent()方法把intent路由到这个实例,而不是创建一个新的实例.activity可以被多次实例化,每个实例可以属于不同的任务,并且一个任务可以具有多个实例(但只是当位于后退栈的顶端的activity不存在时才会出现这种现像).
例如,假设一个任务的后退栈中有根ActivityA和activityB,C,D(A-B-C-D;D位于顶端).一个intent到达了D类型的activity(不是指这里的acitivityD).如果D具有默认的”standard”启动模式,一个新的类的实例被启动并且栈变为A-B-C-D-D.然而,如果D的启动模式是”singleTop”,那么这个已存在的ActivityD就通过onNewIntent()接收到intent,因为它在栈的顶端—栈于是依然保持A-B-C-D.又然而,如果一个intent到达了B类型的activity(不是此处的activityB),那么一个新的B实例被添加到栈中,即使它的启动模式是”singleTop”.
注:当一个新的activity的实例被创建,用户可以按下后退键回到上一个activity.但当一个已存在的activity实例处理了一个新intent,用户就不能按下后退键回到当前的activity在intent来之前的状态.
“singleTask”
系统创建一个新的任务并且实例化activity为新任务的根.然而,如果一个activity的实例已存在于另一个任务,系统就会通过调用这个activity的onNewIntent()把intent路由给它,而不是创建一个新的实例.某个时刻只有一个activity的实例可以存在.
注:尽管activity在一个新任务中启动,后退键依然可以返回到上一个activity.
“singleInstance”.
跟”singleTask”一样.除了系统不能再启动其它activity到拥有这个activity实例的任务中.activity永远是任务的唯一;任何由这个activity启动的其它activity都在另一个任务中打开.
举一个例子,Android浏览器应用声明网页浏览activity必须在它自己的任务中打开—通过在<activity>元素中指定singleTask启动模式.这表示如果你的应用发出一个intent来打开Android浏览器,它的activity不会放到你的应用所在的任务中.代替的是,可能一个新的任务为浏览器启动,或者,如果浏览器已经运行于后台,它所在的任务就被弄到前台并接受这个intent.
不论一个从一个新任务启动activity还是在一个已存在这种activity的任务中启动,后退键总是能后退到前一个activity.然而,如果你在任务A中启动一个声明为singleTask模式的activity,而这个activity可能在后台已有一个属于一个任务(任务B)的实例.任务B于是被弄到前台并处理这个新的intent.那么此时后退键会先一层层返回任务BActivity,然后再返回到任务A的顶端activity.图4演示了这种情形.
图4.演示一个”singleTask”启动模式的acitvity如何被添加到一个后退栈中.如果这个activity已经是一个后台任务(任务B)自己的栈的一部分,那么整个后退栈被弄到前台,位于当前任务(任务A)的上面.
使用 Intentflags
当启动一个activity时,你可以在给startActivity()的intent中包含flag以改变activity与任务的默认关联方式.你可以用来改变默认行为的flag有:
FLAG_ACTIVITY_NEW_TASK
在新的任务中启动activity-即不在本任务中启动.如果一个包含这个activity的任务已经在运行,那个任务就被弄到前台并恢复其UI状态,然后这个已存在的activity在onNewIntent()中接收新的intent.
这个标志产生与”singleTask”相同的行为.
FLAG_ACTIVITY_SINGLE_TOP
如果正启动的activity就是当前的activity(位于后退栈的顶端),那么这个已存在的实例就接收到onNewIntent()的调用,而不是创建一个新的实例.
这产生与”singleTop”模式相同的行为.
FLAG_ACTIVITY_CLEAR_TOP
如果要启动的activity已经在当前任务中运行,那么在它之上的所有其它的activity都被销毁掉,然后这个activity被恢复,而且通过onNewIntent(),initent被发送到这个activity(现在位于顶部了)
没有launchMode属性值对应这种行为.
FLAG_ACTIVITY_CLEAR_TOP多数时候与FLAG_ACTIVITY_NEW_TASK联用.当一起使用时,会在其它任务中寻找一个已存在的activity实例并其把它放到一个可以响应intent的位置.
注:如果Activity的启动模式是”standard”,FLAG_ACTIVITY_CLEAR_TOP会导致已存在的activity被从栈中移除然后在这个位置创建一个新的实例来处理到来的intent.这是因为”standard”模式会导致总是为新的intent创建新的实例.
处理任务亲和力
亲和力表明了一个activity"心仪"哪个任务.默认下,属于同一个应用的所有activities之间具有相同的任务亲和力.所以,默认下,一个应用的所有activities首选属于同一任务.然而,你可以修改一个activity的默认任务亲和力.定义于不同应用的Activities可以具有相同的任务亲和力,或者同一应用中的activities可以分配不同的任务亲和力.
你可以使用元素的taskAffinity属性来修改一个activity的任务亲和力.taskAffinity属性使用字符串作为值,这个字符串必须与在中声明的默认包名不同,因为系统使用包名来标识默认的任务亲和力.
亲和力在以下两种情况起作用:
当启动一个activity的intent包含FLAG_ACTIVITY_NEW_TASK标志.
一个新的activity默认是在调用startActivity()的activity所在的任务中安置.然而,如果传给startActivity()的intent包含了FLAG_ACTIVITY_NEW_TASK标志,系统就会查找另一个能安置这个新*强调内容*activity的任务.通常,它会是一个新任务.然而但是,并不是必须这样做.如果有一个已存在的任务具有与新activity相同的亲和力,那么这个activity就被启动并安置到这个已存在的任务中.如果没有这样的任务,才开始一个新的任务.
如果这个标志导致了一个activity在一个新的任务中启动然后用户按下了HOME键离开了这个新任务,那么必须有一些方法使得用户可以重新回到这个任务.一些实体(比如通知管理器)总是在一个另外的任务中启动activity而从不作为自己任务的一部分,于是它总是把FLAG_ACTIVITY_NEW_TASK设置到传给startActivity()的intents中.如果你有一个activity可以被外部实体使用这个标志调用,应小心用户可能用一个独立的方法回到这个启动的任务,比如使用启动图标(任务的根activity有一个CATEGORY_LAUNCHERintent过滤器).-翻译得挺难受,这句话也就是说,只要使用了相同的亲和力,用户就能回到这个已启动的任务中.
当一个activity的allowTaskReparenting属性为”true”时.
在此情况下,activity可以从启动它的任务移动到一个亲和的任务中,当后一个任务来到前台时.
例如,假设一个报告所选城市的天气状况的activity是作为一个旅游应用的一部分.它与同一个应用中的其它activity具有相同的亲和力(默认的application亲合力)并且它被允许重认父母.当你的一个activity启动了这个天气预报activity,它起初是与你的actvity属于同一个任务.然而,当旅游应用的任务进入前台时,天气预报activity就被重新分配到这个任务并在其只显示.
小提示::如果一个.apk文件包含多个从用户角度所认为的”应用”,你可能想通过为activity指定属性taskAffinity来使它们连接到不同的”应用”.
清空后退栈
如果用户离开了一个任务很长一段时间,系统会清空任务中除了根activity之外的所有其它activity.当用户重新返回这个任务时,只有根activity被恢复.系统之所以这样做,是因为经过一大段时间之后,用户很可能已抛弃掉他们已经做的并且回到任务开始做一些新的事情.
有一些activity属性你可以用来改变这种行为:
alwaysRetainTaskState
如果任务的根activity的这个属性被设置为”true”,前面所述的默认行为就不会发生.任务保持所有的后退栈中的activity,即使经过很长一段时间.
ClearTaskOnLaunch
如果任务的根activity的这个属性被设置为”true”,在用户离开任务再回来时,栈中是清空到只剩下根activity.换句话说,它是与alwaysRetainTaskState反着来的.用户回到任务时永远见到的是初始状态,即使只离开了一小会.
finishOnTaskLaunch
这个属性很像clearTaskOnLaunch,但是它作用于一个单独的activity,而不是整个任务.它也可以导致任何activity死亡,包含根activity.当它被置为”true”时,activity只在当前会话中存活.如果用户离开然后回来,它就已经不在了.
启动一个task
你可以设置一个activity为一个任务的入口,通过给它一个值为”android.intent.action.MAIN”的intent过滤器”和一个值为”android.intent.category.LAUNCHER”的过滤器.例如:
<activity... >
<intent-filter... >
<actionandroid:name="android.intent.action.MAIN" />
<categoryandroid:name="android.intent.category.LAUNCHER" />
</intent-filter>
...
</activity>
一个intent这种类型的过滤器导致activity的一个图标和标签被显示于应用启动界面上.使得用户可以启动这个activity并且再次回到这个任务.
这第二个能力是很重要的:用户必须能离开一个任务并且之后还能通过启动器回来.为此,两种使得activity永远在新任务中启动的启动模式:”singleTask”和”singleInstance”,应该只在当activity具有ACTION_MAIN和CATEGORY_LAUNCHER过滤器时使用.想像一下,例如,如果没有这些过滤器将会发生什么:一个intent启动一个”singleTask”activity,在一个新的任务中初始化,并且用户在这个任务中忙乎了一些时间.然后用户按下HOME按钮.任务现在被移到后台并且不可见了.因为这个activity不是应用的启动activity,用户就再也没有办法回到这个任务了.
但遇到那些你不希望用户能够回到一个activity的情况时怎么办呢?有办法:设置元素的finishOnTaskLaunch属性为”true”!
API