众所周知,Activity有4种启动模式,分别是:Standard、SingleTop、SingleTask和SingleInstance,它们控制了被启动Activity的启动行为。本文将经过具体案例,详细分析这几种模式的差别和使用场景,方便往后查阅。java
在展开具体分析以前,咱们首先要了解下两个基础知识:Activity任务栈和android:taskAffinity
属性。android
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
是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,下面咱们经过示例验证下该启动模式,具体步骤以下所示:
通过上述步骤,最终的任务栈以下所示(adb shell dumpsys activity activities):
经过MainActivity打印出的日志以下所示:
经过上面的任务栈和日志可知:每次启动MainActivity,都建立了新的实例,且每一个实例都在一个任务栈中(任务栈ID都是8191),最终一个任务栈中累积了多个MainActivity的实例。这样每次回退,都会看到相同的MainActivity。
SingleTop
栈顶复用模式。该模式下,若目标Activity的实例已经存在,可是没有位于栈顶,那么仍然会建立新的实例,并添加到任务栈;若目标Activity的实例已经存在,且位于栈顶,那么就不会建立新的实例,而是复用已有实例,并依次调用目标Activity的onPause -> onNewIntent -> onResume
方法。
OK,下面经过两个案例详细分析下SingleTop模式:
案例1,具体步骤以下所示:
通过上述步骤,最终的任务栈以下所示(adb shell dumpsys activity activities):
经过MainActivity打印出的日志以下所示:
经过上面的任务栈和日志可知:尽管屡次启动了MainActivity,但只在第一次的时候建立了实例,后续的屡次启动,仅仅调用了已有MainActivity实例的onNewIntent
方法。最终任务栈(TaskId为2134)内只有一个MainActivity实例。
此外,还有一点须要注意: 屡次启动处于栈顶的SingleTop模式的Activity时,其回调函数的顺序是onPause -> onNewIntent -> onResume
。
案例2,具体步骤以下所示:
通过上述步骤,最终的任务栈以下所示(adb shell dumpsys activity activities):
经过MainActivity打印出的日志以下所示:
经过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属性相同,具体步骤以下所示:
当第一次经过MainActivity启动FirstActivity时,任务栈以下所示:
而后,经过FirstActivity启动MainActivity时,任务栈以下所示:
最后,再次经过MainActivity启动FirstActivity时,任务栈以下所示:
经过MainActivity打印出的日志以下所示:
经过FirstActivity打印出的日志以下所示:
经过上面的任务栈和日志可知:当两个Activity的taskAffinity相同时,第一次启动FirstActivity时,是直接将其实例入栈到MainActivity所在的任务栈(其实MainActivity所在的任务栈就是FirstActivity指望的任务栈);当第二次启动FirstActivity时,则是直接复用已有FirstActivity实例,同时调用其onNewIntent
方法。
案例2,两个Activity的taskAffinity属性不一样,具体步骤以下所示:
android:taskAffinity
属性为leon.com.activitylaunchmode1,而MainActivity的android:taskAffinity
属性为默认值,即App包名:leon.com.activitylaunchmode。当第一次经过MainActivity启动FirstActivity时,任务栈以下所示:
而后,经过FirstActivity启动MainActivity时,任务栈以下所示:
最后,再次经过MainActivity启动FirstActivity时,任务栈以下所示:
经过MainActivity打印出的日志以下所示:
经过FirstActivity打印出的日志以下所示:
经过上面的任务栈和日志可知:和案例1的惟一不一样就是第一次启动FirstActivity时,系统会为其建立新的任务栈。
总的来讲:SingleTask模式与android:taskAffinity属性相关。以MainActivity启动FirstActivity为例(MainActivity为Standard模式,FirstActivity为SingleTask模式):
- 当MainActivity和FirstActivity的taskAffinity属性相同时:第一次启动FirstActivity时,并不会启动新的任务栈,而是直接将FirstActivity添加到MainActivity所在的任务栈;不然,将FirstActivity所在任务栈中位于FirstActivity之上的所有Activity都删除,直接跳转到FirstActivity中。
- 当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模式,具体步骤以下所示:
当第一次经过MainActivity启动FirstActivity时,任务栈以下所示:
而后,经过FirstActivity启动MainActivity时,任务栈以下所示:
最后,再次经过MainActivity启动FirstActivity时,任务栈以下所示:
经过MainActivity打印出的日志以下所示:
经过FirstActivity打印出的日志以下所示:
经过上面的任务栈和日志可知:尽管屡次启动了SingleInstance模式的FirstActivity,但只在第一次的时候建立了Activity实例,而且为该实例建立了新的任务栈,后续的屡次启动,仅仅调用了已有FirstActivity实例的onNewIntent
方法,并将其任务栈切换到了前台。 同时,FirstActivity所处任务栈的TaskId为2087,MainActivity所处任务栈的TaskId为2086,MainActivity的实例永远不可能位于FirstActivity所处的任务栈中,即SingleInstance模式的Activity独占一个任务栈。
此外,还有一点须要注意: 当目标Activity实例已经存在时,启动SingleInstance模式的目标Activity,其回调函数的顺序是onNewIntent -> onRestart -> onStart -> onResume
。
上述经过具体案例详细分析了四种启动模式,可是还有一些特殊使用场景须要详细分析下。
假设如今有两个任务栈:前台任务栈中包含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~3时,任务栈以下所示:
ActivityB -> ActivityA
。
当经过ActivityB启动ActivityD后,任务栈以下所示:
ActivityD -> ActivityC -> ActivityB -> ActivityA
。
android:allowTaskReparenting
主要用于Activity的迁移。当allowTaskReparenting为true时,表示该Activity能从其余任务栈迁移到指望的任务栈,当allowTaskReparenting的值为“false”,表示该Activity不能从其余任务栈进行迁移,默认为false。这个比较抽象,咱们看一个具体的案例,具体步骤以下所示:
OK,首先来看下ActivityC的allowTaskReparenting属性为true的状况。 完成上述1~2步骤后的任务栈以下所示:
完成第3步后的任务栈以下所示:
而后看下ActivityC的allowTaskReparenting属性为false的状况。 完成上述1~2步骤后的任务栈以下所示:
完成第3步后的任务栈以下所示:
先介绍这两个特殊使用场景,后面会陆续补充...