Activity 与生命周期
深入理解 Activity 的 7 种状态、生命周期回调、启动模式、状态保存与配置变更处理,构建稳定可预期的 Android 页面
学习目标
- 掌握 Activity 7 种状态及对应的生命周期回调时序
- 理解 4 种启动模式(standard / singleTop / singleTask / singleInstance)的差异与适用场景
- 学会使用 onSaveInstanceState 与 ViewModel 保存和恢复界面状态
- 正确处理配置变更(屏幕旋转、深色模式切换、语言变更)导致的活动重建
学习目标
Activity 是 Android 四大组件中最常用、最贴近用户的一类,它代表一个“可交互的屏幕”。理解 Activity 生命周期并掌握其状态保存与启动模式,是写出“不闪退、不丢状态、内存不泄漏”应用的前提。本章带你完成以下目标:
- 能在 Logcat 中读出 Activity 的 7 种状态以及对应回调的触发时机;
- 能根据业务需求选择合适的
launchMode,避免重复创建实例或回退栈错乱; - 会在配置变更(如屏幕旋转)时保存并恢复用户输入与滚动位置;
- 知道
onBackPressed()与 OnBackPressedDispatcher 的迁移方向,避免在 Android 13+ 上出现不可预期的行为。
Activity 的 7 种状态与生命周期回调
1. 状态总览
Activity 在其生命周期内会在以下 7 种状态间流转:
| 状态 | 含义 | 是否可见 | 是否可交互 |
|---|---|---|---|
| Created | 已创建但未启动 | 不可见 | 否 |
| Started | 已启动 | 部分可见 | 否 |
| Resumed | 已恢复,获得焦点 | 完全可见 | 是 |
| Paused | 已暂停,失去焦点 | 部分可见 | 否 |
| Stopped | 已停止 | 不可见 | 否 |
| Destroyed | 已销毁,实例被回收 | 不可见 | 否 |
| Restarted | 由 Stopped 重新进入 Started 前的过渡 | 即将可见 | 否 |
2. 7 个核心回调
public class MainActivity extends AppCompatActivity {
private static final String TAG = "LifecycleDemo";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "onCreate");
}
@Override
protected void onStart() {
super.onStart();
Log.d(TAG, "onStart");
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume");
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "onPause");
}
@Override
protected void onStop() {
super.onStop();
Log.d(TAG, "onStop");
}
@Override
protected void onRestart() {
super.onRestart();
Log.d(TAG, "onRestart");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
}
}
3. 典型时序场景
- 首次打开:
onCreate → onStart → onResume - 用户按 Home 键:
onPause → onStop(不销毁) - 从最近任务列表回到 App:
onRestart → onStart → onResume - 关闭 Activity(按返回键):
onPause → onStop → onDestroy - 被半透明 Activity 或 Dialog 遮挡:仅触发
onPause,仍可见 - 被全屏 Activity 完全遮挡:
onPause → onStop
注意:自 Android 7.0(API 24)起,多窗口模式下处于后台的 Activity 会停留在
onPause而非onStop,因为它仍可见。onPause不能再视为“绝对不可见”。
回退栈(Task)与启动模式
1. Task 与 Back Stack
Android 用 Task 管理一组 Activity 实例的回退栈。默认情况下,新启动的 Activity 会被压入当前 Task 顶部;按返回键则弹出栈顶。理解 Task 行为是配置 launchMode 的前提。
2. 四种启动模式
在 AndroidManifest.xml 的 <activity> 标签上通过 android:launchMode 指定:
<activity
android:name=".MainActivity"
android:launchMode="standard" />
| 模式 | 行为 | 典型场景 |
|---|---|---|
standard(默认) |
每次都新建实例并压栈 | 通用详情页 |
singleTop |
若栈顶已是该 Activity 则复用并回调 onNewIntent,否则新建 |
消息列表、推送落地页 |
singleTask |
在指定 Task 中只存在一个实例,再次启动回调 onNewIntent 并清空其上方 Activity |
主页 / 启动页 |
singleInstance |
独占一个 Task,整个系统唯一实例 | 系统级通话、来电界面 |
3. onNewIntent 处理复用场景
public class MessageListActivity extends AppCompatActivity {
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
handlePushIntent(intent);
}
private void handlePushIntent(Intent intent) {
if (intent != null) {
String messageId = intent.getStringExtra("extra_message_id");
if (!TextUtils.isEmpty(messageId)) {
scrollToMessage(messageId);
}
}
}
}
4. 声明 Task 归属
singleTask 常配合 taskAffinity 使用,控制回退栈归属:
<activity
android:name=".RootActivity"
android:launchMode="singleTask"
android:taskAffinity="" />
提示:不要随意修改
taskAffinity,否则可能与系统的最近任务卡片显示行为产生冲突,导致用户难以找到 App。
状态保存与恢复
1. onSaveInstanceState 的触发时机
当系统因内存压力或配置变更可能销毁 Activity 时,会回调 onSaveInstanceState(Bundle) 让你保存“轻量、可序列化”的状态。常见触发点:
- 用户按 Home 键后被系统回收;
- 屏幕旋转(默认行为下 Activity 会重建);
- 系统语言切换、深色模式切换等配置变更。
2. 保存与恢复示例
public class EditNoteActivity extends AppCompatActivity {
private static final String KEY_DRAFT = "key_draft";
private EditText mEditContent;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit_note);
mEditContent = findViewById(R.id.edit_content);
if (savedInstanceState != null) {
String draft = savedInstanceState.getString(KEY_DRAFT);
mEditContent.setText(draft);
}
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(KEY_DRAFT, mEditContent.getText().toString());
}
}
虽然
EditText自身已实现状态保存,但这里仅为演示。对于自定义 View,记得在onSaveInstanceState()与onRestoreInstanceState()中实现状态保存。
3. ViewModel 与 onSaveInstanceState 的分工
| 方案 | 适合保存 | 不适合保存 |
|---|---|---|
onSaveInstanceState |
轻量基本类型、ID、滚动位置 | 大对象、Bitmap |
ViewModel |
跨配置变更的数据(如列表数据、网络结果) | 进程被杀后的恢复 |
SavedStateHandle(ViewModel 内) |
进程被杀后仍能恢复的关键 ID | 大对象 |
public class ProfileViewModel extends ViewModel {
private static final String KEY_USER_ID = "user_id";
private final SavedStateHandle mState;
public ProfileViewModel(SavedStateHandle savedStateHandle) {
mState = savedStateHandle;
}
public String getUserId() {
return mState.get(KEY_USER_ID);
}
public void setUserId(String userId) {
mState.set(KEY_USER_ID, userId);
}
}
// 通过工厂注入
public class ProfileViewModelFactory implements ViewModelProvider.Factory {
private final SavedStateHandle mHandle;
public ProfileViewModelFactory(SavedStateHandle handle) { mHandle = handle; }
@NonNull @Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return modelClass.cast(new ProfileViewModel(mHandle));
}
}
配置变更处理
1. 默认行为:重建 Activity
当屏幕旋转、语言切换、深色模式切换发生时,默认配置下系统会销毁当前 Activity 并重建一个新实例。这是导致“旋转后输入框清空、列表回到顶部”等问题的根源。
2. 方案一:在 Manifest 中声明 configChanges
若你能自行处理某类配置变更,可声明由 Activity 接收而避免重建:
<activity
android:name=".PlayerActivity"
android:configChanges="orientation|screenSize|keyboardHidden|uiMode" />
public class PlayerActivity extends AppCompatActivity {
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
switchToLandscape();
} else {
switchToPortrait();
}
}
}
这种方式被 Google 视为“逃避重建”的简化手段,仅适合简单场景。复杂场景推荐用 ViewModel 配合自动重建。
3. 方案二:用 ViewModel + ViewBinding 应对重建
public class ArticleActivity extends AppCompatActivity {
private ArticleViewModel mViewModel;
private ActivityArticleBinding mBinding;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = ActivityArticleBinding.inflate(getLayoutInflater());
setContentView(mBinding.getRoot());
mViewModel = new ViewModelProvider(this).get(ArticleViewModel.class);
mViewModel.getArticle().observe(this, article -> {
mBinding.setTitle(article.getTitle());
mBinding.setContent(article.getContent());
});
if (mViewModel.getArticle().getValue() == null) {
mViewModel.load(getIntent().getStringExtra("extra_article_id"));
}
}
}
返回键的现代化处理
Activity.onBackPressed() 在 Android 13(API 33+)被标记为废弃,官方推荐使用 OnBackPressedDispatcher:
public class ComposeEditorActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
if (hasUnsavedChanges()) {
showConfirmDiscardDialog();
} else {
setEnabled(false);
getOnBackPressedDispatcher().onBackPressed();
}
}
});
}
}
常见坑与最佳实践
- 在
onPause/onStop中耗时操作导致 ANR:onPause中不要做磁盘 I/O、网络请求;广播注销、监听移除等轻量操作放在onStop,资源释放放onDestroy。 onDestroy不一定被调用:用户直接杀进程时不会回调onDestroy。因此持久化数据应在onPause或onStop中完成,不能依赖onDestroy。launchMode="singleTask"滥用:很多人误以为“主界面用 singleTask 能保活”,实际上它会让该 Activity 上方的所有 Activity 被清空,可能造成意外的回退栈错乱。- 在
onSaveInstanceState中保存大对象:Bundle 有 1MB 左右的总体限制(受Binder事务大小约束),存 Bitmap 会抛TransactionTooLargeException。 - 用
android:configChanges逃避旋转重建:仅对orientation|screenSize起效,且会绕过资源重载,对国际化、深色模式切换不友好。 - 泄漏 Context:把
Activity引用放进静态字段、单例、未注销的广播,是导致 Activity 泄漏最常见的原因。请使用WeakReference或在onDestroy中主动清理。 - 在
onCreate中同步耗时初始化:冷启动时间会显著变长,建议拆分到后台线程或使用App Startup库做按需初始化。 - 忽略多窗口与折叠屏:在多窗口或折叠屏下,
onPause不等于“用户已经离开”,应该使用LifecycleObserver来观察ON_STOP事件做更精确的资源调度。
章节小结
- Activity 在 7 种状态间流转,对应 7 个核心回调,理解时序是处理“用户切到后台又回来”等场景的基础;
- 回退栈由 Task 管理,4 种
launchMode决定实例创建与复用策略; - 状态保存应分层:
ViewModel抗配置变更,SavedStateHandle/onSaveInstanceState抗进程被杀; - 配置变更推荐用 ViewModel 自动重建,避免在 Manifest 中乱写
configChanges; - 返回键处理应迁移到
OnBackPressedDispatcher,为 Android 13+ 做好准备。
下一章预告
第 12 章我们将进入 Fragment 与多屏适配:Fragment 的生命周期、FragmentManager 与 FragmentTransaction 的常用 API、返回栈管理、Fragment 间通信的最佳实践,以及如何用 ViewPager2 + Fragment 构建现代的滑动多页体验。