Tasks and Back Stack

在一个应用中通常包含多个activity。每个activity都会让用户执行几个特定的动作并且能够启动其它的activity。例如,在一个email应用中,其中有一个activity用来显示新email列表,另一个activity用来显示被选择email的内容。

一个activity也可以启动在其它应用中的activity。例如,如果你的应用想要发送email,你可以通过定义一个intent,设置intent的action为send,包含一些如发生地址和数据本事,通过这个intent来启动具备这个功能的activity,哪怕这个activity在其它的应用中。当email被发送后,你的activity将会resume,而这个发送email的activity看起来像是你应用中的一个activity。即便这个activity可能在其它的应用中,android通过将这两个activity放在同一个task中,这种保证了用户体验。

一个task是多个activity的集合,用来执行特定的任务。所有的activity都放在stack(即back stack)中,按照打开的顺利排序。

设备中的主屏幕是大多数task启动的地方,当用户单击一个应用图标时,相应的应用task就放到前端。如果没有这个应用对应的task(也就是说这个应用最近没被使用),那么系统将会创建一个新的task,应用的main activity将会作为root在被打开并存放在stack中。

若当前的activity启动另外一个新的activity时,这个新的activity就会被放置到stack的顶端并获得用户焦点(focus)。之前的那个activity将会被停止但仍存在stack中。当一个activity被停止后,系统将会保存这个activity停止是的UI状态。当用户按下返回按钮是,当前的activity将会从stack抛出(即当前activity被销毁),而前一个activity将会复原(resume)(恢复到之前保存的状态)。在stack中的activity位置不会重新变化,除了进栈或出栈操作(当被当前的activity启动,新的activity就进栈,如果用户按下返回按钮,就被出栈)。在back stack中,activity是后进先出。下图展示了一个back stack的变化过程:


如果用户连续按下返回按钮,那么每个activity都会被出栈并恢复到之前的状态,直到用户返回到主屏幕(或返回这个task开始运行的activity)。当所有的activity都从stack出栈,这个stack将不再存在。

当用户启动一个新的task或者按下Home键,之前的那个task将会移到后台。在后台中,task所有的activity都被停止,但是这个task关联的back stack是完好的,这个task仅仅失去焦点,如下图所示:


一个task可以从后台返回到前端并恢复之前的状态。比如假设当前task A在它的stack中包含三个activity,用户突然按下home键,然后在主界面启动一个新的应用。当值屏幕出现时,task A进入后台。当新的应用被启动后,系统为新的应用建立一个新的task B。当用户与第二个应用(task B)交互完并返回到主屏幕在启动第一个应用(task A)是,task A将重新回到前端,在stack中的三个activity都是完好的,此时stack最顶端的activity将会被复原(resume)。当然此时用户也可以在回到主屏幕闭并重新打开第二个应用。

注意:可能在后台会有多个task,如果在同一时间用户运行多个后台task,那么系统可能会开始销毁后台的activity来回收内存,导致对应的activity的之前的状态丢失,详见Activity state。

因为在back stack的activity的位置相对不变,如果你的应用允许用户重新启动之前在back stack的一个activity,那么系统将会重新创建一个对应activity的实例,并将这个activity放入back stack中(而不是将之前存在的同样的activity放到stack的顶端)。所以在你的应用中,你一个activity可能被多次实例化(或者在其它的task中被实例话),如下图所示:


如果用户按下返回键,每个activity的实例都是按照它们被打开的顺序复原(每个都有自己的UI状态)。当然你也可以设置你的activity只能被实例化一次,详见Managing Tasks。

下面总结了activity和task的默认行为:

1. 当activity a 启动activity b,activity a 被停止,但系统会保存它的状态。如果用户在activity b 中按下返回键,activity a将会复原(resume)并恢复之前的状态。

2. 当用户通过按下Home键离开当前task时,当前的activity被停止,它所在的task进入后台,系统将会保存这个task中所有activity的状态。如果用户后来通过单击这个task 应用的图标重启这个task,这个task将会回到前端并复原在它stack顶端的activity的状态。

3. 如果用户按下返回按钮,当前的activity将会冲stack出栈并被销毁,此时在stack顶端的activity将会被复原。当一个activity被销毁是,系统不会保存这个activity的状态

4. activity可以在一个task或其它task被多次实例化。

导航设计:关于更多app  导航如何工作详见Navigation。

Saving Activity State (保存Activity的状态)

系统会保存被停止的Activity的状态,所以当用户通过返回键返回到之前的Activity是,这个Activity的UI跟之前是一样的。但是,你也许(应该)通过回调函数保存这些状态信息,以免当Activity被销毁或者重新创建。

当一个Activity被停止(比如当启动一个新的Activity或者Activity的task被移到后台是),系统估计会因为要回收内存而销毁这个Activity,当发生这种情况是,这个Activity的状态信息将丢失。当这种情况发送是,系统仍然直到这个Activity在back state中,但是当这个Activity被放置到stack的顶端时,系统只能重新创建它(而不是复原它)。为了不丢失用户之前的工作,最好实在函数onSaveInstanceState()中保存相关信息。

关于更多这部分信息详见Activity文档。

Managing Tasks (管理任务)

系统将所有的Activity连续启动的Activity放在一个task中,并在stack遵循后进先出原则,这个方法对大多数的应用都行得通,你不必担心系统你的Activity是怎么跟task关联或者在back stack中怎么放置。但是,当你想打破这种正常方式是,比如你希望你的一个Activity在一个新的task中被启动(而不是在当前的task启动),或者当启动一个Activity是,你希望将之前启动过的这个Activity放置到前端(而不是创建一个新的Activity实例),或者你希望当用户离开当前task时,back stack中除了root Activity能保存外其它Activity都被清除。

你可以通过在manifest 中的<activity>元素或者设置intent相应的参数来实现上面的任务。

在这方面,你可以试下下面的<activity>参数:

taskAffinity

launchMode

allowTaskReparenting

clearTaskOnLaunch

alwaysRetainTaskState

finishOnTaskLaunch

也可以使用intent中的标志:

FLAG_ACTIVITY_NEW_TASK

FLAG_ACTIVITY_CLEAR_TOP

FLAG_ACTIVITY_SINGLE_TOP

接下来会展示如何通过设置manifest的属性和intent的flags来定义Activity如何与task关联和在back stack的行为。

注意:大多数的应用不该打断Activity和task的默认行为,如果你确定要修改Activity的默认行为,小心使用并在这个Activity被加载后,当从其它Activity返回到这个Activity是否可行。一定要测试返回到这个Activity是否是用户期望的。

Defining launch modes(定义加载模式)

加载模式让你能够定义一个新的Activity实例与当前的task如何相关联,你可以通过两种方式来定义这种关联性:

通过manifest 文件

你可以在Activity中定义这个Activity如何与task关联

通过intent flags

当调用startActivity()启动新的Activity是,可以在传入这个函数的参数intent中设置flag来定义这个Activity如何与task关联。
这样,当Activity a 启动Activity b 时,Activity b 可以在manifest文件中定义如何与当前的task关联,而Activity a 也可以在intent中设置Activity b 如何与当前task关联。如果这两个方式都使用来设置Activity b如何与当前task关联,那么通过Activity a 的intent设置的关联性比通过Activity b 的manifest设置的关联性优先级高。

注意:一些在manifest定义的加载模式在intent中没有对应的flag,类似,一些在intent中定义的加载模式在manifest不能定义。

Using the manifest file (使用manifest文件)

当在manifest文件中定义一个Activity时,你可以通过<activity>元素里面的launchMode模式来定义这个Activity如何与task关联、

launchMode属性中定义了这个Activity如何与task关联的说明。有四种不同的加载模式:

“standard” (默认模式)

创建的新的Activity实例位于启动它的task中或将intent导入到这个Activity。Activity可以被多次实例化,可以在不同的task被实例化,一个task可以有多个实例。

“singleTop”

如果一个Activity的实例已经在当前的task,系统将会通过调用onNewIntent()将相应的intent导入到这个实例,而不是创建一个新的Activity实例。这个Activity可以被多事 实例化,可以在不同的task实例这个Activity,一个task可以有多个实例(只能在当前在back stack顶端的Activity不是要创建的Activity实例)。

假设现在一个task的back stack中有A-B-C-D四个Activity,其中D在顶端,如果现在intent要创建一个Activity D的实例,如果是默认的标准加载模式,系统将会创建一个 新的Activity D 的实例,即现在的stack为A-B-C-D-D;如果加载模式为“singleTop”模式,现有的Activity D实例通过onNewIntent()接收这个intent,因为当前Activity D 位于 back stack的顶端,back stack现在还是A-B-C-D。但是如果是一个请求启动Activity B的intent,那么系统捡回创建一个新的Activity B实例添加到back stack中,不论那种模 式。

注意:

“singleTask”

系统创建一个新的task并在这个新的task实例化相应的Activity。但是如果这个Activity的实例在另一个task中,系统将会把这个intent导入到那个task中,通过调用Activity 的onNewIntent()将对应的intent导入到已经存在的实例中,而不是创建一个新的实例。在一个task中同时只能哟一个Activity实例存在。

注意:虽然会在一个新的task创建新的Activity,但是当用户按下返回按钮时,还是会返回到之前的Activity中。

“singleInstance”

跟“singleTask”一样,只不过系统将不会在新建的task中创建新的Activity实例,也就是这个task只有这个实例,通过这种方式加载的Activity都会在一个独立的task

中打开。


一个常见的例子是android 的浏览器应用声明网页浏览器的Activity都应该只有自己的task中加载,即在<activity>元素中指定“singleTask”模式。也就是如果在你的应用中用一个intent来打开一个浏览器,那么这个浏览器Activity将不会在你应用中的task打开,而是创建一个新的task来启动这个浏览器Activity,或者如果浏览器Activity已经在另一个task中打开,那么对应的task将会接收这个新的intent。

无论是创建一个新的task启动这个Activity或者传入一个已经存在其它task的Activity,当用户按下返回按钮是,用户将会回到之前的Activity中。如果你指定”singleTask“加载模式,那么如果后台的一个task正好有这个Activity的实例,那么后台的那个task将会回到前端。在这种情况下,后台的task对应的stack都会放到stack的顶端。如下图所示:


更多关于加载模式详见<activity>元素。

注意:在manifest制定的加载模式优先级比在intent用flag指定的加载模式的优先级低。

Using Intent flags (使用intent flag)

可以在intent中的flag设置加载模式,有下面几个flag可以使用:

FLAG_ACTIVITY_NEW_TASK

跟在manifest中指定“singleTask”的加载模式一样,即在一个新的task中启动这个Activity,如果这个Activity的实例已经在一个后台的task中,那么后台的这个task将会 移到前端,里面对应的Activity将会通过onNewIntent()接收到这个新的intent。

FLAG_ACTIVITY_SINGLE_TOP

更在manifest中指定”singleTop“的加载模式一样,即要启动的新的Activity正好是当前在前端的Activity(即在当前task的back stack的顶端),那么在顶端的这个Activity 通过onNewIntent()得到新的intent,而不是创建一个新的Activity实例。

FLAG_ACTIVITY_CLEAR_TOP

如果要启动的Activity已经在当前的task中运行,那么系统将会把这个Activity顶部的所有Activity销毁,这样这个Activity就处于顶端,获得用户焦点,这个Activity通过 onNewIntent()获得新的intent。

在manifest中没有对应的加载模式。

FLAG_ACTIVITY_CLEAR_TOP经常和FLAG_ACTIVITY_NEW_TASK联合使用,通常用来定位一个在其它task的Activity,使得这个Activity位于其它task中的back stac k的顶端以便接收intent。


Handing affinities (处理affinities)

affinity指的是Activity更倾向在那个task中。在一个应用中,所有的Activity彼此键都有一个affinity。所以默认的情况下,同一个应用的所遇Activity都希望在同一个task中,当然你也可以修改Activity的默认affinity。在不同的应用中定义的Activity也可以共享一个affinity,类似,在同一个应用中的Activity也可以设置不同的task affinities。

你可以通过在manifest中的<activity>元素的taskAffinity属性来设置Activity的affinity。

taskAffinity接收一个字符串值,这个字符串必须更包名不一样,因为系统使用包名来鉴定应用默认的task 的affinity。

affinity在这两种情况下使用:

1. 当intent的flag为FLAG_ACTIVITY_NEW_TASK:新的Activity通常加载到启动这个Activity所在的task中,并放置到相同的back stack中。但是如果intent包含FLAG_ACTIVITY_NEW_TASK标志,系统将会在一个不同的task中打开这个新的Activity,通常是创建一个新的task,但也不一直是这样,当存在一个task的affinity和新的Activity相同,那么这个Activity就会在相同affinity的task中加载。

如果这个flag导致新建一个task来加载这个新的Activity,那么当用户按下Home键离开这个Activity时,应该要有些方法使得用户能够返回到这个task中,一些工具(比如notification manager)通常在另外一个task中启动新的Activity,所以在intent通常包含FALG_ACTIVITY_NEW_TASK标志,尼国你的Activity能被其它工具通过使用这个标志调用,那么应该注意用户能够返回启动这个Activity的task,比如通过加载图标。

2. 当一个Activity的allowTaskReparenting的属性设置为true时:在这种情况下,Activity可以从启动它的task移到有相同的affinity的task中。

比如,在travel应用中,有一个能报告选定城市的天气状况的Activity,这个Activity拥有和这个应用其它Activity相同的affinity(默认的应用affinity),这个Activity允许通过这个affinity被重新分配task。当你的一个Activity启动报告天气状况的那个Activity是,这个Activity在与你的Activity相同的task中。当时当travel应用的task回到前端时,这个报告天气状况的activi就回到travel应用中。

建议:如果一个.apk文件中包含多个应用(从用户的角度看),那么你可能你需要使用taskAffinity来分配不同的affinities给每个应用。

Clearing the back stack (清除back stack)

如果用户离开一个task很长时间,那么系统将会清除这个task所有的Activity,除了root Activity。当用户回到这个task时,只有root Activity会被恢复。系统这样做是觉得用户长久没有回到这个task,可能已经放弃之前做的东东,当过一段长时间在回来时,有可能是想做新的事情。

可以通过下面的Activity属性来改变这个行为:

alwaysRetainTaskState:

如果在一个task的root Activity这个属性设置为”true“,那么一段长时间后,这个task的所有Activity还是存在back stack中。

clearTaskOnLaunch:

如果在一个task的root Activity这个属性设置为”true“,一旦用户离开这个task,那么系统将会清除back stack所有的Activity。正好与alwayRetainTaskState相反。

finishOnTaskLaunch:

更clearTaskOnLaunch类似,但只针对一个Activity。可以导致任何一个Activity被清除,包括root Activity。当这个值为真时,对应的Activity只存在当前的session中,如果用户离开这个task然后返回,那么这个Activity将不会在出现。

Starting a task (启动一个task)

可以通过在intent filter中设置一个Activity为一个task的入口点。通过下面代码设置:

<activity ... >
    <intent-filter ... >
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    ...
</activity>

这样设置的Activity将会在应用界面中有一个对应的图标和标签。用于可以通过这个图标来加载Activity或返回到之前打开的Activity。

用户可以先离开当前的task,然后相应的图标返回到之前的应用。在这种情况下,”singleTask“和”singleInstance“用于启动一个新的task只能用于action为ACTION_MAIN和category为CATEGORY_LAUNCHER的Activity中。想象下,如果没有这个intent filter,那么当用户打开一个这样的Activity后,按下home键离开,这个task就进入到后台不可见了,这时候用户就没有办法回到这个task,因为没有对应的图标来加载。

如果你不让用户返回到之前的Activity中,可以将<activity>的finishOnTaskLaunch设置为true。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值