概述

Activity 的啟動模式主要包含 4 種:
Standard(標準模式,預設),SingleTop(棧頂重用),SingleTask(棧內重用),
SingleInstance(單例模式)。
Standard 每次啟動都會建立新的 Activity 實體,其它 3 種會根據情況選擇新增或
是重用 Activity 實體。
建立新實體的生命週期為 onCreate -> onStart -> onResume
重用舊實體的生命週期為 onNewIntent -> onResume

如何使用啟動模式A.

在 AndroidManifest.xml 中可以透過設定 launchMode 設定不同的啟動模式
使用 taskAffinity 並加入套件名稱便可以設定不同的栈,預設棧為目前套件名
稱,taskAffinity 只能用於 SingleTop, SingleTask, SingleInstance

1.Standard(標準):

啟動 Activity 的預設模式,當使用 Standard 每次都會產生新的實體。

2.SingleTop(棧頂重用):

當要啟動的 Activity 在棧頂,則重用該實體。若不在棧頂則建立新的實體(建立完成後該實體位於棧頂)。
啟動的生命週期:
D: onCreate
D: onStart
D: onResume
重用的生命週期
D: onPause
D: onNewIntent
D: onResume

3.SingleTask(棧內重用)

A.沒有設定 taskAffinity (情況較為簡單):
1.當要啟動的 Activity 位於棧頂時,重用該實體,不會建立新的實體。
重用的生命週期
D: onPause
D: onNewIntent
D: onResume
2.當要啟動的 Activity 位於棧內時,會重用該實體並清除該實體上方的所有其它Activity。(此時該啟動的 Activity 位於棧頂)。
重用的生命週期
D: onPause
D: onNewIntent
D: onResume
B.有設定 taskAffinity:(注意 taskAffinity 只能用於 SingleTop, SingleTask, SingleInstance)
當設定 taskAffinity 時有幾個重點:
1.被啟動的 Activity 會跟著啟動的 Activity 處於同一個棧中。
2.有一些操作會切換棧,如按下 home key,再點擊 app icon 會重用預設栈
3.當從棧退出 Activity,會優先退出同一個棧的 Activity,而當退完同一個棧的所有 Activity,會切換回桌面,不會啟動另一個棧。
以範例說明:
FirstActivity 啟動 SecondActivity,SecondActivity 啟動 ThirdActivity(SingleTask, 不同棧,棧名為 another.stack))啟動 FourthActivity (預設)
此時 FirstActivity 和 SecondActivity 位於預設棧,而 ThirdActivity 和 FourthActivity 位於 another.stack 棧。顯示在螢幕最上方的為 FourthActivity。
2個走向:
1.按下 home key 再點擊 app icon,此時會啟動預設棧,因此顯示在螢幕上的為 SecondActivity,點擊 back key(SecondActivity 銷毀),顯示 FirstActivity,點擊 back key(FirstActivity 銷毀),回到桌面。
(注意此時 ThirdActivity 和 FourthActivity 還未銷毀,保存在記憶體中)
2.按下 back key(FourthActivity 銷毀),顯示 ThirdActivity,按下 back key(ThirdActivity 銷毀),顯示 SecondActivity,按下 back key( SecondActivity 銷毀),顯示 FirstActivity,按下 back key(FirstActivity 銷毀),回到桌面。

重點:

1.若設定新的任務棧,有些操作會啟動不同的任務棧,這些操作可能會讓使用者感覺混淆。
2.當啟動新的 Activity 而產生新的任務棧時,畫面切換的動畫會不同。

4.SingleInstance(單例實體)

啟動設定為單例實體的 Activity 時,會建立單獨的栈且該棧無法加入其它的 Activiity,之後都會重用該實體。
以範例說明:
FirstActivity 啟動 SecondActivity,SecondActivity 啟動 FifthActivity(單例實體),FifthActivity 啟動 FourthActivity。
(此時有 2 個棧,預設棧有 FirstActivity,SecondActivity,FourthActivity。另一個棧只有 FifthActivity)
2個走向:
1.按下 back key(FourthActivity 銷毀),顯示 SecondActivity
(注意這裡因為出棧的規則是同一個棧的 Activity 會先出完,因此顯示的是 SecondActivity,而不是 FifthActivity),按下 back key(SecondActivity 銷毀),顯示 FirstActivity,按下 back key(FirstActivity 銷毀),顯示 FifthActivity,按下 back  key(FifthActivity 銷毀),回到桌面。
2.FourthActivity 啟動 FifthActivity,FifthActivity 啟動 FourthActivity,按下 back key(FourthActivity 銷毀),顯示 FourthActivity,按下 back key(FourthActivity 銷毀),顯示 SecondActivity,按下 back key(SecondActivity 銷毀),顯示FirstActivity,按下 back key(FirstActivity 銷毀),顯示 FifthActivity,按下back key(FifthActivity 銷毀),回到桌面。

重點:

1.設定為單例實體之後一定都會重用實體。
2.單例實體的 Activity 啟動另一個 Activity 後,被啟動的 Activity 不會加入到單例實體的棧中,而會加到預設棧。
3.出棧規則也是先出完同一個棧,待同一個棧的所有的 Activity 出完之後再切換到另一個棧。
 

如何使用啟動模式B.

呼叫 startActivity 方法時使用參數 intent 設定啟動標幟(flag),如下

intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

常用的標幟如下:
1.FLAG_ACTIVITY_SINGLE_TOP: 和棧頂重用(SingleTop)相同。
2.FLAG_ACTIVITY_NEW_TASK: 和棧內重用(SingleTask)相同,但不會清除該 Activity以上的其它 Activity。
3.FLAG_ACTIVITY_CLEAR_TOP:如果是棧內重用的啟動模式,則會清除棧上其它的 Activity 並重用實體且呼叫 onNewIntent 方法。
如果是預設的啟動模式則會清除自己和其它的實體,再重新建立,呼叫 onCreate

在 AndroidManifest.xml 設定和在 startActivity 的 intent 設定的不同點:

1.2 種方式選擇 1 個使用即可。如果同時使用,startActivity 的優先級較高會蓋過AndroidManifest.xml 的設定
2.startActivity 無法設定 SingleInstance 模式
3.AndroidManifest.xml 只有 SingleTask 具有清除頂部的功能,但 startActivity 可以透過設定 FLAG_ACTIVITY_CLEAR_TOP 清除頂部,在標準的啟動模式也可以使用
4.當使用 startActivityForResult 啟動 Activity 時不會重用實體,會重複建立多個實體,因此使用 startActivityForResult 時不建議同時使用會重用實體的啟動模式(SingleTop, SingleTask, SingleInstance)
 

總結:

1.SingleTop(棧頂重用)
當要啟動的 Activity 位於棧頂時重用該實體。如果不在棧頂則建立新的實體。
2.SingleTask(棧內重用)
當要啟動的 Activity 位於棧頂時重用該實體。如果不在棧頂則清除該 Activity 以上的其它 Activity。
3.SingleInstance(單例實體)
當要啟動的 Activity 不存在時直接建立新的任務棧並放入該 Activity,之後都會重用該 Activity。
4.任務棧的切換
SingleTop, SingleTask, SingleInstance 都可透過 taskAffinity 建立新的任務棧,當使用 back key退出 Activity 時會先把同一個棧的 Activity 全部退出。切換任務棧可能會搞混。
5.設定啟動模式的方法
有 2 種,可在 AndroidManifest.xml 以及 startActivity 的intent。startActivity 的優先性大於 AndroidManifest.xml。
6.startActivityForResult 建議不要和會重用實體的模式同時使用,因為該方法無法重用實體。
 
Source Code:
FirstActivity.java

public class FirstActivity extends AppCompatActivity implements OnClickListener {
  private TextView mActivityInstanceID;
  private Button mLaunchSecondActivity;
  private Button mLaunchFirstActivity;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.first_activity);
    mLaunchSecondActivity = findViewById(R.id.first_activity_launch_second_activity);
    mLaunchSecondActivity.setOnClickListener(this);
    mLaunchFirstActivity = findViewById(R.id.first_activity_launch_first_activity);
    mLaunchFirstActivity.setOnClickListener(this);
    mActivityInstanceID = findViewById(R.id.first_activity_activity_instance_id);
    mActivityInstanceID.setText(this.toString());
  }
  @Override
  public void onClick(View view) {
    int uiID = view.getId();
    switch (uiID){
      case R.id.first_activity_launch_first_activity:
        Intent launchFirstActivityIntent = new Intent(this, FirstActivity.class);
        startActivity(launchFirstActivityIntent);
        break;
      case R.id.first_activity_launch_second_activity:
        Intent launchSecondActivityIntent = new Intent(this, SecondActivity.class);
        startActivity(launchSecondActivityIntent);
        break;
   }
  }
}

SecondActivity.java

public class SecondActivity extends AppCompatActivity implements OnClickListener {
  private static final String TAG = SecondActivity.class.getSimpleName();
  private TextView mActivityInstanceID;
  private Button mLaunchSecondActivity;
  private Button mLaunchThirdActivity;
  private Button mLaunchFifthActivity;
  private int mCount;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    Log.d(TAG, "onCreate");
    super.onCreate(savedInstanceState);
    setContentView(R.layout.second_activity);
    mLaunchSecondActivity = findViewById(R.id.second_activity_launch_second_activity);
    mLaunchSecondActivity.setOnClickListener(this);
    mLaunchThirdActivity = findViewById(R.id.second_activity_launch_third_activity);
    mLaunchThirdActivity.setOnClickListener(this);
    mLaunchFifthActivity = findViewById(R.id.second_activity_launch_fifth_activity);
    mLaunchFifthActivity.setOnClickListener(this);
    mActivityInstanceID = findViewById(R.id.second_activity_activity_instance_id);
    mActivityInstanceID.setText(this.toString());
  }
  @Override
  protected void onStart() {
    Log.d(TAG, "onStart");
    super.onStart();
  }
  @Override
  protected void onResume() {
    Log.d(TAG, "onResume");
    super.onResume();
  }
  @Override
  protected void onPause() {
    Log.d(TAG, "onPause");
    super.onPause();
  }
  @Override
  protected void onStop() {
    Log.d(TAG, "onStop");
    super.onStop();
  }
  @Override
  protected void onDestroy() {
    Log.d(TAG, "onDestroy");
    super.onDestroy();
  }
  @Override
  protected void onRestart() {
    Log.d(TAG, "onRestart");
    super.onRestart();
  }
  @Override
  protected void onSaveInstanceState(Bundle outState) {
    Log.d(TAG, "onSaveInstanceState");
    super.onSaveInstanceState(outState);
  }
  @Override
  protected void onRestoreInstanceState(Bundle savedInstanceState) {
    Log.d(TAG, "onRestoreInstanceState");
    super.onRestoreInstanceState(savedInstanceState);
  }
  @Override
  protected void onNewIntent(Intent intent) {
    Log.d(TAG, "onNewIntent");
    super.onNewIntent(intent);
    mCount++;
    Log.d(TAG, "mcount:"+mCount);
  }
  @Override
  public void onClick(View view) {
    int uiID = view.getId();
    switch (uiID){
      case R.id.second_activity_launch_second_activity:
        Intent launchSecondActivityIntent = new Intent(this, SecondActivity.class);
        startActivity(launchSecondActivityIntent);
        break;
      case R.id.second_activity_launch_third_activity:
        Intent launchThirdActivityIntent = new Intent(this, ThirdActivity.class);
        startActivity(launchThirdActivityIntent);
        break;
      case R.id.second_activity_launch_fifth_activity:
        Intent launchFifthActivityIntent = new Intent(this, FifthActivity.class);
        startActivity(launchFifthActivityIntent);
        break;
    }
  }
}

ThirdActivity.java

public class ThirdActivity extends AppCompatActivity implements OnClickListener {
  private static final String TAG = ThirdActivity.class.getSimpleName();
  private Button mLaunchSecondActivity;
  private Button mLaunchFirstActivity;
  private Button mLaunchThirdActivity;
  private Button mLaunchFourthActivity;
  private TextView mActivityInstanceID;
  private int mReuseActivityCount;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    Log.d(TAG, "onCreate");
    super.onCreate(savedInstanceState);
    setContentView(R.layout.third_activity);
    mLaunchSecondActivity = findViewById(R.id.third_activity_launch_second_activity);
    mLaunchSecondActivity.setOnClickListener(this);
    mLaunchFirstActivity = findViewById(R.id.third_activity_launch_first_activity);
    mLaunchFirstActivity.setOnClickListener(this);
    mActivityInstanceID = findViewById(R.id.third_activity_activity_instance_id);
    mActivityInstanceID.setText(this.toString());
    mLaunchThirdActivity = findViewById(R.id.third_activity_launch_third_activity);
    mLaunchThirdActivity.setOnClickListener(this);
    mLaunchFourthActivity = findViewById(R.id.third_activity_launch_fourth_activity);
    mLaunchFourthActivity.setOnClickListener(this);
  }
  @Override
  protected void onStart() {
    Log.d(TAG, "onStart");
    super.onStart();
  }
  @Override
  protected void onResume() {
    Log.d(TAG, "onResume");
    super.onResume();
  }
  @Override
  protected void onPause() {
    Log.d(TAG, "onPause");
    super.onPause();
  }
  @Override
  protected void onStop() {
    Log.d(TAG, "onStop");
    super.onStop();
  }
  @Override
  protected void onDestroy() {
    Log.d(TAG, "onDestroy");
    super.onDestroy();
  }
  @Override
  protected void onRestart() {
    Log.d(TAG, "onRestart");
    super.onRestart();
  }
  @Override
  protected void onSaveInstanceState(Bundle outState) {
    Log.d(TAG, "onSaveInstanceState");
    super.onSaveInstanceState(outState);
  }
  @Override
  protected void onRestoreInstanceState(Bundle savedInstanceState) {
    Log.d(TAG, "onRestoreInstanceState");
    super.onRestoreInstanceState(savedInstanceState);
  }
  @Override
  protected void onNewIntent(Intent intent) {
    Log.d(TAG, "onNewIntent");
    super.onNewIntent(intent);
    mReuseActivityCount++;
    Log.d(TAG, "mReuseActivityCount:"+mReuseActivityCount);
  }
  @Override
  public void onClick(View view) {
    int uiID = view.getId();
    switch(uiID){
      case R.id.third_activity_launch_third_activity:
        Intent launchThirdActivityIntent = new Intent(this, ThirdActivity.class);
        startActivity(launchThirdActivityIntent);
        break;
      case R.id.third_activity_launch_second_activity:
        Intent launchSecondActivityIntent = new Intent(this, SecondActivity.class);
        startActivity(launchSecondActivityIntent);
        break;
      case R.id.third_activity_launch_first_activity:
        Intent launchFirstActivityIntent = new Intent(this, FirstActivity.class);
        startActivity(launchFirstActivityIntent);
        break;
      case R.id.third_activity_launch_fourth_activity:
        Intent launchFourthActivityIntent = new Intent(this, FourthActivity.class);
        startActivity(launchFourthActivityIntent);
        break;
    }
  }
}

FourthActivity.java

public class FourthActivity extends AppCompatActivity implements OnClickListener {
  private static final String TAG = FourthActivity.class.getSimpleName();
  private TextView mActivityInstanceID;
  private Button mLaunchFifthActivity;
  private int mReuseActivityCount;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    Log.d(TAG, "onCreate");
    super.onCreate(savedInstanceState);
    setContentView(R.layout.fourth_activity);
    mActivityInstanceID = findViewById(R.id.fourth_activity_activity_instance_id);
    mActivityInstanceID.setText(this.toString());
    mLaunchFifthActivity = findViewById(R.id.fourth_activity_launch_fifth_activity);
    mLaunchFifthActivity.setOnClickListener(this);
  }
  @Override
  protected void onStart() {
    Log.d(TAG, "onStart");
    super.onStart();
  }
  @Override
  protected void onResume() {
    Log.d(TAG, "onResume");
    super.onResume();
  }
  @Override
  protected void onPause() {
    Log.d(TAG, "onPause");
    super.onPause();
  }
  @Override
  protected void onStop() {
    Log.d(TAG, "onStop");
    super.onStop();
  }
  @Override
  protected void onDestroy() {
    Log.d(TAG, "onDestroy");
    super.onDestroy();
  }
  @Override
  protected void onRestart() {
    Log.d(TAG, "onRestart");
    super.onRestart();
  }
  @Override
  protected void onSaveInstanceState(Bundle outState) {
    Log.d(TAG, "onSaveInstanceState");
    super.onSaveInstanceState(outState);
  }
  @Override
  protected void onRestoreInstanceState(Bundle savedInstanceState) {
    Log.d(TAG, "onRestoreInstanceState");
    super.onRestoreInstanceState(savedInstanceState);
  }
  @Override
  protected void onNewIntent(Intent intent) {
    Log.d(TAG, "onNewIntent");
    super.onNewIntent(intent);
    mReuseActivityCount++;
    Log.d(TAG, "mReuseActivityCount:" + mReuseActivityCount);
  }
  @Override
  public void onClick(View view) {
    int uiID = view.getId();
    switch(uiID){
      case R.id.fourth_activity_launch_fifth_activity:
        Intent launchFifthActivityIntent = new Intent(this, FifthActivity.class);
        startActivity(launchFifthActivityIntent);
        break;
    }
  }
}

FifthActivity.java

public class FifthActivity extends AppCompatActivity implements OnClickListener {
  private Button mLaunchFourthActivity;
  private TextView mActivityInstanceID;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.fifth_activity);
    mActivityInstanceID = findViewById(R.id.fifth_activity_activity_instance_id);
    mActivityInstanceID.setText(this.toString());
    mLaunchFourthActivity = findViewById(R.id.fifth_activity_launch_fourth_activity);
    mLaunchFourthActivity.setOnClickListener(this);
  }
  @Override
  public void onClick(View view) {
    int uiID = view.getId();
    switch(uiID){
      case R.id.fifth_activity_launch_fourth_activity:
        Intent launchFourthActivityIntent = new Intent(this, FourthActivity.class);
        startActivity(launchFourthActivityIntent);
        break;
    }
  }
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.codefoxx.activitylaunchmode">
  <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/AppTheme">
    <activity
      android:label="FifthActivity"
      android:launchMode="singleInstance"
      android:name=".FifthActivity">
    </activity>
    <activity
      android:label="FouthActivity"
      android:name=".FourthActivity">
    </activity>
    <activity
      android:label="ThirdActivity"
      android:launchMode="singleTask"
      android:name=".ThirdActivity"
      android:taskAffinity="another.stack">
    </activity>
    <activity
      android:label="SecondActivity"
      android:launchMode="singleTop"
      android:name=".SecondActivity">
    </activity>
    <activity
      android:label="FirstActivity"
      android:name=".FirstActivity">
      <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
      </intent-filter>
    </activity>
  </application>
</manifest>