文章目录

  • 几个概念
  • Task
  • Back Stack
  • LaunchMode的作用和使用方法
  • LaunchMode作用
  • LaunchMode使用方法
  • standard
  • singleTop
  • singleTask
  • singleInstance
  • singleInstancePerTask
  • 如何指定启动模式
  • 使用场景
  • 参考

几个概念

Task

Task叫做任务,这个简单,表示我们需要完成的事情,注意,这里我们说的是任务,是个名词,例如要发短信,那我们的任务就是发送一条短信。

Back Stack

我们常叫做回退栈,或者是任务栈,这个是什么意思呢?上面我们说过,需要完成任务,那我们就需要使用一系列的Activity来完成,例如发短信,则完成该任务需要如下步骤:

打开短信主页面MainActivity

点击添加短信按钮,打开NewSMSActivity

在NewSMSActivity中编写短信并发送

以上的任务中涉及到两个Activity,那这两个Activity就存放在这个Back Stack中,又因为Back Stack是栈类型的数据结构,所以上面的步骤在这个Back Stack中的活动顺序如下:

MainActivity先压栈

点击添加按钮,NewSMSActivity压栈,

短信发送完成,点击返回按钮,NewSMSActivity弹栈,回到MainActivity

在MainActivity点击返回按钮,MainActivity弹栈,此时该Back Stack为空,就返回到Launcher了

所以我们明白了,这个回退栈其实就是一个存储Activity实例的容器,执行每个Task时,先创建一个Back Stack,在Task执行过程中将所使用的Activity都按照FILO的顺序以此压入这个Back Stack,Task目标完成之后,按下返回按钮时,Back Stack中的Activity按照压栈相反地的顺序以此弹栈,直到栈中没有Activity实例时,进入Launcher。

由此,我们还可以知道,每个Task和Back Stack是一一对应的关系,一般情况下,每需要执行一个Task时,都至少需要一个Back Stack容器,并且这个容器中都至少会有一个Activity实例。

LaunchMode的作用和使用方法

LaunchMode作用

顾名思义,LaunchMode就是启动模式,啥是启动模式?启动模式意思是使用不同的模式启动之后,会有不同的属性和表现,举个例子,钢铁侠使用正常模式启动,一般可以秒杀所有小兵小将,但是如果对付发狂的绿巨人浩克,就需要启动超强模式,穿上反浩克装甲,要不然打不过,放到我们Actiivty这里也是一样,既然是配置在Activity上的,那就说明Activity有好几种启动模式,使用不同的启动模式启动的Activity有不同的属性和表现。

那为啥需要启动模式呢?需求!对,需求是所有东西被建立或者被制造出来的原因,因为我们对Actiivty有不同的需求,举个老生常谈的发邮件例子,邮件主页Activity要求不论怎么打开,打开多少次,就只能有一个主页Activity的实例,对吧,如果有多个实例我们就很麻烦,不知道显示哪个,也不知道要关闭哪个,那这就是个需求,对应这个需求,我们就需要对这个主页Activity设置一种启动模式,不论怎么打开就只有一个实例,这样需求就满足啦。

LaunchMode使用方法

按照Android Developer上的说明,LaunchMode有两个使用地方,一个是Mainfest的activity节点下,一个是在startActivity方法的Intent中设置Flag,第二种方法我们后续再讨论,先说说第一种方法。

要告诉小白的是,LaunchMode要在启动该Activity之前使用才有效,如果Activity都已经启动了,实例都创建完了,再设置什么模式都没用啦。

第一种使用方式特别简单,在Mainfest中的activity节点中添加android:launchMode即可,如下所示:

android:name=“.MainActivity”

android:label=“邮件列表”

android:launchMode=“singleTask”>

有五种launchMode可选,分别是:

“standard”

“singleTop”

“singleTask”

“singleInstance”

“singleInstancePerTask”

如果不添加launchMode也可以,默认的launchMode是”standard” 。

按照Android Developer官方上,他们按照Activity是否可以被实例化多次,把这四个模式分了两个组, “standard” 和”singleTop” 属于可以被实例化多次这个组,他们的实例可以属于任何Task,并且可以位于Back Stack的任何位置,其余两个属于不可被实例化多次这组,他们常用于启动一个Task,所以一个Task只有一个这种实例,并且这个还往往位于Back Stack的最开始。这种分组可以帮助我们初步理解每种启动模式的区别。

standard

从现在开始好好讲解这四种启动模式了,首先看看”standard”,有时称为标准模式

前面我们说过,Activity默认就是这种模式的,所以你的Activity设置和不设置这个没什么区别,那这种模式的表现是什么样子的呢?

假设我们有一个”standard”模式的Activity,页面上有个按钮,点击这个按钮就会启动这个Activity自身,由于设置的是”standard”模式,每次启动这个Activity,就会创建这个Activity的新的实例,并依次放入Back Stack,点击一百次就会创建一百个这个Activity的实例。

这是一种典型的多实例实现,一个任务栈中可以有多个实例,每个实例也可以属于不同的任务栈。在这种模式下,谁启动了这个Activity,那么这个 Activity 就运行在启动它的那个Activity所在的栈中。比如 Activity A 启动了 Activity B(B 是标准模式),那么 B 就会进入到 A 所在的栈中。(存在特殊情况,singleInstance 启动模式的ac 去启动 standard模式的ac。standard模式的该ac不会放入singleInstance模式的ac所在的任务栈中。而是会放到standard 模式ac对应名为包名的任务栈里。因为singleInstance任务栈只有一个实例对象。所以它不能放入

当用ApplicationContext去启动standard模式的Activity的时候会报错,错误如下

Android 12 上 唯一的Launcher android launchmode_android

这是因为standard模式的Activity默认会进入启动它的Activity所属的任务栈中,但是由于非Activity类型的Context (如ApplicationContext)并没有所谓的任务栈,所以这就有问题了。解决这个问题的方法是为待启动Activity指定FLAG_ACTIVITY_NEW_TASK标记位,这样启动的时候就会为它创建一个新的任务栈,这个时候待启动 Activity 实际上是以 singleTask 模式启动的,

“standard”是最简单的模式,也符合我们正常的思维逻辑,所以最好理解,我用简陋的画图工具画了个图,凑合着看吧:

Android 12 上 唯一的Launcher android launchmode_Back_02

singleTop

栈顶复用模式。这种启动模式和标准模式区别不大,只有一点点不同。

我们已经知道,每个Activity的实例在Back Stack中存储,既然是个Stack数据结构,那么第一个压栈的实例我们叫做栈底实例,因为它将被后进来的实例压在最下面,最后被压入的实例,称作栈顶实例,因为它刚被压人栈中,暂时还没有其他实例在它之上,如果栈中只有一个实例,那这个实例既是栈底实例,也是栈顶实例。

明白了栈底和栈顶的概念,”singleTop” 就好理解了,当我们启动”singleTop” 模式的Activity时,系统会检查当前的Back Stack的栈顶实例是不是这个”singleTop” 模式Activity的实例,如果是的话,就不创建新的实例了,直接复用这个已经存在的栈顶实例,同时它的onNewIntent方法会被回调,通过此方法的参数我们可以取出当前请求的信息。需要注意的是,这个Activity的onCreate、onStart不会被系统调用,因为它并没有发生改变。如果新 Activity 的实例已存在但不是位于栈顶,那么新Activity仍然会重新重建。还拿我们上一个场景为例,如果这个Activity是”singleTop” 模式的,不论你怎么点按钮,Back Stack只会有一个实例,因为栈顶已经存在一个这样的实例,所以不会创建新的了。

如果上个例子不够明确,我们可以举个比较明显的例子,有两个Activity:ActivityA是标准模式,AvtivityB是”singleTop” 模式,完成一个Task,需要经过以下步骤:

启动ActivityA,在ActivityA中点击按钮启动ActivityB,

在ActiivtyB中点击按钮,再次启动ActivityB

根据之前的描述,当ActivityB的实例第一次被创建时,是位于栈顶的,第二次尝试创建ActivityB的实例之前,由于是”singleTop” 模式,并且栈顶已经有它的实例,就不会再创建新的,这个Task完成之后,Back Stack中只有一个ActivityA的实例和一个ActivityB的实例,简图如下:

Android 12 上 唯一的Launcher android launchmode_任务栈_03

singleTask

栈内复用模式

栈内复用模式。这是一种单实例模式(全局单例),在这种模式下,只要 Activity在一个栈中存在,那么多次启动此Activity都不会重新创建实例,和singleTop一样,系统也会回调其 onNewIntent。具体一点,当一个具有 singleTask 模式的Activity 请求启动后,比如Activity A,系统首先会寻找是否存在A想要的任务栈(具体见:taskAffinity的使用详解),如果不存在,就重新创建一个任务栈,然后创建 A 的实例后把 A 放到栈中。如果存在 A 所需的任务栈,这时要看 A是否在栈中有实例存在,如果有实例存在,那么系统就会把 A 调到栈顶(即把A之上的ac出栈),并调用它的onNewlntent 方法,如果实例不存在,就创建 A 的实例并把 A 压入栈中。举几个例子:

  • 比如目前任务栈S1 中的情况为ABC,这个时候 Activity D 以 singleTask 模式请求启动,其所需要的任务栈为 S2,由于 S2 和 D 的实例均不存在,所以系统会先创建任务栈 S2,然后再创建 D 的实例并将其入栈到 S2。
  • 另外一种情况,假设 D 所需的任务栈为 S1,其他情况如上面例子 1 所示,那么由于S1已经存在,所以系统会直接创建D的实例并将其入栈到S1.
  • 如果D所需的任务栈为S1,并且当前任务栈S1 的情况为ADBC,根据栈内复用的原则,此时 D 不会重新创建,系统会把 D 切换到栈顶并调用其 onNewIntent 方法,同时由于 singleTask 默认具有 clearTop 的效果,会导致栈内所有在D上面的 Activity全部出栈,于是最终S1中的情况为AD.这一点比较特殊,在后面还会对此种情况详细地分析。

第三种情况举例:还是继续上面的例子,ActivityC的启动模式为”singleTask” ,我们在Back Stack中原来的ActivityA和ActivityB实例之间插入一个ActivityC的实例,形成这样的Back Stack结构之后,如果再次尝试启动ActivityC时,由于ActivityC的实例已经存在,所以复用已经存在的实例,并且清除实例到栈顶的所有实例,所以ActivityB的实例被清除了,此时Back Stack中就只有ActivtyA和ActivtyC的实例。

Android 12 上 唯一的Launcher android launchmode_Back_04

singleInstance

“singleInstance”是最后一个启动模式,则是和其他三个模式都不同的。

singleInstance:单实例模式全局单例,。这是一种加强的 singleTask 模式,它除了具有singleTask模式的所有特性外,还加强了一点,那就是具有此种模式的Activity只能单独地位于一个任务栈中,换句话说,比如Activity A 是singleInstance模式,当A启动后,系统会为它创建一个新的任务栈,然后 A 独自在这个新的任务栈中,由于栈内复用的特性,后续的请求均不会创建新的Activity,除非这个独特的任务栈被系统销毁了。如果在启动这样的Activiyt时,已经存在了一个实例,那么会把它所在的任务调度到前台,重用这个实例。

简而言之,采用这种启动模式启动一个Activity,会创建一个新的栈存放该Activity的实例,并且这个栈中有且仅有这一个实例。

还是我们之前的例子,现在把ActivtyA,ActivityB和ActivtyC的实例依次放入Back Stack中,并将这个Back Stack编为1号,然后编写一个ActivityD并将其设置为”singleInstance”模式,此时,如果我们在ActivityC中启动ActivityD,那么ActiviyD的实例将不会位于1号Back Stack中,它将会在一个新的Back Stack中创建一个新的ActivityD实例,简图如下:

Android 12 上 唯一的Launcher android launchmode_任务栈_05

上面介绍了几种启动模式,这里需要指出一种情况,我们假设目前有2 个任务栈,前台任务栈的情况为AB,而后台任务栈的情况为CD,这里假设CD的启动模式均为singleTask。现在请求启动D,那么整个后台任务栈都会被切换到前台,这个时候整个后退列表变成了 ABCD。当用户按 back 键的时候,列表中的 Activity 会一一出栈,
如下图所示

关于前台任务栈和后台任务栈的理解:

  • 前台任务栈:当前处于前台并运行、可见并能和用户交互的Activity所在的栈。
  • 后台任务栈:当前处于后台、不可见也不能和用户交互的Activity所在的栈。

具体参考:
《Android开发艺术与探索》中对前台任务栈和后台任务栈的正确理解

singleInstancePerTask

官网解释:

Android 12 上 唯一的Launcher android launchmode_android_06

Android12 新增的一种启动模式,相当于singleTask和singleInstance的合体。

首次启动必创建新栈(相当于singleinstance的作用) 启动其他ac ,可以继续存 ,再启动该ac,之上的ac就出栈了(相当于singleTask)。
所以为为什么说singleInstancePerTask永远处于任务栈的根位置。

如何指定启动模式

有两种方法:

  1. 通过AndroidManifest为Activity指定启动模式如下所示:
<activity
            android:name=".MainActivity2"
            android:exported="true"
            android:launchMode="singleTask"></activity>
  1. 通过在intent中设置标志位来为Activity指定启动模式,代码如下:
Intent intent = new Intent(MainActivity4.this, MainActivity2.class);
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);

startActivity (intent);这两种方式都可以为 Activity指定启动模式,但是二者还是有区别的。

  • 首先,优先级上,第二种方式的优先级要高于第一种,当两种同时存在时,以第二种方式为准;
  • 其次,上述两种方式在限定范围上有所不同,比如,第一种方式无法直接为Activity设定FLAG-ACTIVITY_CLEAR_TOP标识,而第二种方式无法为Activity指定singleInstance模式。

使用场景

不同的启动模式,适用与应用中的不同应用场景。

standard

标准模式适用于大多数场景,因为在应用中,我们基本上可以允许用户同时进行多个任务,每个任务操作不同的数据,这样允许创建一个Activty的多个实例,例如新建邮件Activity,如果当前正在新建一个给张三的邮件,此时同时需要创建一个给李四的邮件,此时Activty就要使用标准模式,这样允许创建多个不同的实例,允许创建多封邮件。

singleTop

singleTop模式,由于其特点是检查栈顶实例,可以用这个特性,防止短时间创建多个实例,例如有个按钮,点击之后打开一个播放视频的Activty,如果用户短时间重复点击,不是singleTop模式的话,就会短时间在Back Stack中出现多个实例,而且每个实例的播放进度不一致,如果是singleTop模式的话,不论打开多少次都没有影响。

singleTask

这个模式,常用于那些有一定任务,且任务已经进行了一部分,但是忽然又去做别的事情了,等会儿回来还要继续任务的场景,还是播放那个视频的例子,如果正在播放的时候需要去打开新Activty搜索相关视频,搜索完毕又要回到播放页面,那这个页面就比较适合使用singleTask模式

singleInstance

这种模式较少使用,如果一定要在你的应用中使用这种启动模式,请提前考虑好是否有这个必要,另外,在做一些特殊场景下的应用,比如Launcher的主屏时,可能会使用得到。

参考