跳转至

Activity四大启动模式

Activity有四种启动模式:

  • Standard 标准模式

  • SingleTop 栈顶复用模式

  • SingleTask 栈内复用模式

  • SingleInstance 单实例模式

任务栈

在分别描述四种启动模式的特性前,需先引出一个概念:任务栈(也可以叫返回栈),它是一种后入先出的数据结构。

每个Activity都依附着任务栈运行,系统在创建Activity前会先创建一个ActivityRecord对象,用于映射该Activity,并将ActivityRecord对象压入任务栈中并处于栈顶,然后再通过ActivityRecord对象生成Activity实例。 每当我们按下Back键或调用finish()方法去销毁一个Activity时,处于栈顶的Activity就会出栈,前一个入栈的Activity就会重新处于栈顶的位置。系统总会显示处于栈顶的Activity给用户。

一部正在运行app的安卓手机中,会有一个前台任务栈和0个或多个后台任务栈。当前在手机上运行的窗口,会有一个前台的任务栈,而处于后台的任务列表,每一个都对应着一个后台任务栈。 当用户将后台应用重新切换至前台时,也伴随着将当前运行的前台任务栈移至后台,将系统下一秒想要运行的后台任务栈切换至前台运行的过程。

可以通过配置的方式指定Activity想要依附的任务栈:

<activity  
    android:name="com.rtfsc.demo.MainActivity"
    android:taskAffinity="com.rtfsc.demo1"
</activity>

通过在AndroidManifest.xml文件中设置,给MainActivity设置想要的任务栈是名为 com.rtfsc.demo1 的任务栈。如果不设置,Activity默认所需任务栈的名字为程序的包名,即默认会将MainActivity压入与程序包名相同的任务栈。

taskAffinity属性主要与 singleTask 启动模式或 allowTaskReparenting 属性配对使用,其他情况下没有意义。

值得注意的是,任务栈实际上也是一个对象(TaskRecord),任务栈之间可以有相同的名字,但实际上如果它们是不同的对象,那么也就不是同一个任务栈。在后文聊到 SingleInstance 启动模式时,再举例说明,这里先简单提一下。

Standard

标准模式,这是系统默认的Activity启动模式,每一次启动一个新的Standard模式的Activity,系统都会创建一个新的Activity实例,无论它在任务栈中是否已经有实例。在这种模式下,谁启动了这个Activity,那么这个Activity就运行在启动它的那个Activity所在的任务栈中。**比如 Activity A 启动了 Activity B (B 是标准模式),那么 B 就会进入 A 所在的任务栈中。**每创建一个新的实例,就会走一遍Activity的经典生命周期。

SingleTop

栈顶复用模式,顾名思义,当 Activity 的启动模式为 SingleTop 时,在启动 Activity 时如果发现任务栈的栈顶已经是该 Activity 的实例,则认为可以直接复用这个实例,不会创建新的Activity实例。此时,Activity 的 onCreate、onStart 不会被系统调用,而另一个方法 onNewIntent 会被调,调用完 onNewIntent 后,紧接着会调用 onResume 方法。

如果新 Activity 的实例在栈内已经存在,但不位于栈顶,那依然会重新创建一个 Activity 实例,并放入栈顶。 比如,如果此时任务栈内的实例顺序是ABC,C 位于栈底且是 SingleTop 模式,此时再次启动一个 C,那么此时栈内实例顺序为CABC。

与 Standard 标准模式一样,谁启动了 SingleTop 模式的Activity,那么这个Activity就会运行在启动它的那个Activity所在的任务栈中。

SingleTask

栈内复用模式,这是一种单实例模式,在这种模式下,只要 Activity 在一个栈中存在,那么多次启动此 Activity 都不会重新创建实例,和 singleTop 一样,复用时系统会回调其 onNewIntent 方法。

一个具有 SingleTask 模式的 Activity 在请求启动后,系统会先寻找是否有该 Activity 想要的任务栈,如果没有,系统则会先创建一个该 Activity 想要的任务栈,然后创建该 Activity 的实例,并放入栈顶。

反之,如果事先已经有了这样一个任务栈,系统则会看看该任务栈是否存在该 Activity 的实例,如果没有该 Activity 的实例,系统也会重新创建一个并放入栈顶。如果已经有该实例,系统则会把该实例调到栈顶,将在该实例之上的其他 Activity 出栈(如果有),并调用该实例的 onNewIntent 方法。

图中,右边的详细过程,因为 B 位于栈顶时,A 此时位于后台,处于停止状态,所以当从 B 跳到 A 时,会依次执行 B.onPause()、A.onNewIntent()、A.onRestart()、A.onStart()、A.onResume()、B.onStop()、B.onDestroy()。

  • 如果实例在栈顶被复用 自身执行的生命周期是:onPause()、onNewInetnt()、onResume()。与 SingleTop 启动模式相同。

  • 如果实例在栈内被复用 自身执行的生命周期是:onNewIntent()、onRestart()、onStart()、onResume()。因为实例处于栈内,不在前台,Activity 处于暂停状态,所以会有 onRestart(),onStart() 过程。

SingleInstance

单实例模式,这是一种加强的SingleTask模式,它除了具有 singleTask 模式的所有特性外,还加强了一点,那就是具有此模式的 Activity 只能单独位于一个任务栈中。

同时,SingleInstance 模式也可以与 taskAffinity 配对使用,通过 taskAffinity 指定 Activity 想要依附的任务栈名字。

考虑一种情况,两个Activity:A,B 都指定为 SingleInstance 启动模式,并同时指定 A,B 的 taskAffinity 为 com.rtfsc.demo1,即为它们指定相同的任务栈栈名。结合 SingleInstance 的特性:具有此模式的 Activity 只能单独位于一个任务栈中。那么这两个 A,B 指定了相同任务栈名字的 Activity ,同时打开时会发生什么情况呢,它们会争同一个任务栈吗?它们会因为争同一个任务栈而杀掉对方以维护自己的”独立存在“特性吗?

举个例子看看,设置 AndroidManifest.xml:

<activity  
    android:name="com.rtfsc.demo.MainActivity"  
    android:exported="true"      
    android:launchMode="singleTop"  
    android:taskAffinity="com.rtfsc.demo1">  
</activity>  

<activity  
    android:name="com.rtfsc.demo.Main2Activity"  
    android:exported="true"  
    android:launchMode="singleTop"  
    android:taskAffinity="com.rtfsc.demo1">  
</activity>

先打开 MainActivity,再通过 MainActivity 打开 MainActivity2,看看任务栈的情况。

adb shell dumpsys activity

        #2 Task=80 type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1368,3192]
         #1 ActivityRecord{d02f4e u0 com.rtfsc.demo/.Main2Activity t80} type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1368,3192]
          #0 6a1cc8b com.rtfsc.demo/com.rtfsc.demo.Main2Activity type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1368,3192]
         #0 ActivityRecord{671bf34 u0 com.rtfsc.demo/.MainActivity t80} type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1368,3192]
          #0 b6edafd com.rtfsc.demo/com.rtfsc.demo.MainActivity type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1368,3192]

只看任务栈的信息,可以看到,虽然 MainActivity 与Main2Activity 有相同的任务栈名,但系统实际上还是为它们创建了不同的任务栈实例,并分别将它们单独放到各自 taskAffinity 指定的任务栈中,两者互不冲突。

回顾一下之前介绍任务栈时提到的:任务栈实际上也是一个对象(TaskRecord),任务栈之间可以有相同的名字,但实际上如果它们是不同的对象,那么也就不是同一个任务栈。

所以,上面的问题显然有了答案,那就是不会。

这里dump出来的跟我现象中的不一样,怀疑可能是手机厂改了。 后续用aosp rom试试

使用场景

  • standard 标准模式 默认的启动模式,适用于应用的大多数场景。

  • singleTop 栈顶复用模式 为了满足特定功能,不需要重复创建显示的页面,比如登录页面,从通知栏消息打开的页面。

  • singleTask 栈内复用模式 适用于重复创建比较耗性能的页面,比如主页面,WebView页面(WebView是一个很耗性能的组件)。

  • singleInstance 单实例模式 这个启动模式我们一般用不到,系统级应用使用该模式比较多。比如锁屏页面,来电显示页面等。

启动模式相关的Flags

Activity的启动模式,不仅可以通过 AndroidManifest.xml 文件来指定,也可以通过在 Intent 中指定 FLAGS 来设置 Activity 启动时的行为。

Intent intent = Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
mContext.startActivity(intent);

Intent 中的标志位有很多,这里就介绍几个与启动模式相关的常用flags。

FLAG_ACTIVITY_SINGLE_TOP

相当于给 Activity 设置了 singleTop 的启动模式。

FLAG_ACTIVITY_NEW_TASK

给 Intent 设置这个 flag 会有两种情况, 即将启动的 Activity 所想要依附的任务栈是否存在:

  • 存在

则按该 Activity 在 AndroidManifest.xml 文件中设置的启动模式进行下一步。

若在 xml 文件中设置的启动模式为 standard,那么每次打开都会重新创建一个 Activity 实例。

若在 xml 文件中设置 singleTask,则会判断任务栈中是否有该 Activity 来决定是否复用,有的话则会直接复用,并调用其 onNewIntent() 方法。若没有则会重新创建一个实例。

其他的启动模式同理,按着上面讨论的启动模式特性来理解就可以了。

若在 xml 文件中没有设置 launchMode,就按默认的 standard 启动模式启动,每次都会重新创建一个新的 Activity 实例。

  • 不存在

如果待启动的 Activity 设置了 taskAffinity 值,则会先创建一个 taskAffinity 为名的任务栈,然后按待启动 Activity 设置的启动模式启动该 Activity。 若没有设置 taskAffinity 属性,则按程序的包名创建一个任务栈,同样以 Activity 设置的启动模式启动 Activity。

总结地说,FLAG_ACTIVITY_NEW_TASK 会帮 Activity 管理其任务栈:如果所需任务栈已经存在了,则设置这个 FLAG 标志位不会有其他任何效果。如果所需任务栈不存在,则会根据所需任务栈的名字创建一个任务栈。 任务栈名可以通过 taskAffinity 属性指定。

安卓中我们经常通过 Context 对象来启动 Activity,它是一个上下文对象,里面包含了我们建立一个上下文的各种配置信息。这个上下文,可以是一整个应用(Application),可以是一个页面(Activity),也可以是一个服务(Service)。

所以,Context 这个对象有三种类型,分别是 Application Context,Service Context,Activity Context。这三种类型的 context 对象都可以用来启动 Activity。

Activity Context 启动 Activity 就不用说了,我们经常使用它。但是你试过用Application Context 和 Service Context 去启动一个 Activity 吗?

一些同学可能试过,第一次使用除 Activity Context 以外的 Context 对象来启动页面时,都会遇上一个报错:

"Calling startActivities() from outside of an Activity "
                    + " context requires the FLAG_ACTIVITY_NEW_TASK flag on first Intent."
                    + " Is this really what you want?"

这是因为只有 Activity 才有任务栈的概念,Application 和 Service 是没有任务栈的概念的,所以我们用 Application Context 或 Service Context 去启动一个Activity的时候,都需要显示地加上 FLAG_ACTIVITY_NEW_TASK 这个标志位,系统才会给这个 Activity 创建任务栈并启动。

FLAG_ACTIVITY_CLEAR_TOP

如果要启动的 Activity 已经在当前任务中运行,则不会启动该 Activity 的新实例,而是会销毁位于它之上的所有其他 Activity,并通过 onNewIntent() 将此 intent 传送给它的已恢复实例(现在位于堆栈顶部)。

这个标志位经常与 FLAG_ACTIVITY_NEW_TASK 结合使用,它俩结合使用时的效果,实际上就是同时设置 FLAG_ACTIVITY_NEW_TASK 和 FLAG_ACTIVITY_CLEAR_TOP 标志位时的效果。正所谓,听君一席话,如听一席话,哈哈,不过我没在开玩笑,它俩结合使用的效果,真的是如上所说的那样!

不过,如果被启动的 Activity 采用 standard 模式启动,那么它连同它之上的 Activity 都要出栈,系统会创建新的 Activity 实例并压入栈顶。

总结

standard

  • 此模式为activity的默认启动模式,即在不指定activity的情况下,所有的activity皆是在此模式下。
  • 在standard模式下,启动一个新的activity,它就会进入任务栈,并处于栈顶的位置。
  • 如图所示,每当有新的activity建立,它会自动创建一个新的实例,并处于栈顶。
  • 入栈时,Activity01,Activity02,Activity03,依次入栈;出栈时,处于栈顶的Activity03先出栈,之后是Activity02,最后是Activity01。

singleTop

此模式与standard类似,

  • 区别是启动了一个Activity处于栈顶时,再次启动时,不再创建新的实例; 如果启动的Activity没有位于栈顶时,则在创建一个新的实例位于栈顶。

  • 如图所示:当前栈顶为Activity03, 若启动的页面仍为Activity03,则复用Activity03; 若启动的页面为不是栈顶的Activity02,则新建Activity04作为栈顶。

singleTask

  • 此模式下,整个Activity有且只有一个实例。
  • 每次启动Activity的模式时,系统会检查是否存在该实例,若存在该实例,则直接使用该实例,并且将该Activity之上的所有Activity出栈。 如果没有,则自动生成新的实例。
  • 如图:依次入栈Activity01,Activity02,Activity03;想要再次启动Activity02,则复用Activity02,并将Activity03移除该栈。

singleInstance

  • 此模式下,Activity在整个系统中有且只有一个实例,其中,最大的不同是Activity会启动i一个新的任务栈。
  • 若启动的Activity不存在,系统会创建一个新的任务栈,再创建一个新的Activity实例,并把该Activity加入栈顶; 若启动的Activity已经存在,系统会把该Activity所在的任务栈转到前台,从而使该Activity显现出来。

Activity四种启动模式详解 activity 4种启动模式 Activity 的四种启动模式

评论