转自:http://blog.csdn.net/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
,其他情况如上面例子1所示,那么由于S1
已经存在,所以系统会直接创建S1
的实例并将其入栈到D
。S1
- 如果
所需的任务栈为D
,并且当前任务栈S1的情况为ADBC,根据栈内复用的原则,此时S1
不会重新创建,系统会把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
,看看效果。关于这个过程,原理图如下: