2.2.3 Tasks and Back Stack

本文详细介绍了Android系统中Task和BackStack的工作原理,包括Activity的启动模式、任务亲和力及如何管理和清理BackStack等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Task and Back Stack

       一个app中通常包括多个activity。每一个activity都应该设计成具有可以实现不同的功能并且可以启动别的activity的功能,例如,一个email app有一个显示新邮件列表的activity,当用户选择其中一封邮件时,会打开一个新的activity显示邮件内容。

       一个activity甚至可以启动设备中别的app中的activity,例如,如果你的app想要发送一封邮件,你可以定义一个intent执行一个“发送”的动作(action),并且在这个intent中包含一些所需数据,例如邮箱地址和需要发送的信息。一个存在于别的app中已经被定义为可以接收并处理这一类型intent的activity就会启动。(如果设备上有多个activity都提供处理此类intent的服务,系统会让用户选择其中之一启动)当email发送时,你的activity会重新运行(resumed),看上去发送email的activity是你的app的一部分。尽管这些activity来自别的app,android通过保持这两个activity在同一个task中使得用户可以享受这样的无缝体验。

       一个task是一个activity的集合,可以在用户交互时执行一些操作。这些打开的activity都被安排在一个堆栈中(stack, the “back stack”)。

       设备的主界面是大部分task开始的地方,当用户点击applicationlauncher中的一个图标(或者主界面的快捷方式图标),app的task就会出现且出现在前台(foreground)。如果这个app没有task存在(这个app最近没有使用),则一个新的task会被创建并且该app的“main”activity会打开,作为root activity被存放在堆栈(stack)中。

       在当前activity打开另一个activity时,新的activity会被堆放到stack的顶部且获得焦点。前一个activity仍然保存在stack中,但是它已经被停止了。当一个activity停止时,系统仍然保持其用户界面的当前状态。当用户点击“返回”按钮时,当前activity从stack的顶部弹出(该activity被摧毁)而原来的activity恢复(resumed)(activity原先的UI状态恢复)。Stack中的activity永远不会重排,只有进栈(push)和出栈(pop)的操作——当启动当前activity时,会(push)堆放在栈顶,当用户使用返回按钮离开activity时(pop)抛出stack。同样的,back stack同样遵循“后进先出(last in, first out)”的原则。


 

 

如上图所示,可视化了每个操作对应的时间点在back stack里的行为。即新的activity开启会被放在back stack顶端,当用户离开前端的activity时,它被销毁,而下一个activity则重新启动。

如果用户持续点击“返回”按钮,在stack顶端的activity会被依次抛出直到用户返回主界面(or to whichever activity was running when thetask began)。当所有的activity都从stack中被移除,task将不会再存在。

一个task是一个紧密结合的单元,它可以在用户开始一个新task或者跳转到主界面的(点击“Home”键)时候整个移动到“background”(后台)中。当在后台的时候,所有的activity都停止,然而back stack依然保持完整——如图2(右图)显示的,当别的task发生的时候,这个task只是单纯的失去了焦点。

<TaskB在前端获得用户的交互,而Task A则在后台等待重启>

一个task依然可以返回前端,重启并维持它们离开前的样子。例如,当前task(TaskA)有三个activity存在于它的stack中——有两个在当前activity之下。用户点击“Home”键,然后从applauncher中启动一个新的app。当主界面出现的时候,Task A被放置在后台,当新的app开启的时候,系统为新的app创建了一个新的task(TaskB),在这个task中的stack放置新app的activity。在与新的app交互完成之后,用户再次返回主界面选择先前的app,则原本的TaskA启动。此时Task A出现在前端——它的stack中的三个activity都保持完整且stack顶端的activity重启。此时用户也可以通过点击“Home”键然后点击开启TaskB的app的图标来转回TaskB。这就是Android系统的多任务操作。

Note: 多任务可以同时被后台保管。如果用户同时在后台开启多个任务,系统可能会毁掉其中的一些来释放资源,这样会导致activity的状态丢失。可以参见如下文档Activity state。

 

<一个activity被多次实例化>

 

因为在back stack中的activity不允许重排,如果你的app允许用户从一个以上的activity开启一个特殊的activity,系统会创建一个新的activity实例并把它堆放在stack的顶端。(而不是将原先有的activity拿出来放置到顶端)。因此,如图三所示,app中的activity可能被多次实例化(甚至是来自不同task的activity)。如果用户使用“返回”键向后导航,每个activity的实例都会被显示出来(它们的UI状态)。如果你不希望一个activity被实例化多次,你可以控制这个行为。如何控制可以参见后面部分Managing Task

总结Activity和Task的默认行为:

●       当Activity A开启Activity B时,ActivityA停止,但是系统保持其状态(例如滚动条的位置和文本输入形式)。当用户在Activity B中按下返回按钮,Activity A会恢复其状态重启

●       当用户点击“Home”键离开一个task的时候,当前activity停止且task会放进后台。系统保留task中每一个activity的状态。如果用户稍后重新返回该task,task返回前台并重启stack顶端的activity

●       如果用户点击返回键,当前activity从stack弹出并销毁。Stack中前一个activity中期。当一个activity销毁时,系统不再保留这个activity的状态。

●       即使是从别的task中,一个activity实例可以被初始化多次

 

NavigationDesign(导航设计)

参见Android Design’s Navigationguide

 

Saving Activity State

       正如上述讨论的,当activity停止的时候,系统默认保存该activity的状态。当用户导航回原先的activity时,它恢复成用户离开时的状态。然而,你可以——也应该使用回调方法保存activity的状态,以免activity被销毁后再次重建无法恢复原来的状态。

       当系统停止你的activity中的一个时(例如一个新的activity启动或者task被移动到后台),系统可能为了释放内存资源而销毁你的activity,此时你的activity的状态数据则会丢失。为了避免丢失,你可以主动调用onSaveInstanceState()方法保存数据

 

Managing Task

       Android管理task和backstack的方法:将所有启动的activity按顺序放置在同一个task里的stack中,且stack遵循“last in, first out”原则。这个管理方法对大部分app都很有效,你也不需要担心你的activity如何与task结合,也不需要担心它们如何在backstack中存在。然而,如果你希望做一些特殊的操作,例如一个存在于你的app中的activity在新的task中启动(而非放置在当前task的stack顶端),或者当你启动一个activity时,你希望将已经存在的activity放置到stack顶端(而不是在stack顶端创建一个新的activity实例),再或者,当用户离开当前task时你希望清空除了rootactivity(就是“main”activity)以外的所有activity。

       通过使用manifest文件中的<activity>元素以及你传递到startActivity()中的intent,你可以完成上述操作。

你可以在<activity>中使用的属性包括:

taskAffinity

launchMode

allowTaskReparenting

clearTaskOnLaunch

alwaysRetainTaskState

finishOnTaskLaunch

你可以使用的主要intent flag有:

FLAG_ACTIVITY_NEW_TASK

FLAG_ACTIVITY_CLEAR_TOP

FLAG_ACTIVITY_SINGLE_TOP 

       在下面的部分,你可以看到如何使用这些manifest属性和intent flag定义activity如何与task连接以及它们在back stack中如何表现的。

 

Caution:大部分app都不需要打断默认设置的activity和task的行为。如果必须打断默认操作,使用警告操作并测试activity在launch、从别的activity或task通过back键导航回来时的可用性。

 

Defining launch modes

       Launchmode允许你定义一个新的activity实例如何与当前task关联。你可以用如下两个方式定义不同的launch mode:

Using the manifest file

       当你在manifest中定义一个activity时,你可以声明这个activity应该如何在其启动的时候与task相关联

Using Intent flags

       当你调用startActivity()时,你可以在Intent中列入一个flag声明这个新的activity如何与当前task关联。

 

       同样的,如果Activity A启动Activity B,ActivityB可以在它的manifest文件中申明它应该与当前task相关联,同时ActivityA也可以要求Activity B如何与当前task相关联。如果两者都定义了ActivityB应该如何与向前task关联,那么ActivityA的要求(定义在Intent中)级别要比Activity B的要求(定义在manifest中)级别高。

 

Note:一些launch模式对manifest文件有效而对intent中的flag定义无效,同样的,也有一些launch模式对intent的flag有效而对manifest文件中的定义无效。

 

Usingthe manifest file

       在manifest文件中定义activity时,你可以使用<activity>的launchMode标签定义activity如何与task关联。

       launchMode标签指定该activity如何be launched into a task.在launchMode标签中有四中不同的launch模式:

“standard”(the default mode)

       默认。该activity可以被初始化多次,每一个实例可以属于不同的task,同时一个task可以拥有多个实例。

“singleTop”

       如果一个activity的实例已经存在于当前task的顶端,系统通过调用onNewIntent()方法将intent发送给这个实例,而不是创建一个新的activity实例。这个activity可以被实例化多次,每一个实例可以属于不同的 task,一个task可以拥有多个实例。(但是只要这个activity在back stack的顶端,它就不是一个现存的activity实例)

       例如,假设一个task的backstack由root activity A,activity B,C和D组成,其中D在顶端,则stack的顺序是A-B-C-D。一个intent到达传递给Activity D,如果D是默认的”standard” launchmode,一个新的D类的实例会被创建并且stack变成A-B-C-D-D。然而,如果D的launchmode是singleTop,已经存在的D实例会通过onNewIntent()方法接收到这个intent,因此此时Activity D正处在stack的顶端——所以stack依然保持A-B-C-D。然而,如果一个intent是传递给Activity B,即使它的launch mode是singleTop系统依然会创建一个新的Activity B实例并把它放在stack顶端。

 

Note:当一个activity的实例被创建的时候,用户可以点击back键返回先前一个activity。但是当一个已经存在的activity实例处理一个新的intent时,用户不能通过点击back键返回activity之前的状态,除非新的intent已经到达onNewIntent()

 

singleTask”(广播!!单独开一个task来处理这个activity实例)

       系统创建一个新的task并在新的task的根部(root)初始化该activity。然而,如果该activity的实例已经存在于别的task中,系统会通过onNewIntent()方法将intent传递给已经存在的activity实例,而不会创建一个新的实例。这个activity的实例在同一时间只能存在一个。

 

Note:尽管这个activity在新的task中开启,当用户点击back键时依然会返回之前的activity。

 

“singleInstance”(广播!!即这个activity独占一个task

       和singleTask相似,除了系统不launch任何别的activity到这个持有该activity实例的task中。这个activity一直是独一无二并且它是这个task中的唯一成员。它启动的别的任何一个activity都会在别的task中运行。

 

例如,Android浏览器app声明webbrowser activity应该永远在它独自的task中打开——就通过在<activity>中声明singleTask的launch mode。这意味着如果你的app发出一个intent打开Android浏览器时,它的activity并不放在你的app的task中。如果浏览器已经有一个task在后台运行,则这个task会被放置到前端接收你发出的intent。

       不管一个activity被在一个新的task中开启,或者是与开启它的activity存在于同一个task中,返回键永远能够让用户返回之前一个activity。然而,如果你启动的activity是singleTask launch mode的,并且activity的实例已经存在于后台的task中,那么整个task会被带到前端。这时,back stack包括所有被带到前台的task的activity,并且这些activity会被放到stack的顶端。如下图所示:

<上图解释了一个launch mode是“singleTask“的activity如何被添加到back stack的顶端。如果一个activity已经是后台运行的task的一部分,并且带有它自己的back stack,那么整个back stack会被带到前端并且放在当前task的顶部>

 

更多使用在manifest文件中launch mode的信息,请参考<activity>文档。

 

Note:在manifest使用launchMode标签定义的activity的launch mode可能会被intent中使用的flag覆盖。

 

UsingIntent flags

       当启动一个activity时,你可以通过你传递给startActivity()的包含了flag的intent来修改这个activity和它的task默认联系方式(defaultassociation)。你可以用来修改默认操作的flag包括:

FLAG_ACTIVITY_NEW_TASK

       在新的task中开启这个activity。如果已经有一个正在运行的task中包含你准备打开的这个activity,则该task会被放置到前端,恢复activity原先的状态,并且activity会在onNewIntent()方法中收到intent。该过程与标记了”singleTask”launchMode值的activity处理方式相同。

 

FLAG_ACTIVITY_SINGLE_TOP

       如果被开启的activity正好是当前activity(即处在back stack的顶端),则已经存在的activity实例会收到onNewIntent()的调用,而不是重新创建一个新的activity。过程与“singleTop”相同。

 

FLAG_ACTIVITY_CLEAR_TOP

       如果将要被开启的activity已经在当前task中运行,系统并不会创建一个新的activity实例,相反会销毁处在这个activity上面的所有的activity,使将要被开启的activity处于stack的顶端,并将intent传递给这个重新启动(resumed)的activity的onNewIntent()方法中。

       在launchMode标签中没有值可以代表这个过程。

       FLAG_ACTIVITY_CLEAR_TOPFLAG_ACTIVITY_NEW_TASK的联合使用是最常见的intent的flag的使用方式。当同时使用它们时,可以将一个已经存在的activity定位到别的task中,并且将它放置在可以对intent产生回应的位置。

 

Note:如果一个activity的launch mode是“standard”,它也是从stack中移除然后创建一个新的实例接收传递过来的intent。所以这个模式的activity接收到intent总会创建新的实例。

 

Handling affinities

       “affinity”指一个activity倾向于属于哪一个task。默认情况下,所有来自同一个app的activity互相都有一个affinity,所以默认情况下来自同一个app的所有activity都在同一个task下。然而,你为一个activity修改其默认affinity。这样定义在不同app下的activity可以共享一个activity,或者同一个app下的activity可以分配到不同task的affinity。

       你可以在<activity>元素中使用taskAffinity标签定义给出的activity的affinity。

       taskAffinity标签是String值,这个值在<manifest>标签中定义的整个默认包的名字都必须是独一无二的,因为系统会使用这个名字从app中鉴别出默认taskaffinity。

       Affinity会在下面两种情况下发挥作用:

●       当一个带有FLAG_ACTIVITY_NEW_TASK标志的intent launch一个activity时

默认情况下,一个新的activity会通过startActivity()launch进入task,它会与调用者(使用startActivity(intent)的activity)一起进入同一个back stack中。然而,如果传递到startActivity()方法中的intent带有FLAG_ACTIVITY_NEW_TASK标记,那么系统会找别的task接收这个新的activity,通常情况下,这个“别的”task是一个新的task。当然,这种情况也不一定会发生。如果有一个已经存在的task带有与新的activity相同的affinity,那么这个activity会launch在该task中。如果不存在这样的task,再创建一个新的task来接收。

如果这个flag引起一个activity开启一个新的task并且用户点击了“Home”键离开它,那么一定会有方法让用户通过导航返回到这个task中。一些实体(像是通知管理/notificationmanager)总是在外部task中开启activity,绝不作为调用它们的activity的task的一部分。所以它们总是在传递给startActivity()的intent中添加FLAG_ACTIVITY_NEW_TASK标志。如果你的activity可以被外部实体调用,那么你可能会需要使用到这个flag,要注意用户可能会返回到这个task中。///If you have an activity that can be invoked byan external entity that might use this flag, take care that the user has aindependent way to get back to the task that’s started, such as with a launchericon (the root activity of the task has a CATEGOTY_LAUNCHERintent filter; see the Starting a tasksection below.)

●       当一个activity的allowTaskReparenting属性设置为true

在这个情况下,当具有affinity的task移动到前台的时候,该activity可以从它开启的task移动到具有affinity的task

例如,假设一个旅行app中有一个activity可以预报选择城市的天气情况。这个activity与app中的其他activity都有相同的affinity(默认app affinity),同时它允许对这个标签重新处理。当你的activity启动这个天气预报activity时,它最初是属于启动它的activity的task的。然而当这个旅行apptask被放置到前端时,这个天气预报activity会被在再次分配并在旅行app的task中显示。

 

Tip:如果一个.apk文件在用户的视角中有包括超过一个“application“,你可能需要使用taskAffinity标签分配不同的affinity,让不同的activity与不同的application相关联。

 

Clearing the back stack

       如果用户长时间离开一个task,系统会清除这个task中除了root activity以外的所有activity。当用户重新返回这个task时,只有rootactivity被恢复。用户长时间不操作会让系统认为用户已经丢弃了原先的操作,并且想开始一些新的操作。

       下面的一些activity属性可以用来修改这个行为:

alwaysRetainTaskState

       如果这个标签在task的rootactivity中设置为“true“,那么默认行为(删掉所有只保留root activity)就不会发生了。Task会在很长一段时间都保留所有的activity。

clearTaskOnLaunch

       如果这个标签在task的rootactivity中设置为“true“,那么无论是用户离开task或者是再次回到这个task,stack都会清除除了root activity以外的所有activity。换而言之,它是alwaysRetainTaskState的反面。用户总会在这个task的初始化状态下返回它(初始化也就只有root activity一个),即使用户只是离开了一小会儿。

finishOnTaskLaunch

这个标签类似clearTaskOnLaunch,但是它只对单独的activity操作,而不对整个task操作。它可以导致任意一个activity离开,甚至包括root activity。当它设置为“true“的时候,仍然保留在task中的activity只剩下当前运行的部分。如果用户离开再返回这个task,它不再有任何东西呈现出来

 

Starting a Task

       设置一个activity作为一个task的入口的方法是:设置其的intentfilter中的action为“android.intent.action.MAIN“,同时设置intent filter中的category为”android.intent.category.LAUNCHER”即可,如下:

<activity…>

       <intent-filter>

              <actionandroid:name=”android.intent.action.MAIN”/>

              <categoryandroid:name=”android.intent.category.LAUNCHER”/>

       </intent-filter>

</activity>

 

这种类型的intent filter可以影响在app launcher显示的activity的图标和标签。

第二个能力比较重要:用户可以离开这个task然后稍后返回使用这个activity的launcher,因为这个原因,总是初始化task的activity应该在同时有ACTION_MAINCATEGORY_LAUNCHER这两个filter的情况下才可以使用这两个launcher mode, ”singleTask”singleInstance。想想一下,如果这个filter丢失了会发生什么:一个intent launch一个”singleTask”的activity,初始化一个新的task,用户使用了一段时间。然后用户点击“Home”。这是这个task被放置到后台,不可见。现在用户已经没有办法再次返回这个task了,因为它在applauncher中已经不可见。

如果你不希望用户返回一个activity,可以设置<activity>finishOnTaskLaunch属性为true即可。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值