Activity启动模式一

众所周知,Activity有4种启动模式,分别是:Standard、SingleTop、SingleTask和SingleInstance,它们控制了被启动Activity的启动行为。本文将经过具体案例,详细分析这几种模式的差别和使用场景,方便往后查阅。java

在展开具体分析以前,咱们首先要了解下两个基础知识:Activity任务栈和android:taskAffinity属性。android

基础知识

Activity任务栈(Task)

Activity任务栈(Task)是一个标准的栈结构,具备“First In Last Out”的特性,用于在ActivityManagerService侧管理全部的Activity(AMS经过TaskRecord标识一个任务栈,经过ActivityRecord标识一个Activity)。git

每当咱们打开一个Activity时,就会有一个Activity组件被添加到任务栈,每当咱们经过“back”键退出一个Activity时,就会有一个Activity组件从任务栈出栈。任意时刻,只有位于栈顶的Activity才能够跟用户进行交互。github

同一时刻,Android系统能够有多个任务栈;每一个任务栈可能有一个或多个Activity,这些Activity可能来自于同一个应用程序,也可能来自于多个应用程序。另外,同一个Activity可能只有一个实例,也可能有多个实例,并且这些实例既可能位于同一个任务栈,也可能位于不一样的任务栈。而这些行为均可以经过Activity启动模式进行控制。shell

在Android系统的多个任务栈中,只有一个处于前台,即前台任务栈,其它的都位于后台,即后台任务栈。后台任务栈中的Activity处于暂停状态,用户能够经过唤起后台任务栈中的任意Activity,将后台任务栈切换到前台。bash

android:taskAffinity属性

android:taskAffinity是Activity的一个属性,表示该Activity指望的任务栈的名称。默认状况下,一个应用程序中全部Activity的taskAffinity都是相同的,即应用程序的包名。固然,咱们能够在配置文件中为每一个Activity指定不一样的taskAffinity(只有和已有包名不一样,才有意义)。通常状况下,该属性主要和SingleTask启动模式或者android:allowTaskReparenting属性结合使用(下面会详细介绍),在其余状况下没有意义。ide

四种启动模式

上面简要介绍了Activity任务栈和android:taskAffinity属性。有了这些基础以后,咱们就能够详细介绍Activity的四种启动模式了。函数

通常状况下,咱们在AndroidManifest配置文件中,为Activity指定启动模式和taskAffinity,以下所示:ui

<activity 
android:name="leon.com.activitylaunchmode.FirstActivity" 
android:launchMode="singleTop"
android:taskAffinity="leon.com.activitylaunchmode1"/>
复制代码

除此以外,咱们也能够经过设置Intent的某些标志位来达到相同的效果,关于这些和Activity启动模式相关的标志位,咱们会在下篇文章进行介绍。this

Standard

标准模式,也是系统的默认模式。该模式下,每次启动Activity,都会建立一个新实例,而且将其加入到启动该Activity的那个Activity所在的任务栈中,因此目标Activity的多个实例能够位于不一样的任务栈。例如:ActivityA启动了标准模式的ActivityB,那么ActivityB就会在ActivityA所在的任务栈中。

关于标准模式的Activity,有一个很经典的异常: 当咱们经过非Activity的Context(例如:Service)启动标准模式的Activity时,就会有如下异常:

Caused by: Android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want? 复制代码

这是由于标准模式的Activity会进入启动它的Activity的任务栈中,可是非Activity的Context又没有任务栈,因此就出错了。解决方案在异常信息中已经给出了:在Intent中添加FLAG_ACTIVITY_NEW_TASK标志位,这样就会为目标Activity建立新的任务栈。

OK,下面咱们经过示例验证下该启动模式,具体步骤以下所示:

  1. 建立MainActivity,而且在MainActivity的onCreate方法中,打印出当前Activity所在的任务栈ID。
  2. 设置MainActivity的启动模式为Standard。
  3. 而后经过MainActivity不断启动MainActivity自己。

通过上述步骤,最终的任务栈以下所示(adb shell dumpsys activity activities):

Standard模式

经过MainActivity打印出的日志以下所示:

Standard模式日志

经过上面的任务栈和日志可知:每次启动MainActivity,都建立了新的实例,且每一个实例都在一个任务栈中(任务栈ID都是8191),最终一个任务栈中累积了多个MainActivity的实例。这样每次回退,都会看到相同的MainActivity。

SingleTop

栈顶复用模式。该模式下,若目标Activity的实例已经存在,可是没有位于栈顶,那么仍然会建立新的实例,并添加到任务栈;若目标Activity的实例已经存在,且位于栈顶,那么就不会建立新的实例,而是复用已有实例,并依次调用目标Activity的onPause -> onNewIntent -> onResume方法。

OK,下面经过两个案例详细分析下SingleTop模式:

案例1,具体步骤以下所示:

  1. 建立MainActivity,而且在onCreate方法中打印出当前Activity所在的任务栈ID,在onNewIntent方法中打印出"onNewIntent"字符串。
  2. 设置MainActivity的启动模式为SingleTop。
  3. 经过MainActivity不断启动MainActivity自己。

通过上述步骤,最终的任务栈以下所示(adb shell dumpsys activity activities):

SingleTop1

经过MainActivity打印出的日志以下所示:

SingleTop1日志

经过上面的任务栈和日志可知:尽管屡次启动了MainActivity,但只在第一次的时候建立了实例,后续的屡次启动,仅仅调用了已有MainActivity实例的onNewIntent方法。最终任务栈(TaskId为2134)内只有一个MainActivity实例。

此外,还有一点须要注意: 屡次启动处于栈顶的SingleTop模式的Activity时,其回调函数的顺序是onPause -> onNewIntent -> onResume

案例2,具体步骤以下所示:

  1. 建立MainActivity,而且在onCreate方法中打印出当前Activity所在的任务栈ID,在onNewIntent方法中打印出"onNewIntent"字符串。
  2. 建立FirstActivity,而且在onCreate方法中打印出当前Activity所在的任务栈ID,在onNewIntent方法中打印出"onNewIntent"字符串。
  3. 设置MainActivity的启动模式为SingleTop,设置FirstActivity的启动模式为Standard。
  4. 首先启动MainActivity,接着经过MainActivity启动FirstActivity,而后再经过FirstActivity启动MainActivity,如此反复。

通过上述步骤,最终的任务栈以下所示(adb shell dumpsys activity activities):

SingleTop2

经过MainActivity打印出的日志以下所示:

SingleTop2-MainActivity

经过FirstActivity打印出的日志以下所示:

SingleTop2-FirstActivity

经过上面的任务栈和日志可知:尽管MainActivity的启动模式为SingleTop,可是当它不位于栈顶时,仍然会建立新的实例。最终任务栈中会出现多个MainActivity和FirstActivity,且自始至终,都只有一个任务栈(TaskId为2068)。

SingleTask

栈内复用模式。该模式是对SingleTop的进一步增强,若Activity实例已经存在,则不论是不是在栈顶,都不会建立新的实例,而是复用已有Activity实例,即清除任务栈中目标Activity之上的全部Activity,使其位于栈顶,同时也会调用其onNewIntent方法;若Activity实例不存在,系统首先会确认是否有目标Activity指望的任务栈,若是没有,就首先建立目标Activity指望的任务栈,而后建立目标Activity实例并添加到指望的任务栈中;相反,若存在指望的任务栈,那么就直接建立目标Activity实例并将其添加到指望的任务栈。

而Activity指望的任务栈名称就是经过上面介绍的android:taskAffinity属性进行设置的。

OK,针对上述状况,咱们来看两个具体案例:

案例1,两个Activity的taskAffinity属性相同,具体步骤以下所示:

  1. 建立MainActivity,而且在onCreate方法中打印出当前Activity所在的任务栈ID,在onNewIntent方法中打印出"onNewIntent"字符串。
  2. 建立FirstActivity,而且在onCreate方法中打印出当前Activity所在的任务栈ID,在onNewIntent方法中打印出"onNewIntent"字符串。
  3. 设置MainActivity的启动模式为Standard,设置FirstActivity的启动模式为SingleTask。
  4. 首先启动MainActivity,接着经过MainActivity启动FirstActivity,而后再经过FirstActivity启动MainActivity,如此反复。

当第一次经过MainActivity启动FirstActivity时,任务栈以下所示:

SingleTask案例1-第一次经过MainActivity启动FirstActivity
可见,由于两个Activity指望的任务栈相同,所以MainActivity和FirstActivity处于相同的任务栈中(TaskId为2074)。

而后,经过FirstActivity启动MainActivity时,任务栈以下所示:

SingleTask案例1-第一次经过FirstActivity启动MainActivity

最后,再次经过MainActivity启动FirstActivity时,任务栈以下所示:

SingleTask案例1-再次经过MainActivity启动FirstActivity
可见,并无为FirstActivity建立新的实例,而是把FirstActivity之上的MainActivity出栈,直接复用已有FirstActivity实例。

经过MainActivity打印出的日志以下所示:

SingleTask案例1-MainActivity

经过FirstActivity打印出的日志以下所示:

SingleTask案例1-FirstActivity

经过上面的任务栈和日志可知:当两个Activity的taskAffinity相同时,第一次启动FirstActivity时,是直接将其实例入栈到MainActivity所在的任务栈(其实MainActivity所在的任务栈就是FirstActivity指望的任务栈);当第二次启动FirstActivity时,则是直接复用已有FirstActivity实例,同时调用其onNewIntent方法。

案例2,两个Activity的taskAffinity属性不一样,具体步骤以下所示:

  1. 建立MainActivity,而且在onCreate方法中打印出当前Activity所在的任务栈ID,在onNewIntent方法中打印出"onNewIntent"字符串。
  2. 建立FirstActivity,而且在onCreate方法中打印出当前Activity所在的任务栈ID,在onNewIntent方法中打印出"onNewIntent"字符串。
  3. 设置MainActivity的启动模式为Standard,设置FirstActivity的启动模式为SingleTask。同时设置FirstActivity的android:taskAffinity属性为leon.com.activitylaunchmode1,而MainActivity的android:taskAffinity属性为默认值,即App包名:leon.com.activitylaunchmode。
  4. 首先启动MainActivity,接着经过MainActivity启动FirstActivity,而后再经过FirstActivity启动MainActivity,如此反复。

当第一次经过MainActivity启动FirstActivity时,任务栈以下所示:

SingleTask案例2-第一次经过MainActivity启动FirstActivity
可见,由于两个Activity指望的任务栈不一样,所以系统会为FirstActivity建立新任务栈(TaskId为2078)。

而后,经过FirstActivity启动MainActivity时,任务栈以下所示:

SingleTask案例2-第一次经过FirstActivity启动MainActivity
由于MainActivity的启动模式为Standard,因此其Activity实例会入栈到启动它的FirstActivity所在的任务栈中。

最后,再次经过MainActivity启动FirstActivity时,任务栈以下所示:

SingleTask案例2-再次经过MainActivity启动FirstActivity
可见,并无为FirstActivity建立新的实例,而是把FirstActivity之上的MainActivity出栈,直接复用已有FirstActivity实例。

经过MainActivity打印出的日志以下所示:

SingleTask案例2-MainActivity

经过FirstActivity打印出的日志以下所示:

SingleTask案例2-FirstActivity

经过上面的任务栈和日志可知:和案例1的惟一不一样就是第一次启动FirstActivity时,系统会为其建立新的任务栈。


总的来讲:SingleTask模式与android:taskAffinity属性相关。以MainActivity启动FirstActivity为例(MainActivity为Standard模式,FirstActivity为SingleTask模式):

  1. 当MainActivity和FirstActivity的taskAffinity属性相同时:第一次启动FirstActivity时,并不会启动新的任务栈,而是直接将FirstActivity添加到MainActivity所在的任务栈;不然,将FirstActivity所在任务栈中位于FirstActivity之上的所有Activity都删除,直接跳转到FirstActivity中。
  1. 当MainActivity和FirstActivity的taskAffinity属性不一样时:第一次启动FirstActivity时,会建立新的任务栈,而后将FirstActivity添加到新的任务栈中;不然,将FirstActivity所在任务栈中位于FirstActivity之上的所有Activity都删除,直接跳转到FirstActivity中。

另外,当目标Activity处于栈顶时,启动SingleTask模式的目标Activity,其回调函数的顺序是onPause -> onNewIntent -> onResume,和SingleTop模式相同。

而当目标Activity存在,可是不位于栈顶时,启动SingleTask模式的目标Activity,其回调函数的顺序是onNewIntent -> onRestart -> onStart -> onResume

SingleInstance

单实例模式。该模式是SingleTask的强化,除了具备SingleTask的全部特性外,还强调任意时刻只容许存在惟一的Activity实例,且该Activity实例独自占有一个任务栈。即该任务栈只能容纳该Activity实例,不能再添加其余Activity实例到该任务栈,若是该Activity实例已经存在于某个任务栈,则直接跳转到该任务栈。

OK,下面经过一个案例详细分析下SingleInstance模式,具体步骤以下所示:

  1. 建立MainActivity,而且在onCreate方法中打印出当前Activity所在的任务栈ID,在onNewIntent方法中打印出"onNewIntent"字符串。
  2. 建立FirstActivity,而且在onCreate方法中打印出当前Activity所在的任务栈ID,在onNewIntent方法中打印出"onNewIntent"字符串。
  3. 设置MainActivity的启动模式为Standard,设置FirstActivity的启动模式为SingleInstance。
  4. 首先启动MainActivity,接着经过MainActivity启动FirstActivity,而后再经过FirstActivity启动MainActivity,如此反复。

当第一次经过MainActivity启动FirstActivity时,任务栈以下所示:

SingleInstance-第一次经过MainActivity启动FirstActivity
可见,此时FirstActivity处于独立的任务栈中(TaskId为2087)。

而后,经过FirstActivity启动MainActivity时,任务栈以下所示:

SingleInstance-第一次经过FirstActivity启动MainActivity
可见,新建立的MainActivity实例不是位于FirstActivity实例所处的任务栈,而是位于以前MainActivity实例所处的任务栈(TaskId为2086)。这也间接证实了SingleInstance模式的Activity是独占一个任务栈的。

最后,再次经过MainActivity启动FirstActivity时,任务栈以下所示:

SingleInstance-再次经过MainActivity启动FirstActivity
可见,并无为FirstActivity建立新的实例,仅仅是把TaskId为2087的任务栈切换到了前台。

经过MainActivity打印出的日志以下所示:

SingleInstance-MainActivity

经过FirstActivity打印出的日志以下所示:

SingleInstance-FirstActivity

经过上面的任务栈和日志可知:尽管屡次启动了SingleInstance模式的FirstActivity,但只在第一次的时候建立了Activity实例,而且为该实例建立了新的任务栈,后续的屡次启动,仅仅调用了已有FirstActivity实例的onNewIntent方法,并将其任务栈切换到了前台。 同时,FirstActivity所处任务栈的TaskId为2087,MainActivity所处任务栈的TaskId为2086,MainActivity的实例永远不可能位于FirstActivity所处的任务栈中,即SingleInstance模式的Activity独占一个任务栈。

此外,还有一点须要注意: 当目标Activity实例已经存在时,启动SingleInstance模式的目标Activity,其回调函数的顺序是onNewIntent -> onRestart -> onStart -> onResume

特殊案例

上述经过具体案例详细分析了四种启动模式,可是还有一些特殊使用场景须要详细分析下。

针对SingleTask模式的进一步强化

假设如今有两个任务栈:前台任务栈中包含ActivityA和ActivityB,后台任务栈中包含ActivityC和ActivityD,且前台任务栈中Activity的启动模式均为Standard,后台任务栈中Activity的启动模式均为SingleTask,两个任务栈的栈名是不一样的。

如今有两种典型的使用场景: 场景1:当经过前台任务栈中的ActivityB启动后台任务栈中ActivityD时,系统会将后台任务栈会切换到前台。此时,当用户经过“back”键退出时,整个退出顺序应该是ActivityD -> ActivityC -> ActivityB -> ActivityA

场景2:当经过前台任务栈中的ActivityB启动后台任务栈中ActivityC时,系统会将后台任务栈会切换到前台,同时把ActivityC之上的ActivityD出栈。此时,当用户经过“back”键退出时,整个退出顺序应该是ActivityC -> ActivityB -> ActivityA

这里咱们经过具体案例验证下场景1(场景2比较相似,再也不赘述),具体步骤以下所示:

  1. 首先建立包名为leon.com.launchmodeab的App1,包含ActivityA和ActivityB,设置其启动模式为Standard,而后建立包名为leon.com.launchmodecd的App2,包含ActivityC和ActivityD,设置其启动模式为SingleTask。
  2. 接着启动App2,并依次启动ActivityC和ActivityD,而后经过“home”键切换到后台。
  3. 而后启动App1,并依次启动ActivityA和ActivityB。
  4. 最后,经过ActivityB启动ActivityD,并经过“back”键依次退出,观察Activity的退出顺序。

当完成步骤1~3时,任务栈以下所示:

前台任务栈和后台任务栈都已启动
可知,TaskIdId为2146的任务栈包含ActivityA和ActivityB,TaskId为2145的任务栈包含ActivityA和ActivityB。此时,如果直接经过“back”键退出,那么退出顺序是 ActivityB -> ActivityA

当经过ActivityB启动ActivityD后,任务栈以下所示:

经过ActivityB启动ActivityD后
可知,包含ActivityD的任务栈被切换到了前台,虽然ActivityC和ActivityD被隔开了,可是从TaskId和栈名来看,他们仍是是同一个任务栈,而ActivityA和ActivityB所在的栈则在后面。此时,若经过“back”键退出,那么退出顺序是 ActivityD -> ActivityC -> ActivityB -> ActivityA

taskAffinity和allowTaskReparenting结合使用场景

android:allowTaskReparenting主要用于Activity的迁移。当allowTaskReparenting为true时,表示该Activity能从其余任务栈迁移到指望的任务栈,当allowTaskReparenting的值为“false”,表示该Activity不能从其余任务栈进行迁移,默认为false。这个比较抽象,咱们看一个具体的案例,具体步骤以下所示:

  1. 首先建立包名为leon.com.launchmodeab的App1,包含ActivityA,设置其启动模式为Standard,而后建立包名为leon.com.launchmodecd的App2,包含ActivityC,设置其启动模式为Standard。
  2. 接着启动App1,并经过App1的ActivityA启动App2的ActivityC,,而后经过“home”键切换到后台。
  3. 而后启动App2,查看任务栈状况。

OK,首先来看下ActivityC的allowTaskReparenting属性为true的状况。 完成上述1~2步骤后的任务栈以下所示:

Activity迁移1-2
可见,此时ActivityC和ActivityA处于同一个任务栈。

完成第3步后的任务栈以下所示:

Activity迁移3
可见,此时ActivityC从TaskId为2166的任务栈迁移到了TaskId为2167的任务栈中,符合咱们的预期。

而后看下ActivityC的allowTaskReparenting属性为false的状况。 完成上述1~2步骤后的任务栈以下所示:

Activity不迁移1-2
可见,此时ActivityC和ActivityA处于同一个任务栈。

完成第3步后的任务栈以下所示:

Activity不迁移3
可见,原来的ActivityC实例并无被迁移,而是系统为ActivityC建立了新的实例,也符合咱们的预期。


先介绍这两个特殊使用场景,后面会陆续补充...

参考文章

  1. Activity启动模式与任务栈(Task)全面深刻记录(上)
  2. Activity启动模式与任务栈(Task)全面深刻记录(下)
  3. Android 之Activity启动模式(一)之 lauchMode
  4. Android 之Activity启动模式(二)之 Intent的Flag属性
  5. Android 之Activity启动模式(三)之 启动模式的其它属性
相关文章
相关标签/搜索