Activity 生命周期与启动模式
问题
Activity 的生命周期是怎样的?四种启动模式分别适用于什么场景?
答案
1. Activity 生命周期
| 回调 | 调用时机 | 典型操作 |
|---|---|---|
onCreate | 首次创建 | 初始化布局、ViewModel、数据绑定 |
onStart | 变为可见 | 注册监听器 |
onResume | 获得焦点 | 恢复动画、开始相机预览 |
onPause | 失去焦点 | 暂停动画、释放相机 |
onStop | 完全不可见 | 释放资源、保存数据 |
onDestroy | 销毁 | 释放所有资源 |
常见场景的生命周期调用
// 场景 1:A 启动 B
// A: onPause → B: onCreate → onStart → onResume → A: onStop
// 场景 2:从 B 返回 A
// B: onPause → A: onRestart → onStart → onResume → B: onStop → onDestroy
// 场景 3:屏幕旋转(默认)
// onPause → onStop → onSaveInstanceState → onDestroy
// onCreate → onStart → onRestoreInstanceState → onResume
// 场景 4:按 Home 键
// onPause → onStop → (onSaveInstanceState)
// 场景 5:弹出对话框
// 如果是 Dialog Activity → onPause(不会 onStop,因为部分可见)
// 如果是普通 AlertDialog → 不影响生命周期(同一 Activity 内)
- Android 9 (API 28) 之前:在
onStop之前调用,顺序不确定 - Android 9+ (API 28+):在
onStop之后调用 - 不保证一定被调用:用户主动按返回键退出时不会调用(因为是用户意愿销毁)
2. 状态保存与恢复
class MyActivity : AppCompatActivity() {
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString("key", "value")
outState.putInt("scroll_position", recyclerView.scrollY)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 恢复状态
savedInstanceState?.let {
val value = it.getString("key")
val scrollPos = it.getInt("scroll_position")
}
}
// 或者在这里恢复(此方法只在有 savedInstanceState 时才调用)
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
val value = savedInstanceState.getString("key")
}
}
onSaveInstanceState 只适合保存少量 UI 状态(Bundle 有 1MB 限制)。大量数据应使用:
- ViewModel — 配置变更时保留(屏幕旋转)
- SavedStateHandle — 进程被杀后恢复
- 持久化存储 — Room/DataStore 保存长期数据
3. 四种启动模式
// AndroidManifest.xml 中声明
<activity
android:name=".DetailActivity"
android:launchMode="singleTop" />
// 或通过 Intent Flag 动态设置
val intent = Intent(this, DetailActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(intent)
standard(标准模式,默认)
每次启动都创建新实例,可以有多个相同 Activity 实例在栈中。
启动 A → A → A → A
栈: [A] → [A, A] → [A, A, A]
singleTop(栈顶复用)
如果目标 Activity 已在栈顶,则复用并调用 onNewIntent(),否则创建新实例。
栈: [A, B, C]
启动 C → 复用栈顶 C,调用 C.onNewIntent()
启动 B → 创建新 B,栈变为 [A, B, C, B]
适用场景:搜索页面、通知跳转页面(避免重复创建)
singleTask(栈内复用)
在目标任务栈中查找实例,如果存在则清除其上方所有 Activity 并调用 onNewIntent()。
栈: [A, B, C, D]
启动 B(singleTask) → 清除 C、D,栈变为 [A, B],调用 B.onNewIntent()
适用场景:主页面(从任何深层页面返回主页)
singleInstance(单实例)
独占一个任务栈,全局唯一实例。
Task1: [A, B]
启动 C(singleInstance) → Task2: [C](独立任务栈)
C 启动 D → Task1: [A, B, D](D 回到原任务栈)
适用场景:来电界面、全局悬浮窗
4. 常用 Intent Flags
| Flag | 效果 |
|---|---|
FLAG_ACTIVITY_NEW_TASK | 在新任务栈中启动 |
FLAG_ACTIVITY_CLEAR_TOP | 清除目标 Activity 上方的所有 Activity |
FLAG_ACTIVITY_SINGLE_TOP | 等价于 singleTop |
FLAG_ACTIVITY_CLEAR_TASK | 清除整个任务栈,结合 NEW_TASK 使用 |
FLAG_ACTIVITY_NO_HISTORY | 不保留在栈中(离开即销毁) |
// 典型组合:重新回到登录页并清空栈
val intent = Intent(this, LoginActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)
finish()
5. taskAffinity
<!-- 每个 Activity 可以指定 taskAffinity(默认为包名) -->
<activity
android:name=".SettingsActivity"
android:launchMode="singleTask"
android:taskAffinity="com.example.settings" />
taskAffinity 决定 Activity 归属哪个任务栈。配合 singleTask 使用时,系统会查找具有相同 taskAffinity 的任务栈。
常见面试问题
Q1: A 启动 B,两者的生命周期调用顺序是什么?
答案:
A.onPause() → B.onCreate() → B.onStart() → B.onResume() → A.onStop()
关键点:A 的 onPause 先于 B 的 onCreate。所以不要在 onPause 中做耗时操作,否则会延迟新 Activity 的显示。
Q2: onSaveInstanceState 和 ViewModel 的区别?
答案:
| 特性 | onSaveInstanceState | ViewModel |
|---|---|---|
| 存活范围 | 进程被杀后恢复 | 仅配置变更 |
| 数据大小 | 小(Bundle,< 1MB) | 无限制(内存) |
| 数据类型 | Parcelable/Serializable | 任意对象 |
| 典型用途 | 页面滚动位置、输入文本 | 网络数据、列表数据 |
| 恢复时机 | onCreate/onRestoreInstanceState | 直接访问 |
最佳实践:ViewModel + SavedStateHandle 组合使用,兼顾两种场景。
Q3: singleTask 启动模式中,onNewIntent 的调用时机?
答案:
当 singleTask Activity 已存在于任务栈中时,系统:
- 将该 Activity 上方的所有 Activity 出栈(调用它们的
onDestroy) - 调用目标 Activity 的
onNewIntent(intent) - 接着调用
onRestart→onStart→onResume
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
setIntent(intent) // ⚠️ 必须手动更新 Intent
// 处理新的 Intent 数据
handleIntent(intent)
}
Q4: 如何避免屏幕旋转导致 Activity 重建?
答案:
<!-- 方案 1:在 Manifest 中声明处理配置变更 -->
<activity
android:name=".MyActivity"
android:configChanges="orientation|screenSize|keyboardHidden" />
// Activity 不会重建,而是回调 onConfigurationChanged
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
// 手动处理配置变化
}
方案 2(推荐):不阻止重建,使用 ViewModel 保存数据,让系统正常重建 Activity。这样能正确加载不同方向的布局资源。
Q5: 什么是 Activity 的透明主题?对生命周期有什么影响?
答案:
透明 Activity(Theme.Translucent)启动时,下层 Activity仍然可见,因此下层 Activity 的 onStop 不会被调用,只会调用 onPause。
<style name="TransparentTheme" parent="Theme.AppCompat">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
</style>
生命周期:下层 Activity onPause(不调用 onStop),因为它仍然部分可见。