Tasks and Back Stack

来源:互联网 发布:东风3怎么运到沙特知乎 编辑:程序博客网 时间:2024/05/22 06:45

一般情况下,一个应用通常会包含很多个Activity。每一个Activity都应该指定一个action,用户可以通过该action启动它,也可以启动其他的Activity。例如一个邮件应用程序,可能会有一个Activity展示新消息的列表,当用户选择一条消息后,一个新的Activity启动并展示这条消息。

一个Activity也可以启动该设备上的其他应用的Activity,例如,你的应用想发送一个邮件,你可以定义一个intent,执行“send”action,并且添加对应的数据,例如邮件的地址以及邮件内容,其他应用中的Activity,如果定义了他自己可以处理这类的Intent,那么这个Activity就会启动,在这种情况下,这个intent想要发送一个邮件,因此一个邮件应用的组件Activity就会启动,(如果有对应Activity可以处理相同的intent,那么系统会让用户选择使用哪个程序进行处理),当邮件被发送出去后,你的Activity回到前台,此时发送邮件的Activity就像你应用的一部分一样,尽管这些Activity处于不同的应用中,android通过将这些Activity放在同一个task中,实现这种无缝的用户体验。

Task就是当用户执行一个动作所产生的与用户交互的所有的Activity的集合。这些Activity被按照打开的顺序被安排在一个栈中(回退栈)。

设备的桌面是大部分task启动的地方,当用户点击桌面的应用图标,点击的应用的task来到前台,如果没有该应用的task存在(这个应用最近没有被使用过),那么系统会创建一个新的task,并且启动应用的“main“Activity会被启动,并且成为栈底Activity。

当前的Activity启动另外一个Activity。被启动的Activity被压入这个task的顶部,并且获得焦点。前一个Activity仍然存在栈中,但是已经被stopped,当一个Activity stop,系统会保存当前的与用户交互的状态与数据,当用户点击了返回按键,当前的Activity从栈顶弹出(这个Activity被销毁),前一个Activity resume(之前的状态被恢复)。在栈中的Activity不会被重新排序,只能入栈或者出栈,当前Activity启动一个Activity时,新的Activity被压入栈,当用户点击返回离开当前Activity时,当前Activity被弹出栈,因此回退栈操作就是一种“后进先出“的结构。下图展示了不同的时间点上,Activity与回退栈之间的关系。



上图展示了一个新的Activity是如何被添加到栈中的,当用户点击返回键,当前的Activity被销毁,前一个Activity resume

如果用户一直按返回键,Activity会一个一个的弹出,直到回到桌面,当所有的Activity都从栈中移除,那么这个task就不存在了。

当用户点击home按键,或者启动一个新的task时,当前的task会作为一个整体单元一起移动到后台,当task处于后台时,在这个task中的所有的Activity都会被stop,但是这个回退栈还是完整的,这个task只是简单的失去了焦点,另外一个task获取了焦点。如下图所示



Figure 2. Two tasks: Task B receives userinteraction in the foreground, while Task A is in the background, waiting to beresumed.

Task可以重新回到前台,用户可以重新与之进行交互。例如当前的task(Task A)在它的栈中有三个Activity,,用户点击了home按键,然后启动了一个新的应用,系统会启动一个新的task(Task B)给这个应用,这个task有自己的栈和Activity,当用户操作一段时间后,用户再次回到桌面,点击了以前的应用启动了Task A,此时 Task A回到前台,在它栈中的所有的Activity都是完整的,在栈顶的Activity resume。此时,用户也可以选择重新回到Task B通过点击home按键,并且选择之前task B对应的应用。

在同一时间后台可以有很多个task,但是如果用户运行过多的task在后台,系统可能为了回收内存而销毁在后台的Activity,这样就会造成Activity的状态丢失。

由于在回退栈中的Activity一直不会被重新排序,如果你的应用允许用户从多个Activity启动一个指定的Activity,那么每次都会创建一个Activity新的实例(不会将以前创建的实例放到栈顶,而是重新创建),并且被放入栈顶,因此你的一个activity可能被初始化多次(也可能在不同的task中)如下图



Figure 3. A single activity is instantiatedmultiple times.

因此,如果用户使用返回键进行返回,那么每一个activity将会根据他打开的顺序进行弹出,然而如果不想activity被初始化多次,那么可以通过相应的方式修改默认的这种行为,

总结Activity和Task的默认行为

1,  当 Activity A start Activity B, Activity A被stop,但是系统会保存Activity A的状态,例如用户滑动的位置,用户输入的文本信息。如果用户在ActivityB中点击返回键,那么Activity A将会resume,并且恢复他刚才的状态

2,  当用户通过点击Home按键离开一个task,当前的Activity stop并且他的task进入后台。系统会保存该task中所有Activity的状态,如果用户通过点击启动图标重新开启这个task,这个task将会回到前台,并且栈顶的Activity resume

3,  如果用户点击返回按键当前的Activity将被弹出栈,并且销毁,在栈里前一个Activityresume,当一个Activity被销毁,系统不会保存该Activity 的状态

4,  Activity可以被初始化多次,可以在相同的task中,也可以在不同的task中

Activity 状态保存

上面已经讨论了,系统默认的行为是当Activitystop时保存Activity的状态,用这种方式,当用户返回到前一个Activity时,该Activity将显示用户刚才离开时的状态和数据,然而你可以并且应该通过Activity的回调方法提前保存Activity的状态,防止该Activity被销毁,或者重新创建

当系统stop了你的Activity(例如一个新的Activity产生或者改task被移到后台),如果系统需要回收内存,那么系统可能会直接销毁你的Activity,当这种情况产生式,该Activity的状态信息将被丢失,如果发生了这种情况,系统仍然知道在返回站中有一个该Activity的“空间“,当该Activity被移到栈顶时,系统就会重新创建它,而不是resume它,为了避免丢失用户的操作,你需要通过Activity中的onSaveInstanceState()回到方法保存你的Activity的状态。

Task管理

Android管理task和返回栈的方式如上所述,将所有启动的Activity放到同一个task中,然后采用后进先出的方式,该种方式适用大部分应用,你不用担心Activity如何与task交互,不用担心Activity在回退栈中的存在方式。如果想要打破这种默认的行为,可能希望应用中的一个Activity一被启动就放到一个新的task中,不是放在当前的task中,或者启动一个Activity时,你希望将前面使用过的实例放到栈顶进行重用,而不是产生一个新的实例,再将新的实例放到栈顶,或者你希望当用户离开这个task时,清除根Activity之外的所有其他Activity。

你可以通过在manifest文件中<Activity>标签的属性,或者你statrActivit()的intent的flag实现上述操作。

为了达到上述的目的,你可能需要使用到的<Activity>属性如下:

·      taskAffinity

·      launchMode

·      allowTaskReparenting

·      clearTaskOnLaunch

·      alwaysRetainTaskState

·      finishOnTaskLaunch

 

你可能使用到的Intent 的falg属性如下:

·      FLAG_ACTIVITY_NEW_TASK

·      FLAG_ACTIVITY_CLEAR_TOP

·      FLAG_ACTIVITY_SINGLE_TOP

在接下来的部分,将讨论如何通过manifest文件的属性以及Intent的flag,设置Activity与task关联方式,以及在回退栈中的行为。

同时,我们分开讨论task和Activity是如何在预览窗口中显示的,如果想了解更多详细信息,可以参考OverviewScreen,通常不应该改变系统默认处理task,以及Activity在多任务中的显示方式。大部分应用不应该打破系统处理task和Activity的默认方式,如果你的应用一定要改变这种默认操作,请做充分的测试,当Activity被启动,当使用返回按键或者task启动导致你的Activity退出或者挂后台的情况,请测试导航行为可能与用户行为存在的冲突。

启动模式解析

启动模式可以决定Activity实例与task的关系,可以通过两种方式定义启动模式

1,  使用mainifest文件

当你在manifest文件中定义一个Activity时,你可以指定当该Activity启动时与task的关系

2.使用Intent的flag属性

当你调用startActivity()时,你可以在你的Intent中设置一个flag属性,决定你要启动的这个Activity与当前task的关系

如果一个Activity A 启动Activity B,Activity B在manifest中定义了该Activity与当前task的关系,同时 Activity A 也可以指定Activity B与当前task的关系,如果两个Activity都指定了Activity B与当前task的关系,那么在Intent 中定义的方式将决定Activity B与当前task关系生效,也就是说Activity A设置的方式生效,Activity B 在manifest中设置的属性不生效。

需要注意的是,有些在manifest中设置的属性对应intent是不存在的,相反,intent的某些flag属性在manifest中也是不存在,也就是说manifest中的属性值并不是与Intent中的flag 一一对应的。

 

使用manifest文件

当在manifest文件中声明一个Activity时,你可以通过<.activity>的lanuchMode属性,指定当前Activity与task的关系。

launchMode属性指定了一个Activity如何被启动到一个task中,lanuchmode属性有四个可选的值

“standard“(默认模式)

默认模式,系统将从启动这个Activity的栈中创建一个新的实例,例如使用Activity启动,那么就会在当前Activity所在的task中创建一个新的Activity实例,使用Intent方式与此一致。

被启动的Activity可以被初始化多次,每一个实例可能属于不同的task,同一个task也可能存在该Activity的多个实例

“singleTop“

如果启动的Activity一个实例已经在当前栈的顶部,系统会调用栈顶的Activity实例的onNewIntent方法,对该Activity进行重用,并不会重新创建一个新的实例,该Activity可以被初始化多次,每一个实例可能属于不同的task,同一个task中也可能存在多个实例,只有当前站顶部的Activity实例与启动的Activity实例不同才会创建新的实例,而不考虑该栈中是否存在对应的实例,只看栈顶实例。

例如,有一个task中有四个Activity的实例, Activity A为栈底,B C D 依次,Activity D为栈顶Activity,顺序为A-B-C-D,一个Intent要启动ActivityD,如果Activity D设置的是默认的启动模式“standard“,那么一个新的实例会被创建,那么当前的task是这样 ABCDD。然而如果D的启动模式为”singleTop“,栈顶存在的D的实例将调用onNewIntent,由于D的实例在栈顶,所以不会创建新的实例,此时栈中的情况是:ABCD,如果一个Intent想要启动Activity B,那么一个新的实例将会被添加到task中,尽管B设置的启动模式是”singleTop“,因为栈顶的实例不是B的实例,所以会重新创建实例。

当新的实例被创建,用户可以通过返回按键回到前面的Activity,但是,当一个存在的实例处理一个新的Intent的时,也就是调用了onNewIntent的Activity实例,用户是不可以通过点击返回键回到前面的状态的,也就是在调用onNewIntent前,该实例的状态为a,该实例通过onNewIntent处理另外的一个Intent的请求,那么用户是不可以通过返回键让该Activity实例的状态再回到a状态的。

“singleTask“

系统 将新创建一个task,并且初始化这个Activity,将该Activity放到该新task的栈中,该Activity是栈底Activity(root),然而,如果要启动的Activity在另外的一个task中已经存在,系统会调用存在实例的onNewIntent方法,而不是创建一个新的实例,在同一时间一个Activity值存在一个实例。

尽管该Activity存在一个新的task中,用户点击返回仍然可以回到之前的Activity。

“singleInstance“

与“singleTask“类似,系统不会启动其他Activity到该栈中,当前的这个Activity是这个task中的唯一成员,而且只有这一个实例,被当前Activity启动的Activity都会被放到另外的一个task中

另外一个例子,android 的浏览器应用,定义包含网页浏览的Activity应该一直在它自己的task中打开,通过在<Activity>中指定lanuchMode 为singleTask。这就意味着,如果你的应用想要打开浏览器的浏览Activity,那么这个Activity其实不是和你的应用在一个task中的,而是在两个task中,如果没有存在的Activity实例,那么会新创建一个task,如果存在了一个对应的实例,那么会调用对应实例的onNewIntent方法,处理你的这个请求。

无论这个Activity是在新的task还是与启动它的Activity或者intent在同一个task中,当用户点击返回后,用户都会返回到前一个Activity,然而当用户指定了“singleTask“的启动模式,如果在后台的task中已经包含了该Activity的一个实例,那么整个task都会被移到前台,此时,回退栈中包含了刚才task中的所有的Activity。如下图所示



例如一个task中已经存在了一个Activity  Y,在对应的task中还存在Activity X的实例,如果用户启动Activity Y 指定了singleTask,那么整个task会被放到回退栈中,栈顶的Activity Y 用户点击返回会返回到Activity X,再点击才会返回到用户的Activity中。

使用Intent 的flag属性

当启动 一个Activity时,你可以通过修改Intent的flag值,来修改默认的Activity与task的关系,可以使用的改变整个默认行为的值如下:

FLAG_ACTIVITY_NEW_TASK

启动这个Activity在一个新的task中,如果一个正在运行的task已经包含了你要启动的这个Activity,那么这个task将会被移动到前台,要启动的Activity实例会调用onNewIntent方法,这个与manifest中的singleTask启动模式一样。

FLAG_ACTIVITY_SINGLE_TOP

如果要启动的Activity位于栈顶,那么存在的这个实例将会调用onNewIntent,而不会创建新的Activity实例。这个与manifest中设置singelTop 启动模式一样

FLAG_ACTIVITY_CLEAR_TOP

如果 要启动的Activity已经在当前的task中,那么不会创建新的实例,而是将该栈中,位于该Activity实例上方的所有Activity实例销毁,让改实例成为栈顶实例,同时调用该实例的onNewIntent方法

如果目标activity启动模式为standard,一般在同一个task中时,启动的实例都在同一个task中,如果应用A,应用B都启动了该Activity,其实A启动的Activity实例在a的task中,B启动的Activity在B的task中

处理Affinities

Affinity表示一个Activity更偏向于哪一个task,默认情况下,在一个应用中的所有的Activity具有一个affinity,因此,默认情况下,在一个应用中的所有的Activity都会在倾向于同一个task中,然后你可以修改Activity的这种默认行为,在不同应用中的Activity可以共享一个Affinity,或者在同一个应用中的Activity可以设置不同的task Affinity

你可以通过在manifest中的<Activity>标签中使用taskAffinity属性来修改指定Activity的Affinity

taskAffinity属性值是一个字符串,该字符串必须是唯一的,默认情况下会使用manifest中声明的package属性的值,因为系统会使用这个名称确定这个应用的taskAffinity

在两种情况下Affinity属性会起作用

1,  当一个intent启动一个Activity时,带有FLAG_ACTIVITY_NEW_TASK flag。

新的Activity默认情况下,会放进启动它的Activity相同的栈中,也会被放到调用它的相同的后退站中,然而如果调用startActivity的Intent设置flag为FLAG_ACTIVITY_NEW_TASK,系统会寻找一个不同的task来存放这个activity实例,通常会是一个新的task。然而也不一定,如果已经有一个存在的task设置了相同的Affinity属性值与当前的Activity实例,这个Activity会放到对应的这个task中,如果没有相同的Affinity属性的task,那么才会创建一个新的task。

2,    当一个Activity设置了allowTaskReparenting为true

在这种情况下,Activity可以从启动它的task中移动到与他有相同Affinity值的task中,当设置了这个Affinity属性的task回到前台。

例如,假设在一个旅游应用中,定义了一个Activity,用来显示指定城市的天气情况,它与位于该应用中的所有其他Activity具有相同的Affinity,默认的应用Affinity。你的一个应用的Activity启动对应的天气情况的Activity,天气情况的Activity会和你的Activity在同一个task中,然后,当这个旅游应用的task回到前台,那么这个天气情况的Activity会回到旅游应用的那个task并且在那个task中进行显示。

清空回退栈

当用户离开一个task较长时间,系统会清除这个task中除根Activity之外的所有的Activity。当用户再次回到这个task,只有这个根Activity被恢复,系统采用这样的方式,因为如果过了较长时间,用户可能已经放弃了他之前的操作而执行其他的事情。

有一些Activity的属性可以修改系统的这种默认行为

alwaysRetainTaskState

如果在这个栈底的Activity这个属性设置为true,系统默认的行为将不会产生,尽管过了很长的时间,那么task中所有的Activity的状态都会被保存

clearTaskOnLaunch

如果在这个栈底的Activity的这个属性设置为true,无论用户离开还是重新回答这个task,这个task都会清空到根Activity,换句话说他是与alwaysRetainTaskState相反的。用户一直返回到这个task的初始化状态,即使离开了很短的时间也会恢复到初始化状态。

finishOnTaskLaunch

这个属性与clearTaskOnLaunch很像,但是这个属性作用在单一的activity上,不是整个task,他可以引起所有的Activity消失,也包括根Activity,当这个属性设置为true,Activity只在当前会话中保存,如果用户离开或者重新回到这个task时,Activity就不存在了。

0 0