在 Android 开发的旅程中,你是否曾想过,为什么当你旋转手机屏幕时,应用界面会重新加载?或者当你接听电话时,正在运行的游戏是如何暂停并恢复的?这一切的背后,都是 Activity(活动) 生命周期在起作用。
作为 Android 应用的四大组件之一,Activity 是我们与用户交互的入口。简单来说,我们在屏幕上看到的每一个“界面”,通常都是一个 Activity。这就好比桌面应用程序中的一个窗口,Android 应用正是由一个或多个这样的“窗口”堆叠而成的。
在这篇文章中,我们将深入探讨 Android Activity 的生命周期机制。我们不仅会了解它的基本概念,还会通过构建一个完整的 Demo App 来演示这些状态在代码中是如何流转的。我们将使用 Java 和 Kotlin 两种语言来展示细节,并分享一些在实际开发中处理生命周期状态的最佳实践。
什么是 Activity 生命周期?
在 Android 系统中,Activity 并不是孤立存在的,它们由一个名为“Activity 栈”的后台栈进行管理。每当一个新的 Activity 启动时,它会被压入栈顶,并获得用户焦点。当用户按下“返回”键时,当前的 Activity 会从栈中弹出,并被销毁,而前一个 Activity 则重新浮现。
这种栈机制决定了 Activity 会经历各种不同的状态。我们可以把这些状态大致归纳为以下四种基本形态,理解它们对于编写健壮的应用至关重要:
- 运行状态:这是 Activity 最“风光”的时候。它位于屏幕的前台,处于栈的顶部。此时,用户正在与它进行积极的交互,它拥有系统的全部关注和资源。
- 暂停状态:当 Activity 失去了焦点,但仍然可见时,它就处于暂停状态。这种情况通常发生在一个半透明的对话框或非全屏的 Activity 覆盖在它上面时。虽然失去了焦点,但该 Activity 依然“活着”,系统通常不会轻易杀死它(除非内存极度紧张),因为它需要保持界面信息。
- 停止状态:如果 Activity 被另一个 Activity 完全遮蔽,它就进入了停止状态。此时,它的窗口被隐藏,但它依然保留着所有的状态信息。不过,由于它不再可见,它往往成为系统回收内存的首选目标。如果系统需要资源,可能会直接杀死这个处于停止状态的进程。
- 销毁状态:这是生命的终点。系统通过要求 Activity 结束(调用
finish())或直接杀死其进程来将其从内存中移除。如果用户想再次看到这个界面,Activity 必须完全重新启动,并恢复到之前的状态。
生命周期回调方法详解
为了应对上述状态的变化,Android 为我们提供了一组由 7 个核心方法组成的生命周期回调函数。每当 Activity 的状态发生改变时,系统都会自动调用相应的方法。下图展示了这些方法之间的迁移路径,这是我们开发 Android 应用的“航海图”。
(此处应有一张展示 Android Activity 生命周期各状态切换的示意图,描绘 onCreate -> onStart -> onResume -> onPause -> onStop -> onDestroy 的流向)
让我们逐一解剖这些方法,看看在每一个阶段,我们到底该做些什么,又该避免做什么。
#### 1. onCreate() – 一切的开始
这是 Activity 的“诞生时刻”。当 Activity 首次被创建时,系统会调用此方法。这是整个生命周期中第一个被执行的方法。在这里,我们需要完成所有基本的初始化工作。
主要职责:
- 加载布局:通过
setContentView()将 XML 布局文件与 Activity 关联起来,这是界面显示的前提。 - 初始化组件:找到视图控件,设置监听器。
- 绑定数据:如果需要,将数据连接到列表或其他视图。
- 恢复状态:如果 Activity 之前被销毁过(例如因为屏幕旋转),
savedInstanceState参数会包含之前保存的状态数据,我们可以在这里用它来恢复用户的输入或滚动位置。
代码示例:
在 onCreate() 中,我们通常会看到类似这样的代码。注意我们使用 Toast 来提示方法被调用,这在我们的 Demo App 中非常有用,能让我们直观地看到生命周期流程。
Java 实现:
import android.os.Bundle;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 1. 加载 UI 布局
setContentView(R.layout.activity_main);
// 2. 显示提示,验证生命周期
Toast.makeText(this, "onCreate Called: 初始化完成", Toast.LENGTH_LONG).show();
// 在这里处理 savedInstanceState 以恢复状态
if (savedInstanceState != null) {
// 恢复之前保存的数据
}
}
}
Kotlin 实现:
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 1. 加载 UI 布局
setContentView(R.layout.activity_main)
// 2. 显示提示,验证生命周期
Toast.makeText(this, "onCreate Called: 初始化完成", Toast.LENGTH_LONG).show()
}
}
最佳实践:
注意,onCreate() 方法运行在主线程上。这意味着我们不能在这里执行耗时的操作,比如网络请求或数据库查询,否则会导致应用无响应(ANR)。所有的繁重工作都应该交给后台线程去处理。
#### 2. onStart() – 准备与用户见面
当 INLINECODEb11ef698 执行完毕后,或者当 Activity 从后台回到前台时,INLINECODE953fcdfa 会被调用。此时,Activity 进入了“已启动”状态,并变得可见。注意,仅仅是可见,用户还不能与它进行交互(比如点击按钮),它还没有获得焦点。
主要职责:
- 注册那些监听 UI 变化的广播接收器。
- 启动那些仅在界面可见时才需要运行的服务。
虽然在此阶段 Activity 还没有与用户交互,但 INLINECODE1379bcaa 是非常短暂的。紧随其后,系统会调用 INLINECODEae083eb2。
代码示例:
Java 实现:
@Override
protected void onStart() {
super.onStart();
// Activity 此时可见,但不可交互
Toast.makeText(this, "onStart Called: 界面可见", Toast.LENGTH_SHORT).show();
// 举例:在这里注册一个 BroadcastReceiver 来监听屏幕变化
// registerReceiver(screenReceiver, intentFilter);
}
Kotlin 实现:
override fun onStart() {
super.onStart()
// Activity 此时可见,但不可交互
Toast.makeText(this, "onStart Called: 界面可见", Toast.LENGTH_SHORT).show()
}
#### 3. onResume() – 登上舞台中心
这是 Activity 的“高光时刻”。当 onResume() 被调用时,Activity 位于栈顶,获得了用户的焦点,用户可以自由地点击、滑动和输入。应用会一直停留在这个状态,直到某些事情发生将其打断(例如来电话了),或者用户跳转到另一个 Activity。
主要职责:
- 开启动画。
- 初始化只在 Activity 运行时需要的组件,比如打开相机连接。
- 注册任何必须实时更新的监听器。
代码示例:
Java 实现:
@Override
protected void onResume() {
super.onResume();
// 获得焦点,用户可以交互了
Toast.makeText(this, "onResume Called: 开始交互", Toast.LENGTH_SHORT).show();
// 举例:如果我们的 App 需要持续获取 GPS 位置,在这里开启定位监听
}
Kotlin 实现:
override fun onResume() {
super.onResume()
// 获得焦点,用户可以交互了
Toast.makeText(this, "onResume Called: 开始交互", Toast.LENGTH_SHORT).show()
}
性能优化提示:
在 onResume() 中,我们应该保持代码极其轻量级。任何稍微耗时的操作都会延迟界面显示的流畅度,给用户带来“卡顿”的感觉。这是最需要保持灵敏响应的阶段。
#### 4. onPause() – 暂时退场
这是第一个可中断生命周期的方法。当系统准备去启动或恢复另一个 Activity 时,会先调用当前 Activity 的 onPause()。此时,Activity 仍然部分可见(通常是被半透明的对话框遮挡),但它已经失去了焦点。
主要职责:
- 停止动画和 CPU 密集型的计算,以节省电量。
- 保存关键数据。如果用户在这个状态下离开了应用(例如长按 Home 键,或直接关机),INLINECODE5f30af0d 和 INLINECODE56ff9c02 可能不会被保证调用。因此,最关键的数据(如用户正在编辑的草稿)必须在这里保存。
- 释放系统资源,比如相机传感器。
代码示例:
Java 实现:
@Override
protected void onPause() {
super.onPause();
// 失去焦点,但可能仍可见
Toast.makeText(this, "onPause Called: 暂停中", Toast.LENGTH_SHORT).show();
// 举例:如果正在录制视频,必须在这里停止,否则其他应用无法使用相机
// releaseCamera();
}
Kotlin 实现:
override fun onPause() {
super.onPause()
// 失去焦点,但可能仍可见
Toast.makeText(this, "onPause Called: 暂停中", Toast.LENGTH_SHORT).show()
}
警惕陷阱:
初学者常犯的错误是在 INLINECODEcdb22dbf 中执行重量级操作(比如写入数据库)。记住,INLINECODE9b1beca0 方法执行期间,下一个 Activity 无法启动,直到这个方法返回。如果这里耗时太久,会导致下一个 Activity 的加载出现明显的延迟,影响用户体验。
#### 5. onStop() – 隐入幕后
当 Activity 完全不再对用户可见时,onStop() 会被调用。这通常发生在用户跳转到另一个 Activity,或者按下 Home 键回到桌面时。此时,Activity 的窗口已被隐藏。
主要职责:
- 执行更重量级的“清理”工作,比如这里适合执行数据库的写入操作(因为此时 UI 已经不展示了,稍微慢一点用户也感知不到)。
- 保持状态准备:由于 Activity 此时处于“停止”状态,如果内存不足,它可能会被系统杀死。因此,我们应当确保所有重要的状态都已经准备好,以便在 INLINECODEf04d01b1 或 INLINECODEdbd67b8f 中恢复。
代码示例:
Java 实现:
@Override
protected void onStop() {
super.onStop();
// Activity 完全隐藏
Toast.makeText(this, "onStop Called: 界面已隐藏", Toast.LENGTH_SHORT).show();
// 举例:将当前的浏览历史记录写入数据库
// saveCurrentStateToDatabase();
}
Kotlin 实现:
override fun onStop() {
super.onStop()
// Activity 完全隐藏
Toast.makeText(this, "onStop Called: 界面已隐藏", Toast.LENGTH_SHORT).show()
}
#### 6. onDestroy() – 最后的告别
这是 Activity 被销毁前的最后一次调用。这发生在以下两种情况:
- 用户主动按下了返回键,Activity 正在结束。
- 系统因为内存不足(配置变更等原因)正在临时销毁这个 Activity 实例。
主要职责:
- 释放所有剩余的资源,比如关闭网络连接,注销所有的广播接收器。
- 清理遗留的对象,防止内存泄漏。
代码示例:
Java 实现:
@Override
protected void onDestroy() {
super.onDestroy();
// Activity 即将被移除
Toast.makeText(this, "onDestroy Called: 正在销毁", Toast.LENGTH_SHORT).show();
// 举例:确保所有的 Handler 回发消息都被移除,防止内存泄漏
// mHandler.removeCallbacksAndMessages(null);
}
Kotlin 实现:
override fun onDestroy() {
super.onDestroy()
// Activity 即将被移除
Toast.makeText(this, "onDestroy Called: 正在销毁", Toast.LENGTH_SHORT).show()
}
实战建议:
在 INLINECODE3546ad2c 中,你需要调用 INLINECODE0a4ed811 方法来判断 Activity 是正在正常结束,还是正在被系统销毁(例如因为屏幕旋转)。如果是系统配置变更导致的销毁,你可能希望保留一些不需要在 onSaveInstanceState() 中保存的大数据(比如下载任务的引用)。
进阶:onRestart() – 重生的入口
除了上面介绍的 6 个主要方法外,还有一个 INLINECODEc468556b 方法。当 Activity 从停止状态恢复为活动状态时,它会被调用。也就是说,它紧跟在 INLINECODE59f6edb5 之前,出现在以下流程中:onStop() -> onRestart() -> onStart() -> onResume()。
这使得我们有机会在 Activity 重新出现之前做一些特殊的检查或准备工作,比如刷新某些不需要在后台实时更新的数据。
完整的 Demo App 构建
为了让你能更直观地感受这一切,我们可以在 Android Studio 中创建一个简单的项目。
- 新建项目:选择 “Empty Activity”。
- 编写代码:在 INLINECODE1037d3a1 中重写所有的生命周期回调方法,并在每个方法中添加 INLINECODEd9ae5666 提示和
Log.d()日志。
当你在模拟器或真机上运行这个 App 时,你可以尝试以下操作来观察 Logcat 日志和 Toast 提示:
- 启动应用:你会看到 INLINECODEdf250491 -> INLINECODE6933316e ->
onResume()。 - 按下 Home 键:Activity 消失,你会看到 INLINECODE884ff6d8 -> INLINECODEe2555394。
- 从后台恢复应用:你会看到 INLINECODEf0ded2a3 -> INLINECODEb4946fd3 ->
onResume()。 - 按下返回键:App 退出,你会看到 INLINECODE9f622794 -> INLINECODEed75e260 ->
onDestroy()。
总结与常见陷阱
掌握 Activity 生命周期是通往高级 Android 开发者的必经之路。让我们总结一下关键点:
- 使用 onCreate() 初始化:这是搭建界面的地方。
- 使用 onResume/onPause 处理交互:这是处理用户输入和资源争夺(如相机)的关键时刻。
- 使用 onStop/onDestroy 清理资源:不要因为忘记释放资源而导致内存泄漏。
- 不要在 onPause 中做重活:这会导致切换应用时的卡顿。
你可能遇到的问题(FAQ):
- 为什么我的数据在旋转屏幕后丢失了?
因为默认情况下,屏幕旋转会导致 Activity 完全销毁并重建。你需要使用 onSaveInstanceState() 或 ViewModel 来保存数据。这在处理敏感数据或大型对象(如 Bitmaps)时尤为重要。记得在 onCreate() 中检查 savedInstanceState 是否为 null,如果非空,说明系统为你保留了状态,你可以取出之前保存的数据。
- 何时使用“单任务”或“单实例”启动模式?
这属于进阶内容,但了解它有助于理解栈管理。如果你希望某个 Activity 在整个应用中只有一个实例(例如浏览器应用),你可以在清单文件中设置它的 launchMode。但这会影响生命周期方法的调用顺序,需要谨慎使用。
希望这篇指南能帮助你更深入地理解 Android 的运行机制。继续探索,享受构建 Android 应用的乐趣吧!