
启动模式一共有4种,分别是standard、singleTop、singleTask和singleInstance,可以在AndroidManifest.xml中通过给<activity>标签指定android:launchMode属性来选择启动模式。
standard
standard是Activity默认的启动模式,在不进行显式指定的情况下,所有Activity都会自动使用这种启动模式。在standard模式下,每当启动一个新的Activity,它就会在返回栈中入栈,并处于栈顶的位置。对于使用standard模式的Activity,系统不会在乎这个Activity是否已经在返回栈中存在,每次启动都会创建一个该Activity的新实例。
MainActivity:
package com.example.launchermodetest;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_main);
Log.d("MainActivity", this.toString());
Button buttonStartMain = findViewById(R.id.startMainActivity);
buttonStartMain.setOnClickListener(view -> {
Intent intent = new Intent(MainActivity.this, MainActivity.class);
startActivity(intent);
});
}
}
layout_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/startMainActivity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="start main activity again"
/>
</LinearLayout>
连续点击MainActivity的startMainActivity中的按钮,查看打印,通过 this.toString() 可以发现每次点击都会创建一个MainActivity活动实例:

singleTop
当Activity的启动模式指定为singleTop,在启动Activity时如果发现返回栈的栈顶已经是该Activity,则认为可以直接使用它,不会再创建新的Activity实例。如果当活动未处于栈顶时,仍然会创建出一个新的活动。
在AndroidManifest.xml中MainActivity标签加入android:launchMode=“singleTop”,此时无论按多少次按钮都不会再次创建MainActivity活动,因为目前已经有一个MainActivity的活动处于栈顶的位置。此时MainActivity也只有一个实例,仅按一次Back键就可以退出程序。

然后再观察MainActivity不在栈顶的情况,添加FirstActivity,在MainActivity中打开FirstActivity,再从FirstActivity中返回MainActivity:
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.launchermodetest">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.LauncherModeTest">
<activity
android:name=".FirstActivity"
android:exported="false" />
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
MainActivity:
package com.example.launchermodetest;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_main);
Log.d("MainActivity", this.toString());
Button buttonStartMain = findViewById(R.id.startMainActivity);
buttonStartMain.setOnClickListener(view -> {
Intent intent = new Intent(MainActivity.this, MainActivity.class);
startActivity(intent);
});
Button buttonStartFirst = findViewById(R.id.startFirstActivity);
buttonStartFirst.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, FirstActivity.class);
startActivity(intent);
}
});
}
}
FirstActivity:
package com.example.launchermodetest;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
public class FirstActivity extends AppCompatActivity {
private static final String TAG = "FirstActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_first);
Log.d(TAG, this.toString());
Button buttonReturnMain = findViewById(R.id.returnMainActivity);
buttonReturnMain.setOnClickListener(view -> {
Intent intent = new Intent(FirstActivity.this, MainActivity.class);
startActivity(intent);
});
}
}
layout_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/startMainActivity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="start main activity again"
/>
<Button
android:id="@+id/startFirstActivity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start First Activity"
android:textAllCaps="false"
/>
</LinearLayout>
layout_first.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Return Main Activity"
android:id="@+id/returnMainActivity"
/>
</LinearLayout>
先启动MainActivity,从Main进入FirstActivity,再从First点击Return Main Activity按钮回到MainActivity,从打印可以看出MainActivity又重新创建了一个新的实例。返回栈此时存在三个Activity,你需要按三下返回才能退出。

singleTask
有没有什么办法可以让某个Activity在整个应用程序的上下文中只存在一个实例呢?这就要借助singleTask模式来实现了。当Activity的启动模式指定为singleTask,每次启动该Activity时,系统首先会在返回栈中检查是否存在该Activity的实例,如果发现已经存在则直接使用该实例,并把在这个Activity之上的所有其他Activity统统出栈,如果没有发现就会创建一个新的Activity实例。
在AndroidManifest.xml文件中的MainActivity标签加入android:launchMode=“singleTask”,再重复singTop模式中的操作,会发现MainActivity并不会创建新的实例,而是调用之前实例的onRestart()方法。此时返回栈中仅剩一个MainActivity实例,只需按一下Back即可退出。

singleInstance
不同于以上3种启动模式,指定为singleInstance模式的Activity会启用一个新的返回栈来管理这个Activity(其实如果singleTask模式指定了不同的taskAffinity,也会启动一个新的返回栈)。
那么这样做有什么意义呢?想象以下场景,假设我们的程序中有一个Activity是允许其他程序调用的,如果想实现其他程序和我们的程序可以共享这个Activity的实例,应该如何实现呢?使用前面3种启动模式肯定是做不到的,因为每个应用程序都会有自己的返回栈,同一个Activity在不同的返回栈中入栈时必然创建了新的实例。而使用singleInstance模式就可以解决这个问题,在这种模式下,会有一个单独的返回栈来管理这个Activity,不管是哪个应用程序来访问这个Activity,都共用同一个返回栈,也就解决了共享Activity实例的问题。
eg: 在MainActivity中启动FirstActivity,再在FirstActivity中启动SecondActivity中:
AndroidManifest.xml
指定FirstActivity为singleInstance模式
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.launchermodetest">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.LauncherModeTest">
<activity
android:name=".SecondActivity"
android:exported="false" />
<activity
android:name=".FirstActivity"
android:exported="false"
android:launchMode="singleInstance"/>
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
MainActivity
package com.example.launchermodetest;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_main);
Log.d(TAG, "Task id is " + this.getTaskId());
Button buttonStartMain = findViewById(R.id.startMainActivity);
buttonStartMain.setOnClickListener(view -> {
Intent intent = new Intent(MainActivity.this, MainActivity.class);
startActivity(intent);
});
Button buttonStartFirst = findViewById(R.id.startFirstActivity);
buttonStartFirst.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, FirstActivity.class);
startActivity(intent);
}
});
}
@Override
protected void onRestart() {
super.onRestart();
Log.d(TAG, this.toString() + " onRestart");
}
}
FirstActivity
package com.example.launchermodetest;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
public class FirstActivity extends AppCompatActivity {
private static final String TAG = "FirstActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_first);
Log.d(TAG, "Task id is " + this.getTaskId());
Button buttonReturnMain = findViewById(R.id.returnMainActivity);
buttonReturnMain.setOnClickListener(view -> {
Intent intent = new Intent(FirstActivity.this, MainActivity.class);
startActivity(intent);
});
Button buttonStartSecond = findViewById(R.id.startSecondActivity);
buttonStartSecond.setOnClickListener(view -> {
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
startActivity(intent);
});
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
}
}
SecondActivity
package com.example.launchermodetest;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("SecondActivity", "Task id is " + this.getTaskId());
setContentView(R.layout.layout_second);
}
}
layout_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/startMainActivity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="start main activity again"
/>
<Button
android:id="@+id/startFirstActivity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start First Activity"
android:textAllCaps="false"
/>
</LinearLayout>
layout_first
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Return Main Activity"
android:id="@+id/returnMainActivity"
/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start Second Activity"
android:id="@+id/startSecondActivity"
/>
</LinearLayout>
layout_second
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="This is SecondActivity"
/>
</LinearLayout>
首先点击MainActivity中的startFirstActivity按钮,再点击FirstActivity中的startSecondActivity按钮,再在SecondActivity中点击按钮,通过activity对象getTaskId()查看Task的id,可以看出MainActivity和SecondActivity的Task id 是47,而FirstActivity的Task id是48,所以FirstActivity确实在单独的一个栈中。

这时点击Back键会先从SecondActivity回到MainActivity中,再点击Back键会回到FirstActivity中,singleInstance的模式示意图如下。

本文详细介绍了Android中Activity的四种启动模式:standard, singleTop, singleTask和singleInstance,通过实例展示了它们在启动和回退栈中的行为差异,帮助理解如何控制Activity的生命周期和实例管理。


被折叠的 条评论
为什么被折叠?



