Android任务和返回栈彻底解析,细数那些你所不知道的细节(最详细的解说了)

本篇文章主要内容来自于Android Doc,我翻译以后又作了些加工,英文好的朋友也能够直接去读原文。
html

http://developer.android.com/guide/components/tasks-and-back-stack.htmlandroid

任务和返回栈

一个应用程序当中一般都会包含不少个Activity,每一个Activity都应该设计成为一个具备特定的功能,而且可让用户进行操做的组件。另外,Activity之间还应该是能够相互启动的。好比,一个邮件应用中可能会包含一个用于展现邮件列表的Activity,而当用户点击了其中某一封邮件的时候,就会打开另一个Activity来显示该封邮件的具体内容。浏览器

除此以外,一个Activity甚至还能够去启动其它应用程序当中的Activity。打个比方,若是你的应用但愿去发送一封邮件,你就能够定义一个具备"send"动做的Intent,而且传入一些数据,如对方邮箱地址、邮件内容等。这样,若是另一个应用程序中的某个Activity声明本身是能够响应这种Intent的,那么这个Activity就会被打开。在当前场景下,这个Intent是为了要发送邮件的,因此说邮件应用程序当中的编写邮件Activity就应该被打开。当邮件发送出去以后,仍然仍是会回到你的应用程序当中,这让用户看起来好像刚才那个编写邮件的Activity就是你的应用程序当中的一部分。因此说,即便有不少个Activity分别都是来自于不一样应用程序的,Android系统仍然能够将它们无缝地结合到一块儿,之因此能实现这一点,就是由于这些Activity都是存在于一个相同的任务(Task)当中的。数据结构

任务是一个Activity的集合,它使用栈的方式来管理其中的Activity,这个栈又被称为返回栈(back stack),栈中Activity的顺序就是按照它们被打开的顺序依次存放的。ide

手机的Home界面是大多数任务开始的地方,当用户在Home界面上点击了一个应用的图标时,这个应用的任务就会被转移到前台。若是这个应用目前并无任何一个任务的话(说明这个应用最近没有被启动过),系统就会去建立一个新的任务,而且将该应用的主Activity放入到返回栈当中。测试

当一个Activity启动了另一个Activity的时候,新的Activity就会被放置到返回栈的栈顶并将得到焦点。前一个Activity仍然保留在返回栈当中,但会处于中止状态。当用户按下Back键的时候,栈中最顶端的Activity会被移除掉,而后前一个Activity则会得从新回到最顶端的位置。返回栈中的Activity的顺序永远都不会发生改变,咱们只能向栈顶添加Activity,或者将栈顶的Activity移除掉。所以,返回栈是一个典型的后进先出(last in, first out)的数据结构。下图经过时间线的方式很是清晰地向咱们展现了多个Activity在返回栈当中的状态变化:ui


若是用户一直地按Back键,这样返回栈中的Activity会一个个地被移除,直到最终返回到主屏幕。当返回栈中全部的Activity都被移除掉的时候,对应的任务也就不存在了。spa

任务除了能够被转移到前台以外,固然也是能够被转移到后台的。当用户开启了一个新的任务,或者点击Home键回到主屏幕的时候,以前任务就会被转移到后台了。当任务处于后台状态的时候,返回栈中全部的Activity都会进入中止状态,但这些Activity在栈中的顺序都会原封不动地保留着,以下图所示:.net


这个时候,用户还能够将任意后台的任务切换到前台,这样用户应该就会看到以前离开这个任务时处于最顶端的那个Activity。举个例子来讲,当前任务A的栈中有三个Activity,如今用户按下Home键,而后点击桌面上的图标启动了另一个应用程序。当系统回到桌面的时候,其实任务A就已经进入后台了,而后当另一个应用程序启动的时候,系统会为这个程序开启一个新的任务(任务B)。当用户使用完这个程序以后,再次按下Home键回到桌面,这个时候任务B也进入了后台。而后用户又从新打开了第一次使用的程序,这个时候任务A又会回到前台,A任务栈中的三个Activity仍然会保留着刚才的顺序,最顶端的Activity将从新变为运行状态。以后用户仍然能够经过Home键或者多任务键来切换回任务B,或者启动更多的任务,这就是Android中多任务切换的例子。翻译

因为返回栈中的Activity的顺序永远都不会发生改变,因此若是你的应用程序中容许有多个入口均可以启动同一个Activity,那么每次启动的时候就都会建立该Activity的一个新的实例,而不是将下面的Activity的移动到栈顶。这样的话就容易致使一个问题的产生,即同一个Activity有可能会被实例化不少次,以下图所示:


可是呢,若是你不但愿同一个Activity能够被屡次实例化,那固然也是能够的,立刻咱们就将开始讨论若是实现这一功能,如今咱们先把默认的任务和Activity的行为简单归纳一下:

  • 当Activity A启动Activity B时,Activity A进入中止状态,但系统仍然会将它的全部相关信息保留,好比滚动的位置,还有文本框输入的内容等。若是用户在Activity B中按下Back键,那么Activity A将会从新回到运行状态。

  • 当用户经过Home键离开一个任务时,该任务会进入后台,而且返回栈中全部的Activity都会进入中止状态。系统会将这些Activity的状态进行保留,这样当用户下一次从新打开这个应用程序时,就能够将后台任务直接提取到前台,并将以前最顶端的Activity进行恢复。

  • 当用户按下Back键时,当前最顶端的Activity会被从返回栈中移除掉,移除掉的Activity将被销毁,而后前面一个Activity将处于栈顶位置并进入活动状态。当一个Activity被销毁了以后,系统不会再为它保留任何的状态信息。

  • 每一个Activity均可以被实例化不少次,即便是在不一样的任务当中。

管理任务

Android系统管理任务和返回栈的方式,正如上面所描述的同样,就是把全部启动的Activity都放入到一个相同的任务当中,经过一个“后进先出”的栈来进行管理的。这种方式在绝大多数状况下都是没问题的,开发者也无须去关心任务中的Activity究竟是怎么样存放在返回栈当中的。可是呢,若是你想打破这种默认的行为,好比说当启动一个新的Activity时,你但愿它能够存在于一个独立的任务当中,而不是现有的任务当中。或者说,当启动一个Activity时,若是这个Activity已经存在于返回栈中了,你但愿能把这个Activity直接移动到栈顶,而不是再建立一个它的实例。再或者,你但愿能够将返回栈中除了最底层的那个Activity以外的其它全部Activity所有清除掉。这些功能甚至更多功能,都是能够经过在manifest文件中设置<activity>元素的属性,或者是在启动Activity时配置Intent的flag来实现的。

在<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在任务中的默认行为。

定义启动模式

启动模式容许你去定义如何将一个Activity的实例和当前的任务进行关联,你能够经过如下两种不一样的方式来定义启动模式:

1.使用manifest文件

当你在manifest文件中声明一个Activity的时候,你能够指定这个Activity在启动的时候该如何与任务进行关联。

2.使用Intent flag

当你调用startActivity()方法时,你能够在Intent中加入一个flag,从而指定新启动的Activity该如何与当前任务进行关联。

也就是说,若是Activity A启动了Activity B,Activity B能够定义本身该如何与当前任务进行关联,而Activity A也能够要求Activity B该如何与当前任务进行关联。若是Activity B在manifest中已经定义了该如何与任务进行关联,而Activity A同时也在Intent中要求了Activity B该怎么样与当前任务进行关联,那么此时Intent中的定义将覆盖manifest中的定义。

须要注意的是,有些启动模式在manifest中能够指定,但在Intent中是指定不了的。一样,也有些启动模式在Intent中能够指定,但在manifest中是指定不了的,下面咱们就来具体讨论一下。

使用manifest文件

当在manifest文件中定义Activity的时候,你能够经过<activity>元素的launchMode属性来指定这个Activity应该如何与任务进行关联。launchMode属性一共有如下四种可选参数:

"standard"(默认启动模式)

standard是默认的启动模式,即若是不指定launchMode属性,则自动就会使用这种启动模式。这种启动模式表示每次启动该Activity时系统都会为建立一个新的实例,而且总会把它放入到当前的任务当中。声明成这种启动模式的Activity能够被实例化屡次,一个任务当中也能够包含多个这种Activity的实例。

"singleTop"

这种启动模式表示,若是要启动的这个Activity在当前任务中已经存在了,而且还处于栈顶的位置,那么系统就不会再去建立一个该Activity的实例,而是调用栈顶Activity的onNewIntent()方法。声明成这种启动模式的Activity也能够被实例化屡次,一个任务当中也能够包含多个这种Activity的实例。

举个例子来说,一个任务的返回栈中有A、B、C、D四个Activity,其中A在最底端,D在最顶端。这个时候若是咱们要求再启动一次D,而且D的启动模式是"standard",那么系统就会再建立一个D的实例放入到返回栈中,此时栈内元素为:A-B-C-D-D。而若是D的启动模式是"singleTop"的话,因为D已是在栈顶了,那么系统就不会再建立一个D的实例,而是直接调用D Activity的onNewIntent()方法,此时栈内元素仍然为:A-B-C-D。

"singleTask"

这种启动模式表示,系统会建立一个新的任务,并将启动的Activity放入这个新任务的栈底位置。可是,若是现有任务当中已经存在一个该Activity的实例了,那么系统就不会再建立一次它的实例,而是会直接调用它的onNewIntent()方法。声明成这种启动模式的Activity,在同一个任务当中只会存在一个实例。注意这里咱们所说的启动Activity,都指的是启动其它应用程序中的Activity,由于"singleTask"模式在默认状况下只有启动其它程序的Activity才会建立一个新的任务,启动本身程序中的Activity仍是会使用相同的任务,具体缘由会在下面 处理affinity 部分进行解释。

"singleInstance"

这种启动模式和"singleTask"有点类似,只不过系统不会向声明成"singleInstance"的Activity所在的任务当中再添加其它Activity。也就是说,这种Activity所在的任务中始终只会有一个Activity,经过这个Activity再打开的其它Activity也会被放入到别的任务当中。

再举一个例子,Android系统内置的浏览器程序声明本身浏览网页的Activity始终应该在一个独立的任务当中打开,也就是经过在<activity>元素中设置"singleTask"启动模式来实现的。这意味着,当你的程序准备去打开Android内置浏览器的时候,新打开的Activity并不会放入到你当前的任务中,而是会启动一个新的任务。而若是浏览器程序在后台已经存在一个任务了,则会把这个任务切换到前台。

其实无论是Activity在一个新任务当中启动,仍是在当前任务中启动,返回键永远都会把咱们带回到以前的一个Activity中的。可是有一种状况是比较特殊的,就是若是Activity指定了启动模式是"singleTask",而且启动的是另一个应用程序中的Activity,这个时候当发现该Activity正好处于一个后台任务当中的话,就会直接将这整个后台任务一块儿切换到前台。此时按下返回键会优先将目前最前台的任务(刚刚从后台切换到最前台)进行回退,下图比较形象地展现了这种状况:


更多关于如何在manifest文件中使用启动模式的讲解,能够去参考《第一行代码——Android》第二章部分的内容。

使用Intent flags

除了使用manifest文件以外,你也能够在调用startActivity()方法的时候,为Intent加入一个flag来改变Activity与任务的关联方式,下面咱们来一一讲解一下每种flag的做用:

FLAG_ACTIVITY_NEW_TASK

设置了这个flag,新启动Activity就会被放置到一个新的任务当中(与"singleTask"有点相似,但不彻底同样),固然这里讨论的仍然仍是启动其它程序中的Activity。这个flag的做用一般是模拟一种Launcher的行为,即列出一推能够启动的东西,但启动的每个Activity都是在运行在本身独立的任务当中的。

FLAG_ACTIVITY_SINGLE_TOP

设置了这个flag,若是要启动的Activity在当前任务中已经存在了,而且还处于栈顶的位置,那么就不会再次建立这个Activity的实例,而是直接调用它的onNewIntent()方法。这种flag和在launchMode中指定"singleTop"模式所实现的效果是同样的。

FLAG_ACTIVITY_CLEAR_TOP

设置了这个flag,若是要启动的Activity在当前任务中已经存在了,就不会再次建立这个Activity的实例,而是会把这个Activity之上的全部Activity所有关闭掉。好比说,一个任务当中有A、B、C、D四个Activity,而后D调用了startActivity()方法来启动B,并将flag指定成FLAG_ACTIVITY_CLEAR_TOP,那么此时C和D就会被关闭掉,如今返回栈中就只剩下A和B了。

那么此时Activity B会接收到这个启动它的Intent,你能够决定是让Activity B调用onNewIntent()方法(不会建立新的实例),仍是将Activity B销毁掉并从新建立实例。若是Activity B没有在manifest中指定任何启动模式(也就是"standard"模式),而且Intent中也没有加入一个FLAG_ACTIVITY_SINGLE_TOP flag,那么此时Activity B就会销毁掉,而后从新建立实例。而若是Activity B在manifest中指定了任何一种启动模式,或者是在Intent中加入了一个FLAG_ACTIVITY_SINGLE_TOP flag,那么就会调用Activity B的onNewIntent()方法。

FLAG_ACTIVITY_CLEAR_TOP和FLAG_ACTIVITY_NEW_TASK结合在一块儿使用也会有比较好的效果,好比能够将一个后台运行的任务切换到前台,并把目标Activity之上的其它Activity所有关闭掉。这个功能在某些状况下很是有用,好比说从通知栏启动Activity的时候。

处理affinity

affinity能够用于指定一个Activity更加愿意依附于哪个任务,在默认状况下,同一个应用程序中的全部Activity都具备相同的affinity,因此,这些Activity都更加倾向于运行在相同的任务当中。固然了,你也能够去改变每一个Activity的affinity值,经过<activity>元素的taskAffinity属性就能够实现了。

taskAffinity属性接收一个字符串参数,你能够指定成任意的值(经我测试字符串中至少要包含一个.),但必须不能和应用程序的包名相同,由于系统会使用包名来做为默认的affinity值。

affinity主要有如下两种应用场景:

  • 当调用startActivity()方法来启动一个Activity时,默认是将它放入到当前的任务当中。可是,若是在Intent中加入了一个FLAG_ACTIVITY_NEW_TASK flag的话(或者该Activity在manifest文件中声明的启动模式是"singleTask"),系统就会尝试为这个Activity单首创建一个任务。可是规则并非只有这么简单,系统会去检测要启动的这个Activity的affinity和当前任务的affinity是否相同,若是相同的话就会把它放入到现有任务当中,若是不一样则会去建立一个新的任务。而同一个程序中全部Activity的affinity默认都是相同的,这也是前面为何说,同一个应用程序中即便声明成"singleTask",也不会为这个Activity再去建立一个新的任务了。

  • 当把Activity的allowTaskReparenting属性设置成true时,Activity就拥有了一个转移所在任务的能力。具体点来讲,就是一个Activity如今是处于某个任务当中的,可是它与另一个任务具备相同的affinity值,那么当另外这个任务切换到前台的时候,该Activity就能够转移到如今的这个任务当中。
    那仍是举一个形象点的例子吧,好比有一个天气预报程序,它有一个Activity是专门用于显示天气信息的,这个Activity和该天气预报程序的全部其它Activity具体相同的affinity值,而且还将allowTaskReparenting属性设置成true了。这个时候,你本身的应用程序经过Intent去启动了这个用于显示天气信息的Activity,那么此时这个Activity应该是和你的应用程序是在同一个任务当中的。可是当把天气预报程序切换到前台的时候,这个Activity又会被转移到天气预报程序的任务当中,并显示出来,由于它们拥有相同的affinity值,而且将allowTaskReparenting属性设置成了true。

清空返回栈

如何用户将任务切换到后台以后过了很长一段时间,系统会将这个任务中除了最底层的那个Activity以外的其它全部Activity所有清除掉。当用户从新回到这个任务的时候,最底层的那个Activity将获得恢复。这个是系统默认的行为,由于既然过了这么长的一段时间,用户颇有可能早就忘记了当时正在作什么,那么从新回到这个任务的时候,基本上应该是要去作点新的事情了。

固然,既然说是默认的行为,那就说明咱们确定是有办法来改变的,在<activity>元素中设置如下几种属性就能够改变系统这一默认行为:

alwaysRetainTaskState

若是将最底层的那个Activity的这个属性设置为true,那么上面所描述的默认行为就将不会发生,任务中全部的Activity即便过了很长一段时间以后仍然会被继续保留。

clearTaskOnLaunch

若是将最底层的那个Activity的这个属性设置为true,那么只要用户离开了当前任务,再次返回的时候就会将最底层Activity之上的全部其它Activity所有清除掉。简单来说,就是一种和alwaysRetainTaskState彻底相反的工做模式,它保证每次返回任务的时候都会是一种初始化状态,即便用户仅仅离开了很短的一段时间。

finishOnTaskLaunch

这个属性和clearTaskOnLaunch是比较相似的,不过它不是做用于整个任务上的,而是做用于单个Activity上。若是某个Activity将这个属性设置成true,那么用户一旦离开了当前任务,再次返回时这个Activity就会被清除掉。

相关文章
相关标签/搜索