Android: 任务和返回堆栈

Task and Back Stack(任务和返回堆栈)

         一个应用程序通常包含多个活动。每一个活动都要围绕一个特定的action来设计,用户能够执行这个action并且能启动其它的活动。例如一个邮件应用可能包含一个活动来显示新邮件列表,当用户选择一个一封邮件,启动一个新的活动来显示这个邮件。

         一个活动甚至可以启动该设备上其它应用的活动。例如如果你的应用想要发送一个邮件,你能定义一个intent,该intent执行”send” action并且包含一些数据,例如:邮件地址和信息。这时候,来自其它原因的活动就会启动,前提是该活动申明能够处理前面所提到的那个类型的intent.在这种情况下,intent是发送一个邮件,所以邮件应用程序的撰写新邮件会响应(当多个活动都能处理这个intent的时候,系统会弹出一个列表给用户选择启动哪一个活动)。当邮件发送完毕以后,重新回到你的活动,看上去似乎邮件活动是你的应用的一部分。即使活动可以来自不同的应用,Android能够通过task(任务)来维持这种无缝的用户体验,做法就是将两个来自不同应用的活动放在同一个任务中。

         当执行某个工作时,跟用户相互作用的一系列的活动组成就组成了一个任务。这些活动是按照他们启动的先后顺序放在一个堆栈中(返回堆栈)。

         设备的Home Screen是大多数任务开始的地方。当用户触摸了应用程序启动器里面的一个图标(或者Home Screen的一个快捷方式),该应用的任务就来到了前台。如果该应用的任务不存在(应用最近没有被使用过),一个新的任务就会创建,并且该应用的main活动会启动作为这个新任务的根活动。

         当当前的活动启动另外一个活动时,新的活动被放到栈顶并获得焦点。之前的活动仍然保留在栈中,但是停止了。当一个活动停止之前,系统会保存它的用户界面的当前状态。当用户按下返回键时,当前的活动会从栈顶弹出(活动被销毁)并且之前的活动得到恢复(UI状态被恢复)。栈中的活动永远都不会重新排列,只能够弹出堆栈或者弹进堆栈-----被当前活动启动的活动会进栈,当用户用返回键离开的时候会出栈。就其本身而言,返回堆栈的操作遵循的是后进先出的原则。

 

         任务是一个紧密结合的单元,当用户开始一个新的任务或者按下Home键回到Home Screen时它能够移动到后台。当它移动到后台以后,所有的活动都停止了。但是该任务的后台堆栈依然原封不动。当用户再次回到该应用时,显示的是该任务后台堆栈的栈顶的活动。

         由于后台堆栈中的活动不会被重新排列,所以当你的应用允许用户通过多个活动启动同一个活动的时候,该活动就会被创建多个实例(而不是把之前的活动实例显示出来)。这种情况下,当用户用返回键时,每一个实例都会按顺序显示一遍。然后如果你不希望一个活动被实例化多次,可以改变这种行为,详见管理任务部分。

 

         活动和任务的默认行为总结:

1.       当活动A 启动活动B时,A 停止,但是系统会保存它的状态(例如Scroll的位置,输入的文字)。如果用户在活动B中按下返回键,活动A会恢复它之前的状态。

2.       当用户按下home键离开一个任务时,当天的活动停止并且它的任务移动到后台。系统保存了该任务中每一个活动的状态。如果用户之后点击启动这个任务的的图标再次将该任务移到前台,并且显示该任务的栈顶的活动。

3.       如果用户按下返回键,当前活动从栈顶弹出并销毁。之前的活动重新显示出来,当活动销毁以后,系统不会保存其状态。

4.       活动能够被多次实例化,甚至是从其它任务实例化。

 

 

Saving Activity State(保存活动的状态)

 

         通过上面的学习,我们知道,当活动停止前,系统的默认行为就是保存该活动的状态。如此,当用户按返回键回到之前的活动时,它的用户界面显示的就是用户离开它时候的样子。然后,你应该利用回调方法前摄地保存活动的状态,以防活动被销毁需要重新创建。

 

         当系统停止一个活动时(例如新的活动要启动或者任务移动到后台),系统可能会完全销毁这个活动当它需要回收系统内存时。在这种情况下,活动状态的信息会丢失,系统知道后台堆栈有这个活动,但是当这个活动回到栈顶时,系统仍然需要重新创建这个活动,而不是恢复它。为了避免丢失用户的数据,你应当在你的活动里面通过实现onSaveInstanceState()方法来前摄性的恢复你的活动。

 

Managing Tasks(管理任务)

 

         系统管理任务和堆栈的方式就是:把所有的活动都按顺序启动在同一个任务和同一个后进先出的堆栈中。这种方式对于大多数应用来讲是很实用的,所以你不用担心你的活动是如何跟任务关联起来的以及它们如何存在于后台堆栈中。然后,某些时候你可能需要打断这种默认的行为。例如你可能需要将你的活动启动在一个新的任务中而不是在当前任务中启动;或者说当你启动一个活动时,你需要的是该活动已经存在的实例,而不是重新创建一个新的实例;再或者当用户离开这个任务时,你需要它的后台堆栈清除掉除了根活动以外的所有活动。

         要实现这些或者更多的操作,你可以应用清单文件中,对活动设置一些属性以及为启动这个活动的intent设置一些标签。

         活动可设置的属性如下:

         taskAffinity

         launchMode

         allowTaskReparenting

         clearTaskOnLaunch

         alwaysRetainTaskState

         finishOnTaskLanuch

        

         Intent 可设置的标签如下:

         FLAG_ACTIVITY_NEW_TASK

         FLAG_ACTIVITY_CLEAR_TOP

         FLAG_ACTIVITY_SIGNAL_TOP

 

         接下来将要讨论的是如何运用这些属性和标签来定义活动跟任务及后台堆栈的关联方式。

警告:大多数应用应该不改变活动和任务之间这种默认行为,如果你觉得你的活动有必要改变这种默认的行为,务必要谨慎使用且测试其可以性,特别是在启动,从其它活动或者任务返回,以及返回键返回。务必要测试导航行为,因为它可能会跟用户期望的行为冲突。

 

 

 

Defining launch modes(定义启动模式)

 

         启动模式允许你定义一个活动的新实例如何跟当前的任务关联。你能够用两种方式来定义不同的启动模式:

        使用manifest file(清单文件)

         使用Intent flags

如果同时使用了两种方式来启动一个活动,则Intent方式更加受推崇。

备注:有些启动模式之能通过manifest file来定义,有些启动模式只能够通过Intent flag定义

 

         使用manifest file

         使用<activity>元素的的launchMode属性来定义活动跟任务的关联方式。launchMode属性指定了一个活动该如何启动到任务中。总共有四种不同的启动模式:

“standard”(默认的模式)

默认情况下,系统会在启动该活动的任务中创建该活动的实例并将intent传递给该实例。这种活动可以被实例化很多次,每个实例能属于不同的任务,并且一个任务中能有多个实例。

“singleTop”

当任务中有一个该活动的实例处于栈顶的位置,系统就会调用onNewIntent()方法将intent传递给该实例,而不是重新创建一个实例。这种活动可以被实例化很多次,每个实例能属于不同的任务,并且一个任务中能有多个实例(但是后台堆栈栈顶的活动不是该活动的实例)。

例如:

一个后台堆栈的组成结构如下:A—B—C—D,其中A是根活动,D位于栈顶。

此时,一个要求启动D活动的intent到来。如果D的启动模式是standard的话,后台堆栈就变成了A—B—C—D1—D2.然而如果D的启动模式是singleTop的话,由于D位于栈顶,所以由栈顶的D实例来接受这个intent,所以后台堆栈仍然是A—B—C—D.但是此时,如果是一个要求启动B活动的intent到来,即使B的启动模式是singleTop的话,B活动的新实例还是会添加到后台堆栈中来,A—B1—C—D—B2.

备注:如果是创建了一个新的实例(A—B—C—D1—D2,当用户点击返回键时,就变成了(A—B—C—D1,但是如果位于栈顶的实例处理了到来的intent,当用户按返回键是不能回到intent到来之前的状态的。

“singleTask”

系统会重新创建一个任务,并且初始化该活动作为任务的根活动。然而如果在一个单独的任务中有一个该活动的实例,系统会调用该实例的onNewIntent()方法来处理这个intent,而不是重新创建一个新的实例。也就是说,该活动同时只能拥有一个实例

备注:尽管该活动会启动在一个新的任务中,但是按下返回键仍然能回到之前的活动。

“singleInstance”

singleTask一样,不同的是系统不会添加任何其它的活动到该任务中来,该活动的实例是它的任务唯一的一个成员。任何被它启动的活动后悔被添加到一个新的任务中去。

例如:Android 浏览器应用申明web browser活动应该有它自己的任务,指定(singleTask),这就意味着如果你的应用需要使用android browser,浏览器的活动跟你的应用程序不在一个任务中。而是为browser重新开启一个任务或者将运行在后台的browser任务带到前台来处理新的intent.

无论新的活动是否启动在同一个任务中,按返回键都能返回到前一个活动。但是如果你从Task   A启动了一个指定为 singleTask的活动时,而该活动有一个实例运行在后台的另外一个任务Task B中时,当B 被带到前台来处理新的Intent时,返回键首先在Task B中导航知道根活动,然后再回到Task  A的栈顶。

Task  A : Activity 1---Activity 2

Task  B : Activity X---Activity YsingleTask

 

运行过程是: Activity 1-----Activity 2------ Activity Y

返回过程是:Activity Y---- Activity X----- Activity 2------ Activity 1

 

备注:使用launchMode指定的行为能够被Intentflag标志所替代。

 

Using Intent flags

通过startActivity()方法来传递一个带flagIntentn来改变活动的默认行为。可以用到的flag 如下:

FLAG_ACTIVITY_NEW_TASK

在新的任务中启动这个活动。如果一个有一个该活动的实例正在运行,则把这个实例带到前台,并且通过onNewIntent()方法来接收Intent

这个跟singleTask的行为相同

 

FLAG_ACTIVITY_SINGLE_TOP

如果被启动的活动就是当前活动,则不创建新的实例,而是该实例的onNewIntent()方法来处理intent

这个跟singleTop的行为相同

 

FLAG_ACTIVITY_CLEAR_TOP

如果被启动的活动已经在当前任务中,则不用重新创建该活动的实例,而是直接将位于该活动之上的活动全面销毁,然后将已经存在的实例带到栈顶显示出来。

 

FLAG_ACTIVITY_CLEAR_TOP经常跟FLAG_ACTIVITY_NEW_TASK一起使用,作用就是确定一个已经存在于其它任务的活动,并将其移动到可以响应intent的位置。

备注:如果目标活动的启动模式standard,则它也会从堆栈中移除然后创建该活动的一个新的实例来处理intent.因为在standard模式下,总是要创建一个新的实例的。

 

Handling affinities

Affnity 象征着一个活动更趋向于属于哪个任务。默认情况下,一个应用的所有活动彼此之间都有一个Affnity.所以默认情况下,一个原因的所有活动趋向于在同一个任务中。但是,我们能够改变这种默认的affnity.不同应用中的活动能共用一个affinity.同一个应用中的活动能指定不同的affnity.

改变方法:<activity>元素的 taskAffnity属性,它的值是一个不同包名的字符串。因为包名是系统用来识别默认affnity

Affnity在以下两种情况下起作用:

1.       启动活动的Intent 包含FLAG_ACTIVITY_NEW_TASK标志

默认情况下,新的活动启动在调用startActivity()的那个活动的任务中,并且在同一个堆栈中。然后,当传递给startActivity()intent包含FLAG_ACTIVITY_NEW_TASK时,系统会找一个不同的任务来装载这个新的活动。通常情况下会启动一个新的任务,但是,如果系统找到一个已经存在的任务,它的affinity跟要启动的活动相同时,这个活动就会被启动到这个已经存在的任务中,不会重新启动一个任务。

           如果这个flag启动了一个新的任务,并且用户按下了Home键离开它,用户一定可以导航回到这个任务。一些实体(例如通知管理器)经常启动一个活动在外部的任务中,绝不会在自己的任务中启动,所以它们经常把FLAT_ACTIVITY_NEW_TASK标志放到intent中去。如果你有一个活动能够被外部实体所请求,并且使用了这个flag标志,请当心用户同样有一个独立的方式能回到这个任务中,例如使用launch icon(任务的根activity有个CATEGORY Launcher intent filter)

2.       当活动的allowTaskReparenting  熟性设置为true

在这种情况下,这个活动能从启动它的那个任务中移动到跟它有相同affinity的任务中,当这个有相同的affnity任务来到前台的时候。

例如:有个旅行的应用,其中包含一个报告选择城市天气的活动,而且该活动跟该应用其它的活动affnity一样(默认的应用affnity,且它的allowTaskReparenting 熟性设置为true。此时一个其它的应用启动了这个报告天气的活动,所以他们都在TASK A中,但是当用户再启动旅游这个应用的时候(来到前台,Task B),这时候报告天气的活动就被分配到TASK  B中显示。

建议:如果从用户的角度来看,一个.apk文件包含了多个应用,这时候就应当使用taskAffnity属性来为每个不同的应用指定不同的affnity.

 

Clearing the back stack(清除后台堆栈)

如果用户离开了一个任务很长时间,系统就会清除掉这个任务中除了根活动以外的所有活动。当用户再次回到这个任务,只有根活动被恢复。系统会这么做是因为在长时间以后,用户可能放弃了之前的行为。

通过如下属性你能改变这种行为:

alwaysRetainTaskState

当根活动的这个属性设置为true的时候,默认的行为不会发生,即使在等待长时间以后,任务仍然保留所有的活动。

 

clearTaskOnLaunch

当根活动的这个属性设置为true的时候,只要用户离开这个任务,除了根活动之外,其它的活动都会被清除。

 

finishOnTaskLaunch

这个属性是针对单个的活动的,不是针对任务。它能够让包含根活动在内的活动都清除,只有在用户停留在这个任务的时候,活动才在任务中,一旦用户离开这个任务,它将不存在。

 

Starting  a  task

要让一个活动作为任务的根活动(entry point),要使用如下intent filter

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

         intent filter会导致这个活动的iconlable 会被显示在应用程序启动器中,用户可以通过这个ICON来启动这个活动并且在离开以后再次回到这个任务。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值