跳转到内容

2.3 实验:Activity 的四种启动模式

1.1 本次实验的项目参数(供参考)

本次已提供初始工程,无需手动新建项目。以下参数是该项目的基本信息,导入后可在 Android Studio 中核对:

参数名
APP_NAMELaunchModeDemo
PACKAGE_NAMEcn.edu.sziit.android.launchmodedemo
MIN_SDKAPI 24

1.2 下载实验记录表

本实验要求你边操作边填写实验记录表,完成后提交作为评分依据。

实验记录表:点击下载作业提交文档


1.3 导入项目

本次实验已为你准备好初始项目,无需从零新建。请按以下步骤操作:

步骤 1:点击下方链接下载项目压缩包:

📦 下载 LaunchModeDemo.zip

步骤 2:将压缩包解压到合适目录。

⚠️ 解压路径要求:

  • 不能含有中文字符
  • 不能含有空格
  • 建议放在非 C 盘的专用目录,例如:D:\AndroidProjects\LaunchModeDemo

步骤 3:将解压后的项目导入 Android Studio,并完成 Gradle Sync。

参考《导入已有 Android 项目》完成导入与同步。

本实验不在此步创建 Activity,后续步骤将手动创建。

2.1 什么是任务栈(Back Stack)

Android 使用任务栈来管理页面的入栈和出栈。每次打开一个新 Activity,它就被压入栈顶;按 Back 键时,栈顶 Activity 出栈被销毁,下方的 Activity 重新可见。

按 Back 前: 按 Back 后:
┌──────────────┐ ┌──────────────┐
│ ActivityC │ ← 栈顶 │ ActivityB │ ← 栈顶
├──────────────┤ ├──────────────┤
│ ActivityB │ │ ActivityA │
├──────────────┤ └──────────────┘
│ ActivityA │ ← 栈底
└──────────────┘

2.2 四种启动模式概览

AndroidManifest.xml 中,通过 android:launchMode 属性为每个 Activity 指定启动模式:

启动模式每次都创建新实例?栈顶复用?清栈?独立任务栈?
standard(默认)✅ 是
singleTop不在栈顶时创建✅ 在栈顶时复用
singleTask栈中存在时复用✅ 清除其上所有页面❌(可配置)
singleInstance只创建一次✅ 独占新任务栈

2.3 用 adb 查看任务栈

后续每个实验步骤均使用以下命令查看任务栈。 本节说明命令各段的作用及输出格式。

使用 adb 命令前,需完成环境变量配置,详见 1.1 搭建 Android 开发环境 · 步骤 7

命令:

Terminal window
adb shell dumpsys activity activities | findstr /R /C:"^ \* Task.*launchmodedemo" /C:"Hist.*launchmodedemo"

命令通过 | 管道连接两段:

片段作用
adb shell dumpsys activity activities输出设备上所有 Activity 和任务栈的当前状态
findstr /R /C:"^ \* Task.*launchmodedemo" /C:"Hist.*launchmodedemo"过滤保留两类行:① 以 2 个空格开头的 Task 行(任务栈摘要);② 含 Hist 的行(栈内 Activity 记录)

Task 行空格前缀的作用: dumpsys 输出中,同一个 Task 会出现在多个区块,但主任务列表中的 Task 行固定以 2 个空格开头,其他区块的引用缩进更深。^ \* Task 前缀精确匹配主任务列表,避免重复输出。

输出格式(以 standard 模式、栈内 3 个 Activity 为例):

* Task{... A=...:cn.edu.sziit.android.launchmodedemo ... sz=3}
* Hist #2: ActivityRecord{...} cn.edu.sziit.android.launchmodedemo/.MainActivity
* Hist #1: ActivityRecord{...} cn.edu.sziit.android.launchmodedemo/.SecondActivity
* Hist #0: ActivityRecord{...} cn.edu.sziit.android.launchmodedemo/.MainActivity
  • sz=3:当前任务栈内共有 3 个 Activity 实例
  • Hist #0 为栈底(最早入栈),序号最大的为栈顶(最近打开)

本步骤创建实验所需的两个界面 MainActivity 和 SecondActivity。不配置任何启动模式,运行后观察 standard(默认)模式的行为:每次跳转都会新建一个界面,不管该界面之前是否已经打开过。

3.1 创建 MainActivity

app/src/main/java/cn/edu/sziit/android/launchmodedemo/ 下右键 → New → Activity → Empty Views Activity,填写:

  • Activity NameMainActivity
  • 勾选 Launcher Activity

activity_main.xml 的布局代码替换成以下内容:

app/src/main/res/layout/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"
android:gravity="center"
android:padding="24dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="MainActivity"
android:textSize="20sp"
android:layout_marginBottom="24dp"/>
<Button
android:id="@+id/btnToSecond"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="跳转到 SecondActivity" />
</LinearLayout>

修改 MainActivity.kt,加入跳转和日志打点:

app/src/main/java/cn/edu/sziit/android/launchmodedemo/MainActivity.kt
package cn.edu.sziit.android.launchmodedemo
import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import cn.edu.sziit.android.launchmodedemo.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setContentView(R.layout.activity_main)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
Log.d("LaunchMode", "MainActivity onCreate hash=${hashCode()}")
binding.btnToSecond.setOnClickListener {
startActivity(Intent(this, SecondActivity::class.java))
}
}
// 实现 onNewIntent 方法,用于 singleTop 和 singleTask 模式的验证
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
Log.d("LaunchMode", "MainActivity onNewIntent hash=${hashCode()}")
}
}

3.2 创建 SecondActivity

同理新建 SecondActivity不勾选 Launcher Activity)。

修改 activity_second.xml,内容如下:

app/src/main/res/layout/activity_second.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"
android:gravity="center"
android:padding="24dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SecondActivity"
android:textSize="20sp"
android:layout_marginBottom="24dp"/>
<Button
android:id="@+id/btnToMain"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="返回 MainActivity" />
</LinearLayout>

修改 SecondActivity.kt,加入日志打点和跳转逻辑:

app/src/main/java/cn/edu/sziit/android/launchmodedemo/SecondActivity.kt
package cn.edu.sziit.android.launchmodedemo
import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import cn.edu.sziit.android.launchmodedemo.databinding.ActivitySecondBinding
class SecondActivity : AppCompatActivity() {
private lateinit var binding: ActivitySecondBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivitySecondBinding.inflate(layoutInflater)
setContentView(binding.root)
setContentView(R.layout.activity_main)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
Log.d("LaunchMode", "SecondActivity onCreate hash=${hashCode()}")
binding.btnToMain.setOnClickListener {
startActivity(Intent(this, MainActivity::class.java))
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
Log.d("LaunchMode", "SecondActivity onNewIntent hash=${hashCode()}")
}
override fun onDestroy() {
super.onDestroy()
Log.d("LaunchMode", "SecondActivity onDestroy hash=${hashCode()}")
}
}

3.3 确认 AndroidManifest.xml(不加 launchMode)

此时两个 Activity 的注册如下(launchMode 留空,使用默认值 standard):

app/src/main/AndroidManifest.xml
<manifest ...>
<application ...>
...
<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>
<activity
android:name=".SecondActivity"
android:exported="false" />
</application>
</manifest>

3.4 运行并观察 standard 行为

运行 App,按 Main → Second → Main → Second 的顺序依次点击跳转按钮。

打开 Logcat,在搜索框输入 LaunchMode 过滤,观察日志:

standard 模式下每次跳转都会创建新实例

如需同时查看此时的任务栈结构,可在 Android Studio 内置 Terminal 中执行:

Terminal window
adb shell dumpsys activity activities | findstr /R /C:"^ \* Task.*launchmodedemo" /C:"Hist.*launchmodedemo"

任务栈结构如下,此时按 Back 需要 4 次才能退出 App。

standard 模式下的任务栈结构

记录:将你观察到的 hash 值变化情况填入实验记录表。

本步骤将 MainActivity 的启动模式设置为 singleTop,通过两个场景验证其行为:① 当 MainActivity 已显示在最顶层时,再次跳转不会新建界面;② 当 MainActivity 不在最顶层时,仍会新建一个界面。

4.1 修改 AndroidManifest.xml

MainActivity 添加 launchMode 属性:

app/src/main/AndroidManifest.xml
<manifest ...>
<application ...>
...
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
...
</application>
</manifest>

4.2 场景一:从外部跳转回 MainActivity

路径:Main → Second → Main,观察 Logcat:

singleTop 模式下从 SecondActivity 跳转回 MainActivity 仍创建新实例

如需查看此时的任务栈结构,可在 Android Studio 内置 Terminal 中执行:

Terminal window
adb shell dumpsys activity activities | findstr /R /C:"^ \* Task.*launchmodedemo" /C:"Hist.*launchmodedemo"

结果如下:

singleTop 模式下从 SecondActivity 跳转回 MainActivity 的任务栈结构

结论:此时 MainActivity 不在栈顶(栈顶是 SecondActivity),singleTop 不起作用,行为与 standard 完全相同


4.3 场景二:在 MainActivity 中”启动自身”

activity_main.xml 中额外添加一个按钮:

app/src/main/res/layout/activity_main.xml
<LinearLayout ...>
...
<Button
android:id="@+id/btnToSecond"
... />
<!-- 新增以下按钮 ↓ -->
<Button
android:id="@+id/btnSelf"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="再次启动 MainActivity" />
</LinearLayout>

MainActivity.ktonCreate 中加入:

app/src/main/java/cn/edu/sziit/android/launchmodedemo/MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
...
binding.btnToSecond.setOnClickListener { ... }
// 新增以下代码 ↓
binding.btnSelf.setOnClickListener {
startActivity(Intent(this, MainActivity::class.java))
}
}
...
}

运行后,在 MainActivity 界面连续点击”再次启动 MainActivity”三次,观察 Logcat(以下示例省去了无关信息):

2026-03-23 16:29:48.660 LaunchMode D MainActivity onCreate hash=139004283 ← 首次创建
2026-03-23 16:30:00.071 LaunchMode D MainActivity onNewIntent hash=139004283 ← 复用!hash 相同
2026-03-23 16:30:02.553 LaunchMode D MainActivity onNewIntent hash=139004283 ← 继续复用

如需查看此时的任务栈结构,可在 Android Studio 内置 Terminal 中执行:

Terminal window
adb shell dumpsys activity activities | findstr /R /C:"^ \* Task.*launchmodedemo" /C:"Hist.*launchmodedemo"

任务栈:

singleTop 模式下在 MainActivity 内部启动自身的任务栈结构

结论:当目标 Activity 已在栈顶时singleTop 不创建新实例,而是调用 onNewIntent() 通知当前实例。

记录:将两个场景的 hash 值变化情况填入实验记录表。

本步骤将 MainActivity 的启动模式改为 singleTask,观察其特有行为:当任务栈中已存在 MainActivity 时,系统会关闭压在它上方的所有界面,直接回到原有的那个 MainActivity。

5.1 修改 AndroidManifest.xml

MainActivity 的启动模式改为 singleTask

app/src/main/AndroidManifest.xml
<manifest ...>
<application ...>
...
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
...
</application>
</manifest>

5.2 验证”清栈”行为

路径:Main → Second → 再次启动 Main

确认 SecondActivity 中已有”返回 MainActivity”按钮,点击时通过 Intent 直接启动 MainActivity(与之前代码相同)。

运行并观察 Logcat:

D/LaunchMode: MainActivity onCreate hash=112013270
D/LaunchMode: SecondActivity onCreate hash=128848572
D/LaunchMode: MainActivity onNewIntent hash=112013270 ← MainActivity 被复用,hash 相同
D/LaunchMode: SecondActivity onDestroy hash=128848572 ← SecondActivity 被系统销毁!

如需查看此时的任务栈结构,可在 Android Studio 内置 Terminal 中执行:

Terminal window
adb shell dumpsys activity activities | findstr /R /C:"^ \* Task.*launchmodedemo" /C:"Hist.*launchmodedemo"
singleTask 模式下再次启动 MainActivity 的任务栈结构

任务栈变化:

操作 任务栈(从底→顶)
──────────────────────────────────────────────
启动 App → [ Main(1) ]
→ Second → [ Main(1) | Second(2) ]
→ Main → [ Main(1) ] ← Second 被弹出销毁,Main 复用(触发 onNewIntent)

记录:此时按 Back 键,App 是直接退出还是回到 SecondActivity?将答案填入实验记录表。

本步骤新建 ThirdActivity 并将其设置为 singleInstance 模式。该模式下,这个界面单独运行在一个独立任务中,不与其他界面共用,并且整个 App 中最多只存在一个。步骤末尾对四种启动模式进行汇总对比。

6.1 创建 ThirdActivity

app/src/main/java/cn/edu/sziit/android/launchmodedemo/ 下右键 → New → Activity → Empty Views Activity,填写:

  • Activity NameThirdActivity
  • 不勾选 Launcher Activity

修改布局文件 activity_third.xml,内容如下:

app/src/main/res/layout/activity_third.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"
android:gravity="center"
android:padding="24dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ThirdActivity"
android:textSize="20sp"
android:layout_marginBottom="24dp"/>
<Button
android:id="@+id/btnToMain"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="跳转到 MainActivity" />
</LinearLayout>

修改 ThirdActivity.kt,加入日志打点和跳转逻辑:

app/src/main/java/cn/edu/sziit/android/launchmodedemo/ThirdActivity.kt
package cn.edu.sziit.android.launchmodedemo
import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import cn.edu.sziit.android.launchmodedemo.databinding.ActivityThirdBinding
class ThirdActivity : AppCompatActivity() {
private lateinit var binding: ActivityThirdBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityThirdBinding.inflate(layoutInflater)
setContentView(binding.root)
setContentView(R.layout.activity_third)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
Log.d("LaunchMode", "ThirdActivity onCreate hash=${hashCode()}")
binding.btnToMain.setOnClickListener {
startActivity(Intent(this, MainActivity::class.java))
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
Log.d("LaunchMode", "ThirdActivity onNewIntent hash=${hashCode()}")
}
override fun onDestroy() {
super.onDestroy()
Log.d("LaunchMode", "ThirdActivity onDestroy hash=${hashCode()}")
}
}

AndroidManifest.xml 中注册,并指定 singleInstance

app/src/main/AndroidManifest.xml
<manifest ...>
<application ...>
...
<activity android:name=".MainActivity" ... />
<activity android:name=".SecondActivity" />
<!-- 将 ThirdActivity 设置为 singleInstance 模式 -->
<activity
android:name=".ThirdActivity"
android:launchMode="singleInstance"
android:exported="false"/>
</application>
</manifest>

同时在 SecondActivity 的布局中添加”跳转到 ThirdActivity”按钮。打开 activity_second.xml,在原有按钮下方追加一个新按钮:

app/src/main/res/layout/activity_second.xml
<LinearLayout ...>
...
<!-- 原有按钮,保持不变 -->
<Button
android:id="@+id/btnToMain"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="返回 MainActivity" />
<!-- 新增以下按钮 ↓ -->
<Button
android:id="@+id/btnToThird"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="跳转到 ThirdActivity" />
</LinearLayout>

然后在 SecondActivity.ktonCreate 中加入跳转逻辑:

app/src/main/java/cn/edu/sziit/android/launchmodedemo/SecondActivity.kt
class SecondActivity : AppCompatActivity() {
...
override fun onCreate(savedInstanceState: Bundle?) {
...
binding.btnToMain.setOnClickListener { ... }
// 新增以下代码 ↓
binding.btnToThird.setOnClickListener {
startActivity(Intent(this, ThirdActivity::class.java))
}
}
...
}

6.2 验证独立任务栈

路径:Main → Second → Third(到达 ThirdActivity 后暂停操作)

在 Android Studio 内置 Terminal(快捷键 Alt+F12)中执行:

Terminal window
adb shell dumpsys activity activities | findstr /R /C:"^ \* Task.*launchmodedemo" /C:"Hist.*launchmodedemo"

输出示例(位于 ThirdActivity 界面时):

* TaskRecord{a1b2c3 #42 A=cn.edu.sziit.android.launchmodedemo U=0 sz=2}
* Hist #1: ActivityRecord{...} .SecondActivity
* Hist #0: ActivityRecord{...} .MainActivity
* TaskRecord{d4e5f6 #43 A=cn.edu.sziit.android.launchmodedemo U=0 sz=1}
* Hist #0: ActivityRecord{...} .ThirdActivity

可以看到,此时存在两个独立的任务栈

任务栈 A(主栈) 任务栈 B(ThirdActivity 独占)
┌────────────────┐ ┌────────────────┐
│ SecondActivity │ ← 栈顶 │ ThirdActivity │ ← 栈顶(当前页面)
├────────────────┤ └────────────────┘
│ MainActivity │
└────────────────┘

在 ThirdActivity 界面按 Back 键后观察 Logcat:

D/LaunchMode: ThirdActivity onDestroy ← ThirdActivity 出栈
D/LaunchMode: SecondActivity onResume ← 回到任务栈 A 的栈顶(SecondActivity)!

注意:Back 键并没有回到 MainActivity(手动跳转的来源),而是回到了任务栈 A 的栈顶(SecondActivity)。


6.3 四种启动模式总结对比

启动模式新实例条件复用机制是否清栈是否独立任务栈
standard每次都创建
singleTop不在栈顶时创建在栈顶时触发 onNewIntent
singleTask栈中不存在时创建存在时触发 onNewIntent,清除其上所有页面❌(可配 taskAffinity)
singleInstance从未创建时创建全局唯一实例,触发 onNewIntent✅ 独占新任务栈

7.1 常见问题排查

问题 1:运行项目后点击按钮没有反应

先检查按钮点击事件是否已经写在对应 Activity 的 onCreate() 中。

重点检查:

  • 是否已经正确初始化 binding
  • 是否把 setContentView(binding.root) 写在了 binding = xxxBinding.inflate(layoutInflater) 后面
  • 按钮 ID 是否与布局文件中的 ID 完全一致,例如 btnToSecondbtnToMainbtnToThird

如果按钮 ID 写错,或者 binding 对应了错误的布局文件,点击事件就不会正确绑定。

问题 2:Android Studio 提示找不到 ActivityMainBinding

这通常说明 ViewBinding 没有开启,或者 Gradle 还没有重新同步。

排查方法:

  1. 打开 app/build.gradle.kts,确认已经在 android {} 中启用 ViewBinding
  2. 点击 Android Studio 顶部的 Sync Now 或执行一次 Gradle Sync
  3. 若仍未生成,尝试执行 Build → Rebuild Project

可参考以下配置:

app/build.gradle.kts
android {
...
buildFeatures {
viewBinding = true
}
}

问题 3:Logcat 中看不到 LaunchMode 日志

先确认 Logcat 顶部筛选条件是否正确。

建议这样排查:

  1. 在搜索框输入 LaunchMode
  2. 确认当前选择的是正在运行的模拟器或真机
  3. 确认日志级别没有被过滤掉,建议选择 VerboseDebug

如果代码里没有执行到 Log.d(...),也不会有任何输出。此时要回头检查按钮点击事件是否真的触发了页面跳转。

问题 4:修改了 launchMode 后,运行结果没有变化

最常见原因是 改了代码,但没有重新安装新版 App

建议按下面顺序操作:

  1. 修改 AndroidManifest.xml 后重新运行 App
  2. 如果现象仍不对,先卸载模拟器中的旧版本 App,再重新运行
  3. 再次执行实验步骤,重新观察 Logcat 和任务栈输出

因为启动模式配置写在 AndroidManifest.xml 中,若系统中仍保留旧安装包,就可能继续沿用旧配置。

问题 5:执行 adb 命令时提示找不到命令

adb 命令需要先配置好环境变量才能在终端中使用,请参考 1.1 搭建 Android 开发环境 · 步骤 7 完成配置。

完成后重新打开终端窗口(重要:已打开的终端不会自动读取新的环境变量),并确认:

  1. 模拟器已经启动,或真机已通过 USB 成功连接
  2. 运行 adb devices 能看到设备列表,再执行实验命令

问题 6:singleTopsingleTasksingleInstance 的现象看起来分不清

可以按下面的方法重新观察,避免混淆:

  1. 先只看 Logcat 的 hash 值是否变化
  2. 再看是否触发了 onNewIntent()
  3. 最后再用 adb 命令检查任务栈中还剩几个 Activity

判断顺序建议如下:

  • hash 改变:说明创建了新实例
  • hash 不变且出现 onNewIntent():说明复用了旧实例
  • 栈中上层页面消失:说明发生了清栈
  • 出现两个 TaskRecord:说明出现了多任务栈

如果一次同时看太多现象,初学者很容易混乱。建议每次只改一种启动模式,只验证一个结论。