转自:http://blog.youkuaiyun.com/liuhe688/article/details/6754323/
我们今天要讲的是Activity的四种launchMode。
launchMode在多个Activity跳转的过程中扮演着重要的角色,它可以决定是否生成新的Activity实例,是否重用已存在的Activity实例,是否和其他Activity实例公用一个task里。这里简单介绍一下task的概念,task是一个具有栈结构的对象,一个task可以管理多个Activity,启动一个应用,也就创建一个与之对应的task。
Activity一共有以下四种launchMode:
standard
singleTop
singleTask
singleInstance
我们可以在AndroidManifest.xml配置<activity>
的Android:launchMode
属性为以上四种之一即可。
下面我们结合实例一一介绍这四种lanchMode
:
1.standard
standard
模式是默认的启动模式,不用为配置android:launchMode
属性即可,当然也可以指定值为standard
。
每次启动一个Activity
就会重新创建一个新的实例,不管这个实例是否已经存在。被创建的实例的生命周期符合典型情况下Activity
的生命周期,它的onCreate
,onStart
,onResume
都会被调用。这是一种典型的多实例实现,一个任务栈中可以有多个实例,每个实例也可以属于不同的任务栈。在这种模式下,谁启动了这个Activity,那么这个Activity就运行在启动它的那个Activity所在的任务栈中。比如Activity A
启动了Activity B
(B是standard
模式),那么B就会进入A所在的栈中。不知大家是否注意到,当我们用ApplicationContext
去启动standard
模式的Activity
的时候会报错,错误如下:
相信这句话大家一定不会陌生,这是因为standard模式的Activity默认会进入启动它的Activity所属的任务栈中,但是由于非Activity类型的Context(如ApplicationContext)并没有所谓的任务栈,所以这就有问题了。解决这个问题的方法是为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK
标记位,这样启动的时候就会为它创建一个新的任务栈,这个时候启动Activity
实际上是以singleTask
模式启动的,大家可以仔细体会。
我们将会一个Activity
,命名为FirstActivity
,来演示一下标准的启动模式。FirstActivity
代码如下:
public class FirstActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first);
TextView textView = (TextView) findViewById(R.id.textView);
textView.setText(this.toString());
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(FirstActivity.this, FirstActivity.class);
startActivity(intent);
}
});
}
}
我们FirstActivity
界面中的TextView
用于显示当前Activity
实例的序列号,Button
用于跳转到下一个FirstActivity
界面。
然后我们连续点击几次按钮,将会出现下面的现象:



我们注意到都是FirstActivity
的实例,但序列号不同,并且我们需要连续按后退键两次,才能回到第一个FristActivity
。standard
模式的原理如下图所示:

如图所示,每次跳转系统都会在task
中生成一个新的FirstActivity
实例,并且放于栈结构的顶部,当我们按下后退键时,才能看到原来的FirstActivity
实例。
这就是standard
启动模式,不管有没有已存在的实例,都生成新的实例。
2.singleTop
栈顶复用模式
在这种模式下,如果新Activity已经位于任务栈的栈顶,那么此Activity
不会被重新创建,同时它的onNewIntent
方法会被回调,通过此方法的参数我们可以取出当前请求的信息。需要注意的是,这个Activity
的onCreate
、onStart
不会被系统调用,因为它并没有发生改变。如果新Activity
的实例已存在但不是位于栈顶,那么新Activity
仍然会重新创建。举个例子,假如目前栈内情况为ABCD,其中ABCD为四个Activity
,A位于栈底,D位于栈顶,这个时候假设要再次启动D,如果D的启动模式为singleTop
,那么栈内的情况仍然为ABCD,如果D的启动模式为standard
,那么由于D被重新创建,导致栈内的情况就变为ABCDD。
我们在上面的基础上为指定属性android:launchMode="singleTop"
,系统就会按照singleTop
启动模式处理跳转行为。我们重复上面几个动作,将会出现下面的现象:



我们看到这个结果跟standard
有所不同,三个序列号是相同的,也就是说使用的都是同一个FirstActivity
实例;如果按一下后退键,程序立即退出,说明当前栈结构中只有一个Activity
实例。singleTop
模式的原理如下图所示:

正如上图所示,跳转时系统会先在栈结构中寻找是否有一个FirstActivity
实例正位于栈顶,如果有则不再生成新的,而是直接使用。也许朋友们会有疑问,我只看到栈内只有一个Activity
,如果是多个Activity
怎么办,如果不是在栈顶会如何?我们接下来再通过一个示例来证实一下大家的疑问。
我们再新建一个Activity
命名为SecondActivity
,如下:
public class SecondActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.second);
TextView textView = (TextView) findViewById(R.id.textView);
textView.setText(this.toString());
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(SecondActivity.this, FirstActivity.class);
startActivity(intent);
}
});
}
}
然后将之前的FirstActivity
跳转代码改为:
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
startActivity(intent);
是的,FirstActivity
会跳转到SecondActivity
,SecondActivity
又会跳转到FirstActivity
。演示结果如下:



我们看到,两个FirstActivity
的序列号是不同的,证明从SecondActivity
跳转到FirstActivity
时生成了新的FirstActivity
实例。原理图如下:

我们看到,当从SecondActivity
跳转到FirstActivity
时,系统发现存在有FirstActivity
实例,但不是位于栈顶,于是重新生成一个实例。
这就是singleTop
启动模式,如果发现有对应的Activity
实例正位于栈顶,则重复利用,不再生成新的实例。
3.singleTask
栈内复用模式 clearTop
这是一种单实例模式,只要Activity
在一个栈中存在,那么多次启动此Activity
都不会重新创建实例,和singleTop
一样,系统也会回调其onNewIntent
。具体一点,当一个具有singleTask
模式的Activity
请求启动后,比如Activity A
,系统首先会寻找是否存在A
想要的任务栈(TaskAffinity参数可以指定要启动的Activity所在的任务栈),如果不存在,就重新创建一个任务栈,然后创建A
的实例后把A
放到栈中。如果存在A
所需的任务栈,这时要看A
是否在栈中有实例存在,如果有实例存在,那么系统就会把A
调到栈顶并调用它的onNewIntent
方法,如果实例不存在,就创建A
的实例并把A
压入栈中,举几个例子:
目前任务栈
S1
中的情况为ABC
,这个时候Activity D
以singleTask
模式请求启动,其所需要的任务栈为S2
,由于S2
和D
的实例均不存在,所以系统会先创建任务栈S2
,然后再创建D
的实例并将其入栈到S1
。另外一种情况,假设
D
所需的任务栈为S1
,其他情况如上面例子1所示,那么由于S1
已经存在,所以系统会直接创建D
的实例并将其入栈到S1
。如果
D
所需的任务栈为S1
,并且当前任务栈S1的情况为ADBC,根据栈内复用的原则,此时D
不会重新创建,系统会把D
切换到栈顶并调用其onNewIntent
方法,同时由于singleTask
默认具有clearTop
的效果,会导致栈内所有在D
上面的Activity
全部出栈,于是最终S1
中的情况为AD
,这一点比较特殊,在后面还会对此种情况详细地分析。
何谓某个Activity所需的任务栈?
这要从一个参数说起:TaskAffinity,可以翻译为任务相关性。这个参数标识了一个Activity所需要的任务栈的名字,默认情况下,所有Activity所需的任务栈的名字为应用的包名。当然我们可以为每个Activity都单独指定TaskAffinity属性,这个属性值必须不能和包名相同,否则就相当于没有指定。TaskAffinity属性主要和singleTask启动模式或者allowTaskReparenting属性配对使用,在其他情况下没有意义。另外,任务栈分为前台任务栈和后台任务栈,后台任务栈中的Activity处于暂停状态,用户可以通过切换将后台任务栈再次调到前台。
当TaskAffinity和singleTask启动模式配对使用的时候,它是具有该模式的Activity的目前任务栈的名字,待启动的Activity会运行在名字和TaskAffinity相同的任务栈中。
在上面的基础上我们修改FirstActivity
的属性android:launchMode="singleTask"
。演示的结果如下:



我们注意到,在上面的过程中,FirstActivity
的序列号是不变的,SecondActivity
的序列号却不是唯一的,说明从SecondActivity
跳转到FirstActivity
时,没有生成新的实例,但是从FirstActivity
跳转到SecondActivity
时生成了新的实例。singleTask
模式的原理图如下图所示:

在图中的下半部分是SecondActivity
跳转到FirstActivity后的栈结构变化的结果,我们注意到,SecondActivity
消失了,没错,在这个跳转过程中系统发现有存在的FirstActivity
实例,于是不再生成新的实例,而是将FirstActivity
之上的Activity实例统统出栈,将FirstActivity
变为栈顶对象,显示到幕前。也许朋友们有疑问,如果将SecondActivity
也设置为singleTask
模式,那么SecondActivity
实例是不是可以唯一呢?在我们这个示例中是不可能的,因为每次从SecondActivity
跳转到FirstActivity
时,SecondActivity
实例都被迫出栈,下次等FirstActivity
跳转到SecondActivity
时,找不到存在的SecondActivity
实例,于是必须生成新的实例。但是如果我们有ThirdActivity,让SecondActivity
和ThirdActivity
互相跳转,那么SecondActivity
实例就可以保证唯一。
这就是singleTask
模式,如果发现有对应的Activity实例,则使此Activity
实例之上的其他Activity
实例统统出栈,使此Activity
实例成为栈顶对象,显示到幕前。
4.singleInstance
这种启动模式比较特殊,因为它会启用一个新的栈结构,将Acitvity
放置于这个新的栈结构中,并保证不再有其他Activity实例进入。
我们修改FirstActivity
的launchMode="standard"
,SecondActivity
的launchMode="singleInstance"
,由于涉及到了多个栈结构,我们需要在每个Activity
中显示当前栈结构的id
,所以我们为每个Activity
添加如下代码:
TextView taskIdView = (TextView) findViewById(R.id.taskIdView);
taskIdView.setText("current task id: " + this.getTaskId());
然后我们再演示一下这个流程:


我们发现这两个Activity
实例分别被放置在不同的栈结构中,关于singleInstance
的原理图如下:

我们看到从FirstActivity
跳转到SecondActivity
时,重新启用了一个新的栈结构,来放置SecondActivity
实例,然后按下后退键,再次回到原始栈结构;图中下半部分显示的在SecondActivity
中再次跳转到FirstActivity
,这个时候系统会在原始栈结构中生成一个FirstActivity
实例,然后回退两次,注意,并没有退出,而是回到了SecondActivity
,为什么呢?是因为从SecondActivity
跳转到FirstActivity
的时候,我们的起点变成了SecondActivity
实例所在的栈结构,这样一来,我们需要“回归”到这个栈结构。
如果我们修改FirstActivity
的launchMode
值为singleTop
、singleTask
、singleInstance
中的任意一个,流程将会如图所示:

singleInstance
启动模式可能是最复杂的一种模式,为了帮助大家理解,我举一个例子,假如我们有一个share
应用,其中的ShareActivity
是入口Activity
,也是可供其他应用调用的Activity
,我们把这个Activity
的启动模式设置为singleInstance
,然后在其他应用中调用。我们编辑ShareActivity
的配置:
<activity android:name=".ShareActivity" android:launchMode="singleInstance">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SINGLE_INSTANCE_SHARE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
然后我们在其他应用中这样启动该Activity
:
Intent intent = new Intent("android.intent.action.SINGLE_INSTANCE_SHARE");
startActivity(intent);
当我们打开ShareActivity
后再按后退键回到原来界面时,ShareActivity
做为一个独立的个体存在,如果这时我们打开share
应用,无需创建新的ShareActivity
实例即可看到结果,因为系统会自动查找,存在则直接利用。大家可以在ShareActivity
中打印一下taskId
,看看效果。关于这个过程,原理图如下:
