Android开发之Activity的启动模式
最后更新于:2022-04-01 09:46:36
黑发不知勤学早,白首方悔读书迟。——《劝学》
今天花了整个下午+晚上的的时间学习了Activity的启动模式,本来以为这个知识点很简单,但是在学习的过程中发现,Activity的启动模式并没有自己想象的那么简单,下面我们一起来看看这Activity的四种启动模式吧,如有疑问欢迎留言,如有谬误欢迎大家批评指正,谢谢
Activity的启动模式共有四种
1.standard
2.singleTop
3.singleTask
4.singleInstance
如图所示:

LaunchMode在多个Activity跳转的过程中扮演着重要的角色,它可以决定是否生成新的Activity实例,是否重用已存在的Activity实例,是否和其他Activity实例公用一个task里。这里简单介绍一下task的概念,task是一个具有栈结构的对象,一个task可以管理多个Activity,启动一个应用,也就创建一个与之对应的task。
下面我们就依次来说说这几种启动模式
1.standard
standard模式是Activity默认的启动模式,当我们在没有配置activity的launchMode时它就会按照standard方式去启动,
下面通过一个实例来解释下这种启动模式
FirstActivity代码如下:
~~~
package com.example.activitylauchmodepractice;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class FirstActivity extends Activity {
private Button btn_jumpToSecondActivity;
private TextView tv_showViewClass;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
tv_showViewClass=(TextView) findViewById(R.id.tv_showViewClass);
tv_showViewClass.setText(FirstActivity.this.toString());
btn_jumpToSecondActivity=(Button) findViewById(R.id.btn_jumpToSecondActivity);
btn_jumpToSecondActivity.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(FirstActivity.this,FirstActivity.class);
startActivity(intent);
}
});
}
}
~~~
启动后的界面

此时它所对应的任务栈如下

在此基础上我们点击按钮再次启动Activity此时的界面如下

此时的任务栈变化过程如下

我们再次点击按钮跳转到FirstActivity界面如下

此时的任务栈的变化过程如下

好了到这我们就可以分析一下了,在上述过程中我们点击了三次按钮它实例化了三个FirstActivity
这就是standard模式的特点:不管任务栈中有没有实例存在它都会实例化一个Activity
当我们点击返回按钮时它会依次把最上面的Activity出栈,上面的过程中一共实例化了三个Activity因此我们需要点击三次返回按钮应用才能退出。
2.singleTop
还用上面那个例子,此时我们给FirstActivity的属性指定为:android:launchMode="singleTop"
启动后的界面

此时的任务栈如下

我们接着点击按钮发现无论点击几次界面都没变说明它只实例化一次,此时的任务站始终是一个Activity此时点击一次返回键便可退出应用。
这是只有一个Activity的情况,下面我们说说多个Activity的情况
再来一个SecondActivity代码如下:
~~~
package com.example.activitylauchmodepractice;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class SecondActivity extends Activity {
private Button btn_jumpToFirstActivity_;
private TextView tv_showViewClass;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
tv_showViewClass=(TextView) findViewById(R.id.tv_showViewClass);
tv_showViewClass.setText(SecondActivity.this.toString());
btn_jumpToFirstActivity_=(Button) findViewById(R.id.btn_jumpToFirstActivity);
btn_jumpToFirstActivity_.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(SecondActivity.this, FirstActivity.class);
startActivity(intent);
}
});
}
}
~~~
把FirstActivity的代码稍作修改
~~~
btn_jumpToSecondActivity.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
startActivity(intent);
}
});
~~~
在上面的Activity中FirstActivity的启动模式是singleTop,SecondActivity的启动模式是默认的standard,做好准备之后我们来做操作
启动后的界面如下:

此时的任务栈如下

在此基础上我们点击一次按钮界面如下

此时的任务栈的变化如下

在SecondActivity中再次点击按钮的界面如下

此时的任务栈的变化如下

从上面的过程中我们看到再次从SecondActivity跳转到FirstActivity时两次的FirstActivity的序列号不同说明又重新生成了一个FirstActivity
singleTop模式的特点:当从SecondActivity跳转到FirstActivity时,系统发现存在有FirstActivity实例,但不是位于栈顶,于是重新生成一个实例。这就是singleTop启动模式的特点,即如果发现有对应的Activity实例正位于栈顶,则重复利用,不再生成新的实例,如果栈顶没有对应的Activity则实例化一个。
该模式和standard模式基本一致,但有一点不同,当将要被启动的Activity已经位于Task栈顶时,系统不会重新创建目标Activity的实例,而是直接复用Task栈顶的Activity
3.singleTask(内单例模式)
我们还是建立在上面的基础上,把FirstActivity的启动模式改为android:launchMode="singleTask"
启动后我们点击三次跳转按钮界面如下图所示




在上面的过程中,FirstActivity的序列号是不变的,SecondActivity的序列号是改变的,说明从SecondActivity跳转到FirstActivity时,没有生成新的实例,但是从FirstActivity跳转到SecondActivity时生成了新的实例。
在此过程中任务栈的变化过程如下

在上面的跳转过程中当从SecondActivity跳转到FirstActivity时发现SecondActivity消失了,这就是singleTask的特点在这个跳转过程中系统发现有存在的FirstActivity实例,于是不再生成新的实例,而是将FirstActivity之上的Activity实例统统出栈,将FirstActivity变为栈顶对象,显示到幕前。
singleTask模式的特点:如果发现有对应的Activity实例,则使此Activity实例之上的其他Activity实例统统出栈,使此Activity实例成为栈顶对象,显示到幕前。
Activity在同一个Task内只有一个实例. 当系统采用singleTask模式加载Activity时,又分为以下三种情况:
(1)如果将要启动的Activity不存在,那么系统将会创建该实例,并将其加入Task栈顶
(2)如果将要启动的Activity已存在,且存在栈顶,那么此时与singleTop模式的行为相同
(3)如果将要启动的Activity存在但是没有位于栈顶,那么 此时系统会把位于该Activity上面的所有其他Activity全部移出Task,从而使得该目标Activity位于栈顶
4.singleInstance(全局单例模式)
这种模式是四种模式中最难理解的一种模式,因为这种模式会重新创建一个新的任务栈,将Activity放置于这个栈中,并保证其它的Activity不再进入,由于这种模式比较复杂,我们首先来说说它的原理,然后再结合实例进一步的理解,假如现在用户打开了两个应用分别为应用1和应用2,应用1和应用2的任务栈假如如下图左边,此时在应用1中想打开Activity3,这时应用1和应用2就会共享Activity3的引用,
注意:之所以能公用Activity的引用是以应用2中的Activity设置了LaunchMode="singleInstance"为前提的。

由于这种模式比较复杂,我们举两个不同例子,来说明不同的问题
举例一、
还是上面的两个Activity。FirstActivity和SecondActivity在两个Activityt跳转的过程中我们打印两个Activity所在的任务栈的ID
对以上两个Activity做如下修改,并且把SecondActivity的启动模式改为singleInstance
~~~
tv_showViewClass=(TextView) findViewById(R.id.tv_showViewClass);
~~~
~~~
tv_showViewClass.setText("当前Activity:"+"\n"+this.toString()+"\n"+"当前TaskId:"+this.getTaskId());
~~~
启动后和点击跳转按钮后的界面如下


我们发现两个Activity的TaskId是不同的,说明这两个Activity是位于不同的任务栈中的,从而证实了为SecondActivity重新建立了一个任务栈,可能有的朋友会问,在这个时候如果点击返回按钮它们是怎么出栈的呢?假如现在我们点击返回按钮它的任务栈的变化如下图

假如我们在SecondActivity中点击按钮跳转到FirstActivity然后会以怎样的方式退出应用呢?此时 它的任务栈的变化如下

图中下半部分显示的在SecondActivity中再次跳转到FirstActivity,这个时候系统会在原始栈结构中生成一个FirstActivity实例,然后回退两次,注意,并没有退出,而是回到了SecondActivity,为什么呢?是因为从SecondActivity跳转到FirstActivity的时候,我们的起点变成了SecondActivity实例所在的栈结构,这样一来,我们需要“回归”到这个栈结构。
由于singleInstance比较复杂些,我们再来举一个两个应用的例子为了和上面的例子混淆,我们重新写两个应用
第一个App中有两个Activity分别为Activity1和ShareActivity
第二个App中有一个Activity2我们在这个App中启动第一个App的ShareActivity
第一个App的Activity源码如下
~~~
package com.example.activitylauchmodepractice;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class Activity1 extends Activity {
private Button btn_jumpToSecondActivity;
private TextView tv_showViewClass;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
tv_showViewClass=(TextView) findViewById(R.id.tv_showViewClass);
tv_showViewClass.setText("当前Activity:"+"\n"+this.toString()+"\n"+"当前TaskId:"+this.getTaskId());
btn_jumpToSecondActivity=(Button) findViewById(R.id.btn_jumpToSharedActivity);
btn_jumpToSecondActivity.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(Activity1.this,ShareActivity.class);
startActivity(intent);
}
});
}
}
~~~
ShareActivity源码
~~~
package com.example.activitylauchmodepractice;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class ShareActivity extends Activity {
private Button btn_jump;
private TextView tv_showViewClass;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
tv_showViewClass=(TextView) findViewById(R.id.tv_showViewClass);
tv_showViewClass.setText("当前Activity:"+"\n"+this.toString()+"\n"+"当前TaskId:"+this.getTaskId());
}
}
~~~
我们要特别注意ShareActivity在清单文件中的配置如下
~~~
<activity
android:name="com.example.activitylauchmodepractice.ShareActivity"
android:launchMode="singleInstance">
<intent-filter>
<action android:name="SecondActivity_action"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
~~~
我们需要配置ShareActivity的action在另一个应用中启动时会用到
第二个App中的Activity2的源码如下
~~~
package com.example.singleinstancepractice;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity {
private Button btn_jump;
private TextView tv_showTaskId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_showTaskId=(TextView) findViewById(R.id.tv_showTaskId);
tv_showTaskId.setText("当前Activity:"+"\n"+this.toString()+"\n"+"当前TaskId:"+this.getTaskId());
btn_jump=(Button) findViewById(R.id.btn_jump);
btn_jump.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent =new Intent();
intent.setAction("SecondActivity_action");
startActivity(intent);
}
});
}
}
~~~
当我们在第一个App中打开ShareActivity后再按后退键回到原来界面时,ShareActivity做为一个独立的个体存在,如果这时我们在第二个App中打开ShareActivity无需创建新的ShareActivity实例即可看到结果,因为系统会自动查找,存在则直接利用。原理图如下:

注意:上图是建立在第一个App运行到手机上时点击第二个App上的跳转按钮跳转到ShareActivity的情况的基础上的变化过程。