Android的启动模式

任务栈

在谈启动模式之前,让我们来了解下什么是任务栈?其实Android是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈就是任务栈,也被称为返回栈

当一个App启动时,如果当前环境不存在该App的任务栈,那么系统就会创建这个任务栈,此后,这个App所启动的Activity都将在这个任务栈中被管理。值得一提的是,一个任务栈中的Activity可以来自不同的App,同一个App中的Activity也可能不在同一个任务栈中。

任务栈是一种先进后出的栈结构,在默认情况下,每当我们启动了一个新的活动,系统就会创建活动的实例并把这个实例放入任务栈中,并处于栈顶位置。而每当我们按下返回键或调用活动的finish方法销毁一个活动时,处于栈顶的Activity就会出栈,直到栈空为止,而当栈中无任何Activity的时候,系统就会回收这个任务栈。当然,我们也可以修改这种默认的行为。就是通过在AndroidManifest文件中的属性android:launchMode来设置或者通过Intent的flag来设置。

Activity的LaunchMode

目前有四种启动模式,分别为:

  • standard:标准模式
  • singleTop: 栈顶复用模式
  • singleTask:栈内复用模式
  • singleInstance: 单例模式

standard

标准模式,也是系统默认的启动模式。如果不指定启动模式,则活动都会自动使用这种模式。这种启动模式每次启动一个Activity都会重新创建一个新的实例,不管这个实例是否存在。这时候被创建的活动onCreate,onStart,onResume都会被调用。在这种模式下,谁启动了这个Activity,那么这个Activity就运行在启动哪个Activity的栈中。比如ActivityA启动了ActivityB(B是标准模式),那么B就会进入到A所在的栈中。此时栈内情况为AB(B处于栈顶),假设在这种情况下再次启动ActivityB,则依旧会重新创建B的实例,那么栈内情况为ABB。因此想退出程序时必须连按3次Back键。

需注意,当我们用ApplicationContext去启动一个standard模式的Activity的时候会报错,这是因为standard模式的Activity默认会进入启动它的任务栈中,但是由于非Activity的Context并没有所谓的任务栈,故会出现报错。解决这个问题的方法是:为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,这样启动时就会为它创建一个新的任务栈,这个时候待启动的Activity实际上是以singleTask模式启动的

singleTop

栈顶复用模式。在这个模式下,启动新Activity时,系统会判断启动的Activity是否位于栈顶。如果是,那么此Activity不会被重新创建,因此不会调用此Activity的onCreate,onStart,onResume方法,取而代之的是会回调onNewIntent方法。如果Activity已存在实例但不位于栈顶,那还是得重新创建该Activity的实例。比如,当前栈内的情况为ABCD,其中A,B,C,D为四个Activity,A位于栈底,D位于栈顶,且A,D启动模式都为singleTop,这个时候再次启动D,则栈内情况还是ABCD;而启动A时,则栈内情况就变为:ABCDA

这种启动模式通常适用于接受信息后显示的界面,例如qq接到信息后弹出Activity,如果一次发来10条信息,总不能一次弹出10个Activity。

singleTask

栈内复用模式,这个模式跟singleTop模式类似。只要Activity在一个栈中存在,那么多次启动这个Activity都不会重新创建实例,同时回调onNewIntent方法。比如具有singleTask模式的ActivityA请求启动后,系统首先会先寻找是否存在Activity A所需的任务栈,如果不存在,则重新创建一个任务栈,然后创建A的实例并放到栈中。如果存在A所需的任务栈,这时候就得看看这个任务栈是否存在A的实例,如果有实例存在,那么系统就会把A调到栈顶同时调用onNewIntent方法,并将A以上的Activity都销毁,即出栈。如果实例不存在,则创建A的实例并把A压入栈中。举几个例子:

  • 比如目前任务栈S1的情况为ABC,这个时候D以singleTask模式请求启动,其所需的任务栈为S2,由于S2和D的实例都不存在,故系统首先会创建任务栈S2,并创建D的实例压入任务栈S2中。

  • 上面情况下假设这时候D所需的任务栈为S1,由于S1不存在D的实例,则直接创建D的实例然后压入S1栈中,这时S1栈内情况为:ABCD

  • 如果D所需的任务栈为S1,并且S1当前的情况为ADBC,根据栈内复用的原则,D不会被创建,系统会将D切换到栈顶并调用onNewIntent方法,同时singleTask默认具有clearTop的功能,故D上面的Activity都出栈,S1的情况为AD。

singleInstance

单实例模式。这是加强的singleTask模式,它具有singleTask所有的特性外,还加强了一点,就是具有这种模式的Activity只能单独的位于一个任务栈中。比如ActivityA是singleInstance模式,当A启动时,系统会为A建立一个新的任务栈,然后A独自在这个任务栈中,由于栈内复用的原则,后续的请求都不会创建A的实例,除非这个任务栈被系统销毁。

如果ActivityA中通过startActivityForResult()方法来启动另外一个singleTask或者singleInstance的活动ActivityB,那么在5.0以下系统将直接返回Activity.RESULT_CANCELED而不会再去等待返回,而5.0以上的版本将会重新创建B的实例,不管栈内是否有B的实例。故如果一定要传递,只能通过Intent绑定。

特殊情况

假设目前有2个任务栈,前台任务栈为AB,后台任务栈为CD,且CD的启动模式为singleTask。现在在B启动D,则整个后台任务栈都会被切换到前台,这时候整个任务列表为ABCD。当用户按Back键后,列表中的Activity会一一出栈。假如我们不是请求启动D,而是请求C,则情况就会有所不同。因为C是singleTask模式,故首先clearTop,将D出栈后位于栈顶,此时任务列表为ABC。

Activity的Flags

Activity的Flags很多,大部分情况下不需要为Activity指定标记位。不过在此之前先来了解下TaskAffinity

TaskAffinity

简单翻译就是任务相关性,这个参数标识了一个Activity所需要任务栈的名字,默认情况下所有Activity所需要的任务栈的名字为应用的包名。当然我们也可以在AndroidMenifest为每个Activity单独指定taskAffinity属性。taskAffinity属性主要和singleTask启动模式、allowTaskReparenting、标记位FLAG_ACTIVITY_NEW_TASK配对使用,其他情况下没意义。比如ActivityA启动了standard启动模式的ActivityB,即使使用了taskAffinity属性指定所需的任务栈,但是最终ActivityB被压入ActivityA的任务栈中,等同于设置无意义。

Flag启动模式

  • FLAG_ACTIVITY_NEW_TASK

    使用一个新的Task来启动一个Activity。使用该标志位启动的Activity并不一定会新建任务栈,这要参照启动Activity的taskAffinity属性(如果不指定默认为包名)。启动时首先会去寻找taskAffinity属性指定的任务栈,如果没有找到就会新建,如果找到了就会在该任务栈栈内做操作。所以在不指定taskAffinity的情况下,单独使用该标志位启动Activity,创建的Activity都会在与包名相同的那个任务栈中,也就是说不会新建任务栈。如果一个包含此activity的task已经运行了,比如后台栈,则直接把这个栈整体移动到前台,并保持栈中的状态不变,即栈中的Activity顺序不变。该Flag通常使用在从广播,服务启动Activity的场景。

    参考博客:

  • FLAG_ACTIVITY_SINGLE_TOP

    为Activity指定“singleTop”启动模式,效果和在XML指定该启动模式相同。

  • FLAG_ACTIVITY_CLEAR_TOP

    具有此标记位的Activity,当它启动时,在同一个任务栈中所有位于它上面的Activity都要出栈。singleTask启动模式默认具有此标记位的效果。如果被启动的Activity采用standard模式启动,那么连同它之上的Activity都要出栈,系统会创建Activity实例并放入栈顶。

  • FLAG_ACTIVITY_NO_HISTORY

    具有这个标记位的Activity不会保留在Activity栈中,比如栈内情况为AB,B以这种模式启动C,C启动D,则当前的Activity栈为ABD。

指定Activity启动模式

1.通过AndroidMenifest给Activity指定启动模式

1
2
3
4
<activity 
android:name=".MainActivity"
android:launchMode="singleTask"
>

2.通过在Intent中设置标志位来为Activity指定启动模式

1
2
3
Intent intent = new Intent(MainActivity.this,SecondActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

这两种方法都可以为Activity指定启动模式,但是还是有些区别。第二种的优先级明显高于第一种,当两者同时存在时,以第二种为准。

参考书籍:《Android艺术探索》《Android群英传》

-------------本文结束感谢您的阅读-------------