A
第 11 章JAVA45 分钟

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();
                }
            }
        });
    }
}

常见坑与最佳实践

  1. 在 onPause/onStop 中耗时操作导致 ANR:onPause 中不要做磁盘 I/O、网络请求;广播注销、监听移除等轻量操作放在 onStop,资源释放放 onDestroy。
  2. onDestroy 不一定被调用:用户直接杀进程时不会回调 onDestroy。因此持久化数据应在 onPause 或 onStop 中完成,不能依赖 onDestroy。
  3. launchMode="singleTask" 滥用:很多人误以为“主界面用 singleTask 能保活”,实际上它会让该 Activity 上方的所有 Activity 被清空,可能造成意外的回退栈错乱。
  4. 在 onSaveInstanceState 中保存大对象:Bundle 有 1MB 左右的总体限制(受 Binder 事务大小约束),存 Bitmap 会抛 TransactionTooLargeException。
  5. 用 android:configChanges 逃避旋转重建:仅对 orientation|screenSize 起效,且会绕过资源重载,对国际化、深色模式切换不友好。
  6. 泄漏 Context:把 Activity 引用放进静态字段、单例、未注销的广播,是导致 Activity 泄漏最常见的原因。请使用 WeakReference 或在 onDestroy 中主动清理。
  7. 在 onCreate 中同步耗时初始化:冷启动时间会显著变长,建议拆分到后台线程或使用 App Startup 库做按需初始化。
  8. 忽略多窗口与折叠屏:在多窗口或折叠屏下,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 构建现代的滑动多页体验。