4、Activity与生命周期:Activity概念、启动模式、生命周期回调、状态保存与恢复、任务栈与返回栈管理
Activity是Android应用中最核心的界面载体,它承载了用户与应用程序之间的每一次交互。理解Activity不仅仅是学会创建一个界面,更重要的是掌握其背后的生命周期机制、启动模式以及任务栈管理规则。这些知识决定了应用在何时创建、何时暂停、何时销毁,以及当系统内存不足或用户切换应用时,应用的状态如何被保存与恢复。本章将从Activity的基本概念出发,深入剖析其生命周期回调的每一个阶段,并结合启动模式与任务栈的底层逻辑,帮助你建立起对Android界面管理的系统性认知。
4.1 Activity核心概念与生命周期回调机制
Activity是Android四大组件之首,它代表一个可交互的用户界面屏幕。每个Activity都是一个独立的类,继承自android.app.Activity或其子类(如AppCompatActivity)。从用户视角看,Activity就是"一个页面";从系统视角看,Activity是运行在进程中的组件实例,由ActivityManagerService(AMS)统一调度。理解Activity的生命周期,本质上是在理解一个界面从创建到销毁的完整过程,以及在这个过程中系统与开发者之间的协作契约。
4.1.1 生命周期回调的七个核心阶段
Android系统为Activity定义了七个生命周期回调方法,它们按照固定的顺序被调用。开发者通过重写这些方法,在合适的时机执行对应的业务逻辑。下表总结了每个回调的触发时机与典型用途:
| 回调方法 | 触发时机 | 典型用途 | 是否可被系统杀死 |
|---|---|---|---|
| onCreate() | Activity首次创建时 | 初始化布局、绑定数据、创建Handler | 否 |
| onStart() | Activity即将可见时 | 注册广播、初始化动画 | 否 |
| onResume() | Activity可交互时 | 启动相机、获取传感器数据 | 否 |
| onPause() | Activity失去焦点但仍可见 | 暂停动画、释放独占资源 | 是(极端情况) |
| onStop() | Activity完全不可见时 | 停止耗时操作、保存草稿 | 是 |
| onRestart() | Activity从停止状态重新可见前 | 重新加载数据 | 否 |
| onDestroy() | Activity被销毁前 | 释放所有资源、解绑服务 | 是 |
需要特别注意的是,onPause()和onStop()方法执行后,Activity实例仍然存在于内存中,但系统可能在任何时候回收它。因此,在onPause()中应停止消耗CPU的操作(如动画),在onStop()中应释放系统资源(如相机),在onDestroy()中应彻底清理所有引用,防止内存泄漏。
生命周期执行顺序验证:当Activity A启动Activity B时,实际调用顺序为:A.onPause() → B.onCreate() → B.onStart() → B.onResume() → A.onStop()。这个顺序保证了新Activity在旧Activity完全停止前就能显示出来,从而让用户感受到流畅的切换体验。如果A.onStop()先于B.onResume()执行,用户会看到短暂的黑屏。
4.1.2 状态保存与恢复的完整方案
当系统内存不足或用户旋转屏幕时,Activity可能被销毁并重建。为了保持用户体验的连续性,Android提供了两套状态保存机制:onSaveInstanceState()/onRestoreInstanceState()用于保存轻量级UI状态,ViewModel用于保存重量级数据。前者适合保存EditText的文本、ScrollView的滚动位置等视图状态,后者适合保存网络请求结果、列表数据等业务数据。
// 保存状态示例
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("user_input", editText.getText().toString());
outState.putInt("scroll_position", scrollView.getScrollY());
}
// 恢复状态示例
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState != null) {
editText.setText(savedInstanceState.getString("user_input"));
scrollView.scrollTo(0, savedInstanceState.getInt("scroll_position"));
}
}
在实际开发中,不要依赖onSaveInstanceState()来保存所有数据。它只在Activity被异常销毁时调用,正常返回(按返回键)时不会触发。对于需要持久化的数据,应使用SharedPreferences、Room数据库或文件存储。
常见误区:很多初学者在onPause()中保存数据,这是不推荐的。onPause()可能被频繁调用(如弹出对话框),如果在这里执行数据库写入操作,会导致UI卡顿。正确的做法是:轻量级UI状态用onSaveInstanceState(),业务数据用ViewModel,持久化数据在合适的时机(如onStop())异步写入。
4.2 启动模式与任务栈管理
启动模式决定了Activity在任务栈中的实例化方式,而任务栈(Task)是Android管理Activity的容器。默认情况下,每个应用有一个任务栈,栈中存放着按启动顺序排列的Activity实例。理解启动模式,是解决"页面重复创建"、"返回栈混乱"等问题的关键。
4.2.1 四种启动模式详解
在AndroidManifest.xml中通过android:launchMode属性可以设置启动模式,也可以在Intent中通过FLAG_ACTIVITY_NEW_TASK等标志动态指定。四种模式的核心区别如下:
| 启动模式 | 实例化规则 | 适用场景 | 注意事项 |
|---|---|---|---|
| standard | 每次启动都创建新实例 | 普通页面,如详情页 | 可能产生大量重复实例 |
| singleTop | 栈顶已存在则复用,否则新建 | 接收推送通知的页面 | 需处理onNewIntent() |
| singleTask | 栈中存在则清除其上所有Activity并复用 | 应用主页面、首页 | 会销毁其他Activity |
| singleInstance | 独占一个任务栈,全局唯一 | 来电界面、闹钟提醒 | 与其他Activity通信需跨Task |
以singleTask模式为例,当Activity A(singleTask)启动时,系统会查找是否存在包含A的任务栈。如果存在,系统会将该栈中A之上的所有Activity出栈,并将A带到栈顶,然后调用A的onNewIntent()方法。这种模式非常适合应用的主界面,可以避免用户从其他应用返回时看到层层叠叠的页面。
4.2.2 任务栈的返回栈管理策略
返回栈(Back Stack)是任务栈中Activity的排列顺序。用户按返回键时,栈顶Activity出栈并销毁,下一个Activity显示。开发者可以通过以下方式管理返回栈:
- finish():主动销毁当前Activity,立即从栈中移除。
- finishAffinity():销毁当前任务栈中所有同属一个Affinity的Activity。
- Intent.FLAG_ACTIVITY_CLEAR_TOP:如果目标Activity已在栈中,则清除其上所有Activity。
- Intent.FLAG_ACTIVITY_SINGLE_TOP:与singleTop模式效果相同。
- onBackPressed():重写返回键逻辑,可拦截返回事件。
// 返回栈管理实战:从A→B→C,希望从C直接返回A并清除B
Intent intent = new Intent(this, ActivityA.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
// 此时栈变为:A(栈顶),B和C被销毁
经验提醒:在Android 10及以上版本,系统对后台Activity启动进行了限制。如果应用在后台尝试启动Activity,会抛出异常。因此,需要从后台唤起界面时,应使用通知(Notification)的full-screen intent,而不是直接startActivity()。
4.3 实战案例:电商App的页面栈优化
假设我们正在开发一个电商应用,包含以下页面:首页(MainActivity)、商品列表(ListActivity)、商品详情(DetailActivity)、购物车(CartActivity)、结算(CheckoutActivity)。用户的操作路径为:首页 → 列表 → 详情 → 加入购物车 → 购物车 → 结算。在这个过程中,我们需要解决两个核心问题:避免页面重复创建和优化返回路径。
4.3.1 问题分析与方案设计
初始现象:用户从首页进入列表,再进入详情,然后返回列表,再次点击另一个商品进入详情。此时栈中已有两个DetailActivity实例。如果用户连续操作,栈会越来越深,导致内存占用过高,且返回时需要多次点击才能回到首页。
关键输入数据:通过adb shell dumpsys activity activities命令可以查看当前任务栈的Activity实例数量。我们发现,在连续浏览10个商品后,栈中DetailActivity实例数达到10个,内存占用增加了约15MB。
分析判断过程:DetailActivity属于高频访问页面,且每次展示的数据不同,不能直接复用同一个实例。但我们可以通过singleTop模式避免栈顶重复创建,同时结合FLAG_ACTIVITY_CLEAR_TOP在从详情返回列表时清理多余的详情实例。
4.3.2 具体实施步骤
- 在AndroidManifest.xml中为DetailActivity设置
android:launchMode="singleTop"。 - 在ListActivity中跳转详情时,使用标准Intent;在DetailActivity中跳转另一个商品详情时,由于栈顶已是DetailActivity,系统会复用实例并调用onNewIntent()。
- 在onNewIntent()中更新页面数据:
setIntent(intent); refreshData(intent.getStringExtra("product_id")); - 在购物车页面点击"去结算"时,使用
FLAG_ACTIVITY_CLEAR_TOP清除结算页之上的所有页面,确保用户从结算页返回时直接回到购物车。 - 在CheckoutActivity中重写onBackPressed(),如果支付成功,则跳转到首页并清除整个任务栈。
// CheckoutActivity 返回处理
@Override
public void onBackPressed() {
if (isPaymentSuccess) {
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
} else {
super.onBackPressed();
}
}
4.3.3 结果对比与复盘
优化后,通过dumpsys命令验证:连续浏览20个商品,栈中DetailActivity实例数始终为1(栈顶复用),内存占用稳定。用户从详情返回列表时,只需一次返回操作。从结算页返回时,直接回到首页,符合电商"支付完成即结束购物流程"的业务逻辑。
风险与代价:singleTop模式要求开发者必须处理onNewIntent()方法,否则页面数据不会更新。此外,FLAG_ACTIVITY_CLEAR_TOP会销毁中间Activity,如果这些Activity中有未保存的数据,需要提前处理。
复盘经验:启动模式的选择不能一概而论,需要结合页面的业务属性(是否高频、是否唯一)和用户操作路径来综合判断。建议在项目初期就制定启动模式规范,避免后期大量返工。
4.4 常见误区与最佳实践总结
在Activity生命周期与任务栈管理方面,开发者经常陷入以下误区:
- 误区一:在onPause()中执行耗时操作。onPause()执行完毕后,新Activity才会显示,耗时操作会导致界面切换卡顿。应改为在onStop()中执行。
- 误区二:忽略配置变更(如屏幕旋转)时的状态保存。默认情况下,配置变更会导致Activity重建,如果不保存状态,用户输入的内容会丢失。
- 误区三:滥用singleTask模式。singleTask会清除栈顶其他Activity,如果目标Activity不是"主界面"级别的页面,会导致用户返回时页面丢失。
- 误区四:不处理onNewIntent()。使用singleTop或singleTask模式后,如果Activity被复用,系统会调用onNewIntent()而不是onCreate(),如果不处理,页面数据不会更新。
- 误区五:在onDestroy()中保存数据。onDestroy()可能不被调用(如系统直接杀死进程),应使用onStop()或更可靠的持久化方案。
最佳实践清单:① 使用ViewModel保存业务数据,使用onSaveInstanceState()保存UI状态;② 在onStart()中注册广播,在onStop()中注销;③ 在onResume()中启动动画,在onPause()中停止;④ 为每个页面明确指定启动模式,并在代码评审时检查;⑤ 使用adb命令定期检查任务栈深度,防止栈溢出。
总结而言,Activity的生命周期管理是Android开发的基础功,它直接影响应用的稳定性、内存占用和用户体验。掌握生命周期回调的准确时机,理解启动模式与任务栈的配合关系,能够帮助开发者构建出结构清晰、行为可预测的应用。在实际项目中,建议将生命周期相关的逻辑封装到基类或自定义组件中,减少重复代码,同时通过单元测试和UI测试验证生命周期行为的正确性。