详解Activity 启动模式

173 阅读8分钟
原文链接: www.jianshu.com

前言

相信大家在Android日常开发中遇到疑难问题、概念模糊不清的时候,大多都会去csdn、stackoverflow、简书、稀土掘金里面查看学习大牛们博客。各位大牛大神总是把自己的经验分享出来,帮助我们这些需要帮助的人,我也作为受益者群众之一,由此表示衷心感谢!然而现在自己仔细想了一下,自己也应该写一写博客,一方面分享经验,另一方面总结需要熟知的技能点,在开始写博客之前甚是忐忑,主要是担心自己对Android的理解不够深入,到时候大家看了之后说——你这是什么玩意儿?简直是坐井观天不知所谓——那样就很尴尬了。不过又转念一想,我辈年轻人自当有一种一往无前的锐气,自我推赏岂不更好?并且大家都是文明人,总归更多的是理解与补充,而不是侮辱与谩骂。所以最终还是决定把自己开发中遇到的问题、解决方案和经验总结分享出来,希望能帮助到有需要的人。

好了,接下来进入正题,谈谈我对Activity启动模式的理解和认识。

简述Activity的启动模式

Activity是Android系统中的四大组件之一,它的主要功能是为人机交互提供界面。一般可以通过startActivity(Intent intent)方法来启动一个Activity。而一个程序一般由多个Activity组成,每个Activity都可以随意启动其它的Activity。每当一个Activity被启动,则前一个Activity就被停止。所以需要指定一个Activity为主界面,作为app进入的第一界面。一个程序中的所有启动的Activity都被放在一个Activity栈中,所以被停止的Activity并没有销毁,而在存于栈中。

正常情况下,新启动的Activity先被存放于栈中,然后获得输入焦点。在当前活动的Activity上点返回键,它被从栈中取出,然后销毁,然后上一个Activity被恢复。出于需求的可变性高,后进先出的模式不能完全满足业务逻辑,比如:某个新闻客户端的新闻内容页面,如果收到10个新闻推送,每次都新建一个Activity来显示新闻内容,退出的时候,需要连摁10次返回键才能退出,这样的交互是极其不友好的,而Android启动模式恰当使用正可解决此类问题。

启动模式(LaunchMode)在多个Activity跳转的过程中扮演着重要的角色,它可以决定是否生成新的Activity实例,是否重用已存在的Activity实例,是否和其他Activity实例公用一个task里。这里简单介绍一下task的概念,task是一个具有栈结构的对象,一个task可以管理多个Activity,启动一个应用,也就创建一个与之对应的task。

配置Activity的启动模式

在AndroidManifest.xml中需要配置的anctivity标签下配置android:launchMode属性为以下四种之一即可。

Activity的四种的启动模式

1.standard

standard模式是默认的启动模式,在未配置android:launchMode属性或者配置android:launchMode="standard"都为standard模式。首先我们新建一个demo来演示一下4种模式的区别。

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_code"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <Button
        android:id="@+id/bt_skip"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="跳转"/>

</LinearLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView tv_code = (TextView) findViewById(R.id.tv_code);
        tv_code.setText(this.toString());
        Button bt_skip = (Button) findViewById(R.id.bt_skip);
        bt_skip.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                click();
            }
        });
    }

    private void click(){
        Intent intent = new Intent(MainActivity.this,MainActivity.class);
        startActivity(intent);
    }

}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.fpt.launchmode_demo">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity"
            android:launchMode="standard">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

MainActivity界面中的TextView用于显示当前Activity实例的序列号,Button用于跳转到下一个Activity界面。 然后我们摁一次跳转按钮,将会出现下面的现象:

MainActivity.png MainActivity.png

由上看出虽然启动的都是MainActivity的实例,但是序列号不同。由此可知,在跳转时系统会在栈结构的顶部放一个MainActivity,当我们按下后退键时,才能看到原来的MainActivity实例。

2.singleTop

我们在上面的基础上为修改MainActivity属性android:launchMode="singleTop ",系统就会按照singleTop启动模式处理跳转行为。再启动软件摁一次跳转按钮。会出现:

MainActivity.png MainActivity.png

我们发现序列号没有变化,也就是没有生成新的Activity。接着我们继续修改代码:复制MainActivity在同一目录下名为Main2Activity,别忘了在AndroidManifest里注册Main2Activity!

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.fpt.launchmode_demo">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity"
            android:launchMode="singleTop">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".Main2Activity"/>
    </application>

</manifest>

修改MainActivity的click方法:

private void click(){
    Intent intent = new Intent(MainActivity.this,Main2Activity.class);
    startActivity(intent);
}

再修改Main2Activity的click方法:

private void click(){
    Intent intent = new Intent(Main2Activity.this,MainActivity.class);
    startActivity(intent);
}

然后我们点击跳转按钮2次,会出现以下情况:

MainActivity.png Main2Activity.png MainActivity.png

然后我们继续观察发现,诶?这不是跟standard模式一样吗?然而综合上边的实验一起来考虑,区别一下子就出来了。当从Main2Activity跳转到MainActivity时,系统发现存在有MainActivity实例,但不是位于栈顶,于是重新生成一个MainActivity实例。当从Main2ctivity跳转到MainActivity时,系统发现存在有MainActivity实例,且位于栈顶,则重复利用,不再生成新的实例。这就是singleTop启动模式。

3.singleTask

在上面的基础上我们修改MainActivity的属性android:launchMode=”singleTask”,然后点击3次跳转按钮,演示结果如下:

MainActivity.png Main2Activity.png MainActivity.png Main2Activity.png

在上面的过程中,我们发现MainActivity的序列号是不变的,Main2Activity的序列号却不是唯一的,说明从Main2Activity跳转到MainActivity时,没有生成新的实例,但是从MainActivity跳转到Main2Activity时生成了新的实例。实际上如果栈中如果发现有对应的Activity实例,则使此Activity实例之上的其他Activity实例统统出栈,使此Activity实例成为栈顶对象,当再加入任何Activity时都将会是新建的Activity。

4.singleInstance

修改MainActivity和Main2Activity中的tv_code.setText(this.toString())将其改为:
tv_code.setText(this.toString()+"\ncurrent task id:" + this.getTaskId());
点击2次跳转运行结果如下:

MainActivity.png Main2Activity.png

我们发现这两个Activity实例分别被放置在不同的栈结构中,也就是从MainActivity跳转到Main2Activity时,重新启用了一个新的栈结构,来放置Main2Activity实例,然后按下后退键,再次回到MainActivity原始栈结构。

singleInstance模式下,系统保证无论从哪个Task中启动目标Activity,只会创建一个目标Activity实例,并会使用一个全新的Task栈来装载该Activity实例。 当系统采用singleInstance模式启动目标Activity时可分为如下两种情况:
1.如果将要启动的目标Activity不存在,系统会先创建一个全新的Task,再创建目标Activity的实例,并将它加入新的Task的栈顶。
2.如果将要启动的目标Activity已经存在,无论它位于哪个应用程序中,无论它位于哪个Task中,系统将会把该Activity所在的Task转到前台,从而使用该Activity显示出来。

总结

standard模式。哪里需要调用我我就去哪里,可以多次实例化,可以几个相同的Activity重叠。

singleTop模式。可以多次实例化,但是不可以多个相同的Activity重叠,当堆栈的顶部为相同的Activity时,会调用onNewIntent函数。

singleTask模式。同一个应用中调用该Activity时,如果该Activity没有被实例化,会在本应用程序的Task内实例 化,如果已经实例化,会将Task中其上的Activity销毁后,调用onNewIntent;其它应用程序调用该Activity时,如果该 Activity没有被实例化,会创建新的Task并实例化后入栈,如果已经实例化,会销毁其上的Activity,并调用onNewIntent。一句 话,singleTask就是“独立门户”,在自己的Task里,并且启动时不允许其他Activity凌驾于自己之上。

singleInstance模式。加载该Activity时如果没有实例化,他会创建新的Task后,实例化入栈,如果已经存在,直接调用 onNewIntent,该Activity的Task中不允许启动其它的Activity,任何从该Activity启动的其他Activity都将被 放到其他task中,先检查是否有本应用的task,没有的话就创建。


如果本文对你有所帮助,请点赞!你的鼓励是我写作的最大动力!

不懂自律的人,不足以谈人生。

欢迎关注冯裴添的简书!

不定期分享关于安卓的文章,内容包含Android日常开发遇到中的问题、常用框架的介绍以及需要熟记的概念等等。