1.6、Activity任务和返回堆栈3(Tasks and Back Stack)之LaunchMode

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/LonelyRoamer/article/details/9839583

LaunchMode直译为启动模式,很容易理解,即我们Activity的启动模式。

LaunchMode允许我们定义一个新的Activity实例是如何和当前的Task相关联的。我们可以通过两种方式定义LaunchMode:

  • 使用manifest文件

            当你在manifest文件中声明的时候,你可以指定这个Activity在启动的时候如何与Task相关联。

  • 使用Intent flags

           当你调用startActivity()时候,你可以通过对Intent设置一个flag,来声明这个新的Activity如何(或者是否)与当前Task相关联。

举个例子:如果Activity A启动Activity B,Activity B可以在自己的manifest文件中定义自己如何和当前Task结合起来。而Activity A同样可以请求Activity B如何和当前Task结合。如果同时使用了这两种方式,那么Activity A的请求将覆盖Activity B自身在manifest中的声明。
注意:一些LaunchMode可以再manifest中使用,而不能再Intent flag中使用。同样的,一些LaunchMode在Intent flag中可以使用,但是在manifest中不能使用。

LaunchMode与Task和Back stack密切相关。在多个Activity跳转的过程中扮演着重要的角色,它可以决定是否生成新的Activity实例,是否重用已存在的Activity实例,是否和其他Activity实例公用一个Task里,等等。


manifest中四种启动模式的简单介绍

下面的介绍基本上都来自官方文档,不过简化了下,只是先简单的了解下这四种启动模式

1、standard

默认的LaunchMode,也是最容易理解的。如果某个Activity使用该LaunchMode,当这个Activity启动时,系统会创建一个该Activity的新的实例,并且传递一个intent给它。该Activity可以被实例化多次,各个实例可以属于不同的Task,一个Task中也可以存在多个实例。

2、singleTop

如果这个Activity有一个实例已经存在于当前Task的顶部,那么系统就会传递一个intent给这个实例的onNewIntent()方法,而不会去创建一个新的Activity实例。这个Activity可以被实例化多次,每个实例可以属于不同的task,一个task中也可以存在多个实例(但只有当Back Stack栈顶的Activity实例不是该Activity 的实例时)。
注意: 一个Activity的新实例创建完毕后,用户可以按返回键返回前一个activity。但是当Activity 已有实例正在处理刚到达的intent时,用户无法用返回键回到onNewIntent()中intent 到来之前的Activity 状态。

3、singleTask

系统创建一个新的Task,并且实例化这个Activity作为这个Task的根Activity。然而,如果这个Activity的已经存在了一个实例在一个Task中,那么系统就会将这个Intent传递到这个Activity的onNewIntent()方法,而不是去重新创建一个实例。同一个时间,只允许存在一个这样的Activity。

4、singleInstance

"singleTask"类似,不同的是,系统不会再该activity实例的task中,启动任何其他Activity到这个task中。这个Activity是它所在的task中唯一的成员。任何有这个activity启动的Activity都会放入到另外一个task中。

实际使用中的疑点

官方文档对于启动模式的介绍,不是很详细,如果单单看上面的简单介绍,也许会觉得很好理解,但是在使用的时候,发现实际情况和上面的介绍有很大的出入。
1、在使用singleTask启动模式时,并没有启动一个Activity的新的Task里作为根Activity?
比如,有两个Activity,Activity  A、B。B是singleTask模式。由A启动B,却发现,A和B都是在同一个Task中,并且B不是根Activity。
可以在Activity中打印下面的Log查看:
Log.d(TAG, "TaskId:"+getTaskId()+"  isTaskRoot:"+isTaskRoot());
2、假设现在有个Activity A,在Task1里面,他启动ActivityB到Task2里面,按下返回键,为什么能从B返回到A?
这个在后面没有补充的内容,直接在这里说明下原因。
看下官方文档的原话:
If the user continues to press Back, then each activity in the stack is popped off to reveal the previous one, until the user returns to the Home screen (or to whichever activity was running when the task began). When all activities are removed from the stack, the task no longer exists.
翻译:如果用户继续按下Back button,返回堆栈里的activity就会一个一个的返回到之前的一个,直接用户退出到Home界面(或者这个Task启动时正在运行的Activity),当返回堆栈里的所有Activity都被移除,这个task就不会再存在了。
实际上Home界面也是相当于一个Activity,所以可以理解为,当一个Task结束掉,就会返回到它启时正在运行的那个activity。
另外注意:无论一个Activity被启动到一个新的Task,还是在当前的Task,Bakc按钮总是可以回到之前的一个Activity。但是,如果你启动一个singleTask声明的Activity,并且这个Activity已经有一个实例存在于后台的Task中,那么当你再次启动的时候,它所在的整个Task就会转移到前台。那么,当你再次按Back按钮的时候,就不再是回到你之前的Activity。如上面的图中所示。也正如在上上一篇中: 1.4、Activity任务和返回堆栈1(Tasks and Back Stack ) 提到的观点:一个Task就是一个内聚单元

等等,还有一些其他的问题。是在让本人在使用这些启动模式的时候很困惑,于是自己研究了下官方文档。

研究官方文档

1、 启动一个Task(Starting a Task)

首先不谈启动模式的问题,看如何启动一个Task,参照官方文档:
http://developer.android.com/guide/components/tasks-and-back-stack.html#ManagingTasks 下得Starting a task子章节.
你可以设置如下的Intent filter使得一个Activity作为一个Task的入口:
<activity ... >
    <intent-filter ... >
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    ...
</activity>

这个Intent filter在熟悉不过了,它为我们的应用程序在launcher中创建一个icon和label。以前,可能大部分开发者的想法就是,这为我们提供了一个app的入口,实际上,实际上,它提供的是一个Task入口。而我们的一个app中,可以有多个Task。但是,通过这个Activity,我们只能进入到其中的一个Task。

所以 ACTION_MAINCATEGORY_LAUNCHER所声明的Activity提供的重要功能就是:当用户离开了这个Task后,他可以通过Activity Launcher在回到这个Task。
可以做个实验:
现在有一个app,他有两个Activity,Activity1和Activity2。Activity1是MainActivity,也就是通常的我们程序的入口,Activity2的launchMode是singleInstance。这样做的目的是使得两个Activity处于不同的Task。点击launcher,进入Activity1,再由Activity1启动Activity2,然后点击Home按钮,退出到后台。在点击launcher,我们发现进入的是Activity1,而不是程序退出到后台之前的Activity2。这是因为launcher所提供的只是Activity1所在Task的入口,而我们没有方式直接返回到Activity2所在的Task。
这样的程序,显然不是用户所期待的行为模式。看官方文档原话:
For this reason, the two launch modes that mark activities as always initiating a task,"singleTask" and ""singleInstance", should be used only when the activity has an ACTION_MAIN and aCATEGORY_LAUNCHER filter. 
翻译:基于此原因,“singleTask”和“singleInstance”这两种启动模式总是会实例化一个新的Task,它们应该仅仅用于有ACTION_MAIN和CATEGORY_LAUNCHER filter的Activity中。

可以从上看到,singleTask和singleInstance设计的意图,是为了新实例化一个Task,并且与ACTION_MAIN和CATEGORY_LAUNCHER filter结合使用,但是在实际开发中,我们总是能看到如下的做法:
1、我有某些特殊原因,确实需要用户不能回到程序退出到后台时的那个Task,这个时候,我们可以使用singTask和singeInstance另开辟一个Task。但是同样的,我们同样可以使用<activity>中得finishOnTaskLaunch属性来完成这个功能。
2、假设我有4个Activity,A->B->C->D,我需要能从B、C、D都直接能返回到A,如果你说一步步的按Back Button,那显然有时候不符合我们的需求。这个时候,我们就可以将A的启动模式定为"singleTask",那么,当我们需要在B、C、D中返回的时候,直接startActivity A就可以了。这样确实简单了很多。我们同样的可以使用别的方式来完成这个功能,我们可以使用Intent中得FLAG_ACTIVITY_SINGLE_TOP和FLAG_ACTIVITY_CLEAR_TOP结合起来,完成这个功能。

确实,singleTask和singleInstance的某些特性,确实可以解决上面的问题,我们也可以这么做(我自己也经常这么干),但是我们应该明白,google设计这两种启动模式的根本目的是为了结合ACTION_MAIN和CATEGORY_LAUNCHER filter使用,去开启一个新的Task。

2、处理任务共用性(Handling affinities)

官方文档对singleTask的介绍就是开辟一个新的Task,但是在实际使用过程中,我们发现,有时候并不是这样。这是为什么?答案就是affinities——任务公用性。
affinity定义了一个Activity将被分配到哪一个Task中。默认情况下,同一个app中得所有activity有一个同样的affinity,所以,默认情况下,同一个应用程序中得所有activity都在同一个task中。一个Task的affinity由这个Task的根Actiivty决定。然而,我们可以修改Activity默认的affinity。不同应用程序的Activity可以共用同样的affinity,或者同一个程序的不同Activity分配不同的affinity。

Note:一个应用程序默认的affinity就是应用程序的包名,所以,如果我们想定义一个不同的affinity,必须和默认的affinity不同。我们可以通过<application>中得android:taskAffinity修改整个程序的affinity,也可以通过<activity>的android:taskAffinity对单个Activity的affinity修改。android:taskAffinity是一个String类型的值,官方文档貌似没有说明这个值的具体写法,但是我发现,如果这个值中不带.号的话,程序能编译通过,但是运行不到手机上,会出现如下错误:
[2014-04-13 23:39:23 - AndroidTest2] Installation error: INSTALL_PARSE_FAILED_MANIFEST_MALFORMED
[2014-04-13 23:39:23 - AndroidTest2] Please check logcat output for more details.
[2014-04-13 23:39:23 - AndroidTest2] Launch canceled!
所以,比较好得写法是如果包名那样去定义,如com.roamer.test这样的形式。

affinity的作用发挥与两种情况:

1、当android:launchMode是singleTask或者Intent中包含FLAG_ACTIVITY_NEW_TASK时。

默认情况下,我们调用startActivity(),会实例化一个Activity,放入到与调用者相同的task。但是如果,这个如果这个Activity的的启动模式是singleTask,或者启动他得Intent包含了
FLAG_ACTIVITY_NEW_TASK时,系统会进行如下的步骤:
1、判断这个Activity有没有实例已经存在了,有的话,直接传递Intent到它的onNewIntent()方法中。
2、如果不存在,系统查找是否有与这个Activity相同affinity的Task已经存在,如果存在,那么就将这个Activity启动到这个Task中。
3、如果不存在这样的Task,那么系统就会创建一个新的Task,并且将这个Activity启动这个Task中,作为根Activity。

如上,终于明白为什么我们的singleTask指定的Activity始终不能开辟一个新的Task了,因为我们并没有给他指定affinity。官方文档对于singleTask的描述,都是基于我们使用了不同的affinity的前提下,只不过是省略了这个描述。
所以,同时,我们也明白,singleTask的正确用法,应该是结合affinity使用的。

2、当一个Activity设置allowTaskReparenting属性为true

这个属性定义了一个Activity,是否可以从一个启动他的task,切换到和他有相同affinity的task中里去(当这个task切换到前台的时候)。true表示可以移动,false表示它必须呆在启动他得task里。
通常情况下,当一个Activity启动了,那么他就会和启动他得task结合,并且在整个生命周期中都留在这个task中。但是,我们可以通过这个属性,做出如下改变,当这个Activity当前的Task处于后台,这个时候如果有一个该Activity具有相同affinity的Task被启动到前台,那么这个Activity就可以从他之前的Task,移动到这个新的Task显示。
通常,它的作用是将app中得Activity与app的main task结合起来,举个例子,如下:
有一个e-mail程序,他需要调用浏览器程序的某个Activity(假设为Activity A)来显示一些数据,这个Activity A的该属性设置为true。现在,e-mail程序调用了这个Activity A,在用户看来,好像这个Activity A就是e-mial程序的一部分,因为这个Activity A和这个e-mail程序在同一个task中。现在将e-mail退出到后台,启动浏览器程序,因为Activity  A和浏览器程序有相同的affinity,所以Activity A从e-mail程序的Task移动到浏览器程序的Task,并显示在前台。当我们下次再启动e-mail程序时,Activity A就不会存在,因为他已经移动到浏览器程序的Task里去了。

官方文档上说道:
 Since activities with " singleTask " or " singleInstance " launch modes can only be at the root of a task, re-parenting is limited to the " standard " and " singleTop " modes. (See also the  launchMode  attribute.)

因为一个Task的affinity值由他得根Activity决定,所以,Task的根Activity是不能重新移动Task的,所以该属性对于根Activity无效。又因为只有singleTask和singleInstance两种启动模式才能启动一个新的task,该属性也就是说只对standard和singleTop启动模式的Activity有效。不过,官方的描述,同样是基于singleTask使用了不同的affinity值,开辟了一个新的Task来描述的,如果一个singleTask启动模式的Activity没有开启一个新Task作为根Activity,他同样也可以移动。


四种启动模式总结

可以将四种启动模式分为两组,standard和singleTop为一组,singleTask和singleInstance为一组。
1、是否能被实例化多次?
“standard”和”singleTop”可以被实例化多次,并且可以存在于不同的task中,且一个task可以包括一个activity的多个实例;
“singleTask”和”singleInstance”则限制只生成一个实例,并且singleInstance必须是task的根元素。
singleTop要求如果创建intent的时候栈顶已经有要创建 的Activity的实例,则将intent发送给该实例,而不发送给新的实例。

2、是否允许其它activity存在于本task内
“singleInstance”独占一个task,其它activity不能存在那个task里;如果它启动了一个新的activity,不管新的activity的launch mode 如何,新的activity都将会到别的task里运行(如同加了FLAG_ACTIVITY_NEW_TASK参数)。
而另外三种模式,则可以和其它activity共存。

3、是否每次都生成新实例
“standard”对于没一个启动Intent都会生成一个activity的新实例;
“singleTop”的activity如果在task的栈顶的话,则不生成新的该activity的实例,直接使用栈顶的实例,否则,生成该activity的实例。
比如现在task栈元素为A-B-C-D(D在栈顶),这时候给D发一个启动intent,如果D是 “standard”的,则生成D的一个新实例,栈变为A-B-C-D-D。
如果D是singleTop的话,则不会生产D的新实例,栈状态仍为A-B-C-D
如果这时候给B发Intent的话,不管B的launchmode是”standard” 还是 “singleTop” ,都会生成B的新实例,栈状态变为A-B-C-D-B。
“singleInstance”是其所在栈的唯一activity,它会每次都被重用。 
“singleTask”如果在栈顶,则接受intent,如果不在栈顶,则会把它切换到前台,并销毁其上的Activity。

4、如何决定所属task
“standard”和”singleTop”的activity的目标task,和收到的Intent的发送者在同一个task内,除非intent包括参数FLAG_ACTIVITY_NEW_TASK。
如果提供了FLAG_ACTIVITY_NEW_TASK参数,可能会启动到别的task里。
“singleTask”会查找是否存在合适的task(taskAffinity相同),有的话则在其中启动,且不一定是根Activity,没有的话新建一个task。
另外,如果上面三种启动模式的Activity的allowTaskReparenting设置为true,那么他们可能重新移动到其他的task。

”singleInstance”总是把activity作为一个task的根元素,他们不会被启动到一个其他task里。




猜你喜欢

转载自blog.csdn.net/LonelyRoamer/article/details/9839583