Activity 的LauncherMode, 不可遗漏的知识点

转载请标明出处:http://blog.csdn.net/edisonchang/article/details/49981457

最近遇到一个与 Activity LauncherMode 有关的bug,情况大概是这样的,我们的应用apkA,activityA 是程序的入口,LauncherMode 是默认的standard模式,activityB是singleTask模式,在程序启动activityA后,activityA在执行完业务逻辑后会跳转ActivityB,然后finish自己。apkB是合作方的一个应用,在apkB中会通过Intent启动应用apkA 的activiyA, 并通过intent传递的不同数据来实现特定的业务逻辑。示例代码如下:

Intent intent = mContext.getPackageManager().getLaunchIntentForPackage(Consts.PRODUCT_PACKAGE_NAME);        
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);

问题出现了,在调试中发现(1)apkA没有提前启动,由apkB调用时一切正常,(2)apkA提前启动后,home键退到后台,然后再由apkB调用,这时apkA 虽然回到了前台,但是却没有执行apkB意图实现的逻辑。在activityA的 onCreate、onNewIntent、onResume 等方法中都加上日志,然后再次从apkB中执行调用apkA 的代码 ,发现日志没有输出。太奇怪了,这些生命周期方法竟然一个都没有调用,apkA是如何再次回到前台的。

问题到底出在哪呢,带着这些疑问,我们还是仔细了解下Activity 的LuncherMode,Activity有四种启动模式:standard, singleTop, singleTask, singleInstance,下面逐一介绍:

(1)standard :标准模式,也是Activity的默认加载模式。每次启动时,不管这个Activity实例是否存在,都会重新创建一个新实例,被创建的实例遵守Activity的典型生命周期。一个任务栈中可以有多个实例,每个实例也可以属性不同的任务栈,这一点取决于启动的Activity 所处的task栈,以及启动时是否指定FLAG_ACTIVITY_NEW_TASK创建一个新的任务栈。

举个例子:比如Activity 栈中的情况为:A、B、C , 这是C通过Intent 再次启动了C,那么现在栈中的情况就变成了 A、B、C、C , 如果栈顶的C又通过Intent跳转到B(B 为standard), 则栈中的顺序依次为 A、B、C、C、 B 。此时按Back 键返回时,B、C、C、B、A 会依次弹出Activity 栈出现在前台。

(2)singleTop , 顾名思义,栈顶单一模式。属于此类模式的Activity 如果已经处于任务栈的栈顶时,即便再次启动也不会重新创建,此时activity 中onNewIntent方法会被回调。如果Acitivity 的实例不是位于栈顶,那么就和standard标准模式一般。

如上述例子,如果C的启动模式为singleTop,那么在栈中情况为 A、B、C时,如果C通过Intent再次启动C,则不会新创建一个C的实例,此时栈中的情况依旧是 A、B、C; 同样,如果B 也是 singleTop ,由于B并没有位于栈顶,那么就和standard 模式一样会新创建一个B 的实例入栈,栈中的情况为 A、B、C、B。Back键出栈顺序也是遵循先进后出原则。

(3)singleTask,栈内的单实例模式。此种模式下,如果在一个栈中Activity已经有一个实例存在,那么多次启动此Activity也不会再重复创建,区别于singleTop主要在于singleTask模式对于Activity是否位于栈顶并没有要求,如果创建时,发现栈中已经存在该activity的实例,系统会把该实例调到栈顶,并调用它的onNewIntent方法,如果此类实例不存在,就创建一个并压入栈中。

以上面的例子来说,C的启动模式为singleTask,那么C通过Intent再次启动C,这种情况下和SingleTop一样不会重复创建一个C的实例,此时栈中的情况依旧是 A、B、C,但是如果B也是singleTask模式,那么通过C创建B时,由于系统发现B已经在栈中存在,就会将B调到栈顶,将B上面的Activity全部清除出栈,此时栈的情况变成了 A、B 。

此外,在某些场合下singleTask模式还会与TaskAffinity参数配对使用,由TaskAffinity参数来指定Activity运行所处的任务栈名称,Activity会运行在TaskAffinity相同的任务栈中。如果Activiity指定TaskAffinity,或者启动时指定FLAG_ACTIVITY_NEW_TASK标志那么分析栈中情况就和上面例子有些区别了。

比如目前任务栈T1的情况为A、B、C,这个时候如果C 通过Intent启动D ,D是singleTask模式且需要的任务栈为T2, 由于T2 并不存在,系统就会先创建T2栈,,然后将D的实例压入T2栈中,按Back键时,依然可以看到 C、B、A 显示在前台并依次出栈;如果启动D时,T2栈情况E、D、F, 那么启动后T1依旧不变 ,T2 栈中情况变为 E、D ,Back 键后退时T2 先出栈,然后T1再出栈, 顺序为D、E、C、 B、A;还有另一种场景,D是singleTask模式,但没有指定TaskAffinity,栈中的原来的情况时A、B、C ,那么由C启动D后,D并不会重新创建一个任务栈,此时栈中的顺序为 A、B、C、D,但是如果D是由其他Apk xx调起的话,就得分两种情况说明了,情况一:D已经启动,那么和前面分析的类似,将D调到栈顶,并清除D上的所有Activity,情况二:D没有启动,那么就启动一个新的任务栈,将D压入栈内,不会和Apki xx 的Activity共用一个任务栈,因为包名不同,不指定TaskAffinity 情况下,默认就是包名。

(4)singleInstance, 加强的单实例模式。除了具备singleTask的栈内单实例特性外,不同之处在于系统会对这种模式下的Activity单独创建一个新的任务栈,由于其作用域不仅仅在栈内,所以后续的任何启动都不会再次创建新的Activity实例。

接上面的例子,如果T1栈中的情况时A、B、C, C通过Intent启动D ,D 为singleInstance, 则会新创建一个T2, 并将D压入栈中,此时T1 栈不变,T2 为D,如果C再次启动D,那么T2也不会新建一个D的实例,所以两个栈的情况也不会变化,如果D此时再次跳转C ,则T1就变为A、B、C、C。此时按Back 返回时,出栈顺序为C、C、B、A、D,T1 整体出栈后,T2再出栈。

对Activity的四种启动模式粗略的了解后,我们再来分析下问题出现的场景。按照以上的描述,apkA 运行后任务栈的情况为B ,那么apkB 再次启动ApkA的activityA 为什么在没有回调生命周期方法的前提下将activityB再次调到前台呢?原因在于,apkB启动activityA加了FLAG_ACTIVITY_NEW_TASK标记位,我们来看下标志的这段注释说明:

When using this flag, if a task is already running for the activity
     * you are now starting, then a new activity will not be started; instead,
     * the current task will simply be brought to the front of the screen with
     * the state it was last in.  

大概意思时,如果运行时发现目标Activity的任务栈已经在,则不会再次创建Activity,只会将任务栈调到前台。是否有一种豁然开朗的感觉,果然我们把这个标记位去掉后,activiyA就妥妥调起了。

感兴趣的朋友可以自行测试,如果上述介绍中存在错误或者遗漏,欢迎大家指正。

你可能感兴趣的:(android)