以下是讲解activity的任务和返回栈,从android开发文档中翻译而来。
一个app通常都包含多个activities,每个activity 的设计都是基于用户可以执行特定行为,用户也可以开启其他activities。比如一个email app 可能有一个展示新邮件的activity ,当用户选择一封邮件的时候,就开启了一个新的activity 来展示该邮件。
一个activity甚至可以开启在其他app上的activities 。比如:你可以定义一个intent 来执行“send”动作,intent中可以包含data,如邮件地址,内容。其他app上的activity 申明了自己能够处理这种itent的就会被开启。在这种情况下,intent 就是发送邮件,emial app 中的activity 开启。(如果有多个activities 能匹配同一个intent的话,那么这个系统就会让用户选择其中一个)。当邮件被发送时,你的activity 获取了焦点,并且看上去email activity 是你app的一部分。尽管activities 可能来自于不同的app,但是Android 通过把这些activities 放在同一个栈中,保证了用户的无缝体验。
一个任务就是当用户在执行某些操作时,需要和一系列activities 交互,将这些activities 放置在一个集合中,就形成了栈。activities 是在返回栈中被管理,每个activity 都是按照顺序被打开的。
设备的 Home screen(主界面) 就是大多数任务开启的地方。当用户触摸app启动项的图标,这个app的任务就跑到前台来了。如果这个app没有任务(这个app最近没有被使用),那么一个新的任务就会创建出来,main activity 会作为任务中的root activity 被开启。
当前activity A 开启了另外一个activity B,新开启的activity B就会被推到栈顶获取焦点。之前的activity A 仍然在栈中,但是是stop状态。当一个activity stop后,系统保留与用户交互的当前状态。当用户按了后退键,当前activity 就会从栈顶弹出(activity 被销毁),之前的activity 重新获取焦点(之前的UI状态会被恢复)。在栈中的activities 不会被重新排列,只有进栈或是弹栈—–有当前activity 开启的时候,会进栈,当用户按下后退键就会弹栈。所以返回栈就是按照“后进先出”(last in, first out)的原则来执行。图一展示了在每个时间点上,activities 和返回栈的进程。展示了每个在任务中的新的activity 是怎样将activity 添加到任务栈中的。当用户按下后退键,当前activity 被销毁,之前的activity 恢复获取焦点。
如果用户继续按后退键,那么每个存在栈中的activity 都会被依次弹出,直至用户返回到主界面(或是任务开启的时候,任何一个正在运行的activity )。当所有的activities 都被从返回栈中移除,栈就不再存在。
任务是一个内聚单元,当用户开启一个新的任务或是通过Home button 返回到主界面的时候,之前的任务转移到“后台”。在后台,所有的activities 都是stop状态,但是任务的返回栈仍然保持不变—这个任务仅仅是当其他任务开启的时候它失去了焦点,如下图所示。一个任务也可以返回到前台,因此用户可以接着之前的继续交互。比如,当前Task A 在它的栈中有3个activities —-2个在当前activity的后面。用户按下Home button,就从app launcher中启动一个新的app.当出现主界面,Task A运行在后台了。当新的app开启时,系统会为app 开启一个新的Task B, 装载自己的activities。当交互之后,用户又返回到主界面,并且又选择了当初开启Task A的app。现在,Task A来到前台—栈中的3个activities 还是完整不变的,并且在栈顶的activity 获取焦点。这时候,用户可以通过返回到主界面,选择app图标开启这个任务来跳转到Task B .
许多tasks 能够立刻被安排到后台。然而,如果用户在同一时间在后台运行许多tasks 的话,系统为了回收内存,可能会销毁一些后台的activities ,这会导致activity 状态丢失。
由于在返回栈的activities 不会被重新排列,如果app允许用户从众多的activity中开启一个特定的activity,一个新的实例就会被创建并且进栈(而不是把之前的activity 实例拿来放在栈顶)。因此,你的app中的一个activity 有可能被实例化许多次,正如图3所展示的。在这种情况下,如果用户用按后退键一步一步返回,每个activity 实例会按照顺序被依次打开。然而,如果你不希望activity 被实例化许多次,你也可以做修改。以下会讲解如何管理Tasks。
以下总结的是activities 和tasks默认的行为方式:
- 当Activity A 启动Activity B,Activity A 就会stop,但是仍会保持它的状态(比如滚动的位置,输入表格中的文字)。如果用户在Activity B时按下了后退键,Activity A 就会带着之前的状态恢复过来。
- - 当用户按下Home button,就会离开这个task 。当前的activity stop的时候,task 就会在后台运行。系统会保持每个task中activity 的状态。如果用户稍后通过点击app启动图标来恢复task ,task 就会运行在前台,恢复在栈顶的activity 。
- - 如果用户按下Back button,当前activity 弹栈并被销毁。之前在栈中的activity 就会恢复。当activity 被销毁,系统就不会保留activity 的状态。
- - Activities 可以被实例化许多次,甚至在不同的tasks中也可以。
管理任务:Android 管理tasks 和back stack的方式是:把许多已启动的activities 以一定的顺序放在同一个task 中,并遵循”last in, first out”.则对于大多数app来说是行之有效的,你没有必要担忧activities 是怎样与tasks 关联,或是怎样在back stack 中存在。然而,你有可能希望app上的一个activity 在开启的时候,就开启一个新的task (而不是运行在当前的task);或者当你开启一个activity时,你希望把已经存在的activity实例拿来而不是创造一个新的实例放在back stack的栈顶;或是当你用遗留下的task,你希望清空back stack 中除了root activity 的所有activities 。通过manifest activity的属性、传递带有flags 的Intent来startActivity(),你还可以做的更多。
注意:大多数的app 不应该干扰activities 和tasks默认的行为。如果你认为有必要修改activity 的默认行为,确保在启动的时候测试activity 的可用性。当用back button 导航一步一步返回到此activity ,或是其他activities返回到此activity 时候,也测试它的可用性。也对和用户期望的习惯相冲突的导航行为做测试。
定义启动模式:启动模式允许用户定义一个新的activity 实例是怎样和当前task相关联。你可以用以下2种方式来定义:
- 用manifest 配置
- 用Intent flags
如此,如果Activity A 开启Activity B,Activity B可以在它的manifest 申明自己的启动模式。Activity A同样也可以要求Activity B应该用怎样的启动模式。如果Activity A 和Activity B 都定义了Activity B的启动模式,那么 Activity A的优先权高于Activity B
注意:一些启动模式有可能对manifest 文件有效,对带有flags 的意图没效。同样,一些启动模式有可能对不能在manifest中申明的、带有flags 的意图有效。
使用manifest 文件:启动模式定义了一个activity 应该怎样在任务中被启动。启动模式有4种:
- ”standard“(默认的启动模式)。当一个activity 开启的时候,系统就会在task 中创建一个新的实例。这个activity 能够被实例化许多次,每个实例都属于不同的tasks,一个task 可以有许多的实例。
- ”singleTop“:如果一个activity的实例已经在当前task顶部,系统将通过调用onNewIntent() 方法来将意图传递给这个activity实例,而不是通过创建一个新的activity实例。这个activity也可以被实例化许多次,每个实例都可以属于不同的tasks,一个task 可以有许多的实例(但是仅仅是这个activity 在当前back stack 的顶部,而不是已经就存在的activity)例如,假定一个任务的back stack 包括root activity A ,带有activities B, C,D. D在顶部。即栈中排列顺序是:A-B-C-D。一个intent 传递给activity D。如果D是默认的”standard“启动模式,一个新的实例就会被创建出来,stack 中的排列就变为: A-B-C-D-D。然而,如果D的模式是singleTop,已经存在的实例activity D通过onNewIntent()收到intent 。因为D在栈的顶部,所以栈中的顺序还是:A-B-C-D。如果有个intent 传递给B,那么B的一个新的实例就会被增加到栈中。注意:当一个activity 新的实例被创建的时候,用户可以按 Back button 返回到之前的
activity,但是当由已经存在的activity 实例来处理新的intent时,在新的intent 没有通过onNewIntent()传递过来的时候,用户不能按Back button返回到之前的activity 状态。 - ”singleTask“:系统创建一个新的task 并在新的task根部实例化activity 。然而,如果一个activity 的实例已经在另外一个独立的task中存在,系统就会把通过调用onNewIntent()方法将intent 传递给已经存在的activity 实例,而不是创建一个新的实例。一次仅仅只有一个activity 的实例能存在。注意:尽管activity 是在新的task中开启的,Back button 仍然能让用户返回到之前的activity。
- ”singleInstance“:和singleTask一样,除了系统不会在已经有了activities 实例的task中启动任何的activities 了。这个activity 经常是单一的,并起task中只有它一个成员。任何是这种启动模式的activities 能够在一个独立的task中开启。
另外一个例子就是:Android 浏览器申明了网页浏览的activity 应该经常在自己的task中开启—-在mainfest中定义了singleTask 的启动模式。这意味着如果你的app发送了一个打开Android 浏览器的intent ,那么网页浏览的activity 不会和你的app在同一个task中。相反,要么会为浏览器开启一个新的task ,要么如果Browser 已经有一个任务在后台运行,那么这个task 就会被带到前台来处理新的intent。
不管一个activity 是在新的task 中开启或是被在相同的task 中的activity 开启,Back button经常能够使得用户返回到之前的activity。然而,如果你特别指定了一个activity 是singleTask 启动模式,如果在后台的task中已经存在了这个activity 实例,那么整个task 就会被带到前台。这时,back stack包括了task 带来的所有activities ,并在栈的顶部。用图4说明这种情形:
注意:你在manifest中设置activity 的启动模式有可能会被intent 中的flags 重写。
使用Intent flags:当开启一个activity,在用intent来startActivity()时候,可以在这个intent 中增加flags 来修改activity的默认启动模式。你可以用来修改activity默认启动模式的flags有:
FLAG_ACTIVITY_NEW_TASK: 在新的task中开启activity.如果你要开的这个activity的task已经正在运行,那么这个task 就会被带到前台,并恢复之前的状态。Activity在onNewIntent()接收新的intent 。这和之前讨论的singleTask 模式相同。
FLAG_ACTIVITY_SINGLE_TOP:如果当前的activity 已经开启了(在back stack的顶部),那么已经存在的实例就会调用onNewIntent(),而不是创建这个activity的新的实例。这和singleTop模式相同。
FLAG_ACTIVITY_CLEAR_TOP:经常性和FLAG_ACTIVITY_NEW_TASK结合使用。当一起使用时,flags在另外的task 中定位已经存在的activity 并将它放在能够响应intent的地方。
注意:如果一个activity 的启动模式被指定为standard。这个activity 也会从stack 中移除,一个新的实例就会替代它处理即将到来的intent。这是由于当启动模式是standard时,会为新的intent 创建新的实例。
清理 back stack:如果用户离开任务有很长一段时间了,那么系统就会清理这个task 中所有的activities ,除了root activity。当用户又返回到task 时,仅仅只能恢复root activity。系统之所以这样做是由于当过了很长一段时间后,用户极有可能已经放弃了他们之前所做的,重新开始做一些新的事情。你可以用以下属性来修改上述行为:
alwaysRetainTaskState:如果将task中root activity的这个属性值设置为真,默认的模式就不会发生了。在很长一段时间后,这个task还是会保留里面的所有activities 。
clearTaskOnLaunch:如果将task中root activity的这个属性值设置为真,无论何时用户离开和返回task,都会为root activity清理stack 。换言之,就是和alwaysRetainTaskState相反。用户经常会返回到activity的初始状态,尽管他只离开了一小会儿。
finishOnTaskLaunch:这个属性和clearTaskOnLaunch很像,但是它只作用于单一的activity上,不在整个task中。它也可以引起任何的activity 包括 root activity的销毁。当属性值设置为真时,只有在当前的会话中,activity 还是task 的一部分。如果用户离开再返回,就不再是当前的了。
开启 task:
你可以给一个activity设置 intent filter action :”android.intent.action.MAIN”,category:android.intent.category.LAUNCHER作为开启一个task 的入口。例如:
这种intent filter 会使得这个activity 的icon 和label 在app被启动的时候展示出来,这给了用户一个方法来启动这个activity,返回到这个task 。
第二个能力非常重要:用户必须能够离开一个task 并使用了launcher后,能返回到这个task。因此,这里有2种模式能够使activities 经常在一个task中初始化。当activity 有ACTION_MAIN 和CATEGORY_LAUNCHER 的intent filter时,应该使用singleTask 和singleInstance 。想象一下,如果这个intent filter 消失不见了:一个intent 开启了singleTask activity,初始化一个新的task,并且用户花了一些时间工作在task上。用户然后按下Home button,那么这个task 被转到后台并不可见。现在用户却没有办法返回到这个task了。因为它没有这个launcher。所以对于上述,如果你不希望用户能够返回到activity,那么就把finishOnTaskLaunch属性值设为true.