Android学习笔记:Activity生命周期&数据存储
1. Activity生命周期核心概念
为什么生命周期如此重要?
它决定了应用在不同状态下的行为,是处理内存管理、数据保存、UI更新的关键。理解它,你就能写出不崩溃、不丢失数据的Android应用。
stateDiagram-v2
[*] --> Created
Created --> Started
Started --> Resumed
Resumed --> Paused
Paused --> Stopped
Stopped --> Destroyed
Paused --> Resumed
Stopped --> Started
Resumed --> Stopped
Stopped --> Destroyed
Paused --> Destroyed
关键状态说明:
| 状态 | 说明 | 用户可见 | 为什么重要 |
|---|---|---|---|
Created | Activity创建完成 | ❌ | 初始化UI、数据 |
Started | Activity可见但未获得焦点 | ✅ | 准备显示内容 |
Resumed | Activity获得焦点,用户可交互 | ✅ | 核心交互状态 |
Paused | Activity失去焦点但可见(如弹出对话框) | ✅ | 保存关键数据 |
Stopped | Activity完全不可见(如进入后台) | ❌ | 释放非关键资源 |
Destroyed | Activity被销毁 | ❌ | 释放所有资源 |
💡 关键洞察:
用户不会等待应用完成生命周期,必须在onPause()保存关键数据,否则会丢失!
2. 生命周期方法详解
每个方法都是一个"关键时刻",必须正确处理
| 方法 | 调用时机 | 做什么 | 为什么 |
|---|---|---|---|
onCreate() | Activity首次创建 | 初始化UI、数据 | 必须 |
onStart() | Activity可见但未获得焦点 | 无需操作(通常) | 通常无需处理 |
onResume() | Activity获得焦点,用户可交互 | 重启动画、恢复数据 | 关键交互点 |
onPause() | 失去焦点(如弹出对话框) | 保存关键数据、暂停动画 | 防止数据丢失 |
onStop() | 完全不可见(进入后台) | 释放非关键资源 | 优化内存 |
onDestroy() | Activity被销毁 | 释放所有资源 | 必须 |
⚠️ 新手常见错误:
// 错误:在onPause()中保存数据,但未在onResume()恢复 @Override protected void onPause() { // 保存数据 SharedPreferences.Editor editor = prefs.edit(); editor.putString("text", textView.getText().toString()); editor.apply(); } // 未在onResume()恢复数据!
3. 实战:生命周期日志监控
步骤1:修改MainActivity
public class MainActivity extends AppCompatActivity {
private static final String TAG = "ActivityLifecycle";
@Override
protected void onCreate(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 onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy()");
}
}
步骤2:运行并观察Logcat
- 运行应用
- 点击Home键(进入后台)
- 从最近任务列表重新打开应用
- 观察Logcat输出
典型输出:
D/ActivityLifecycle: onCreate()
D/ActivityLifecycle: onStart()
D/ActivityLifecycle: onResume()
// 点击Home键
D/ActivityLifecycle: onPause()
D/ActivityLifecycle: onStop()
// 重新打开应用
D/ActivityLifecycle: onStart()
D/ActivityLifecycle: onResume()
💡 关键发现:
每次从后台回来,都会触发onStart()和onResume(),但不会重新调用onCreate()。这就是为什么数据保存在onPause()而非onCreate()。
4. 生命周期实战场景
场景1:保存用户输入
// 在onPause()中保存数据
@Override
protected void onPause() {
super.onPause();
// 保存输入框内容
SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putString("user_input", editText.getText().toString());
editor.apply();
}
场景2:恢复用户输入
// 在onCreate()中恢复数据
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 恢复输入
SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE);
String savedInput = prefs.getString("user_input", "");
editText.setText(savedInput);
}
场景3:暂停/恢复媒体播放
private MediaPlayer mediaPlayer;
@Override
protected void onResume() {
super.onResume();
if (mediaPlayer != null && !mediaPlayer.isPlaying()) {
mediaPlayer.start(); // 恢复播放
}
}
@Override
protected void onPause() {
super.onPause();
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause(); // 暂停播放
}
}
✅ 避坑指南:
🚫 不要在
onCreate()中保存关键数据(会被重复调用)
✅ 应该在onPause()中保存关键数据
🚫 不要在onResume()中初始化UI(会重复)
✅ 应该在onCreate()中初始化UI不是所有变量都需要声明为成员变量,但 UI 控件(如 EditText、Button)必须是成员变量。
📌 关键代码(数据保存恢复):
// 保存数据
@Override
protected void onPause() {
super.onPause();
SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putString("input", editText.getText().toString());
editor.apply();
}
// 恢复数据
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE);
String savedInput = prefs.getString("input", "");
editText.setText(savedInput);
}
SharedPreferences 深度解析:Android 持久化存储的基石
SharedPreferences 是 Android 中最常用、最轻量级的本地存储方案,用于保存简单的键值对数据(如用户偏好、登录状态、应用设置等)。
🔑 一、SharedPreferences 的本质
| 特性 | 说明 |
|---|---|
| 本质 | 一个 XML 文件(存储在 /data/data/<package_name>/shared_prefs/) |
| 数据格式 | 键值对(String 键 + String/int/boolean/float/long 值) |
| 存储位置 | 应用私有目录(其他应用无法访问) |
| 数据持久性 | 永久存储(应用卸载后才删除) |
| 适用场景 | 小型数据(<100KB)、非敏感数据(如用户设置) |
💡 类比:SharedPreferences 就像一个加密的用户手册,你把关键设置写在上面,应用每次启动都能读取。
📦 二、SharedPreferences 的核心使用场景
场景 1:保存用户输入
// 保存输入内容(在 onPause 中)
SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE);
prefs.edit().putString("user_input", editText.getText().toString()).apply();
场景 2:保存用户偏好设置
// 保存主题设置
SharedPreferences prefs = getSharedPreferences("settings", MODE_PRIVATE);
prefs.edit().putBoolean("dark_mode", true).apply();
场景 3:记录用户登录状态
// 保存登录状态
SharedPreferences prefs = getSharedPreferences("auth", MODE_PRIVATE);
prefs.edit().putBoolean("is_logged_in", true).apply();
⚙️ 三、SharedPreferences 的核心 API
✅ 1. 获取 SharedPreferences 对象
// 方式 1:默认名称(推荐)
SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE);
// 方式 2:自定义名称
SharedPreferences prefs = getSharedPreferences("user_settings", Context.MODE_PRIVATE);
💡 关键点:
MODE_PRIVATE:唯一安全模式(其他应用无法访问)- 建议用常量定义名称(避免拼写错误):
public static final String PREFS_NAME = "app_prefs";
✅ 2. 保存数据(安全写入)
// 获取编辑器(不推荐直接用 edit())
SharedPreferences.Editor editor = prefs.edit();
// 写入数据(推荐使用 apply() 而不是 commit())
editor.putString("username", "张三");
editor.putInt("age", 25);
editor.putBoolean("is_active", true);
editor.apply(); // ✅ 异步写入,不阻塞主线程
💡 为什么用
apply()而不是commit()?
方法 特点 适用场景 apply()异步(后台线程) 99% 场景(推荐) commit()同步(主线程阻塞) 仅当需要立即确认写入时
✅ 3. 读取数据
// 读取字符串(提供默认值)
String username = prefs.getString("username", "游客");
// 读取整数(提供默认值)
int age = prefs.getInt("age", 0);
// 读取布尔值(提供默认值)
boolean isActive = prefs.getBoolean("is_active", false);
⚠️ 四、常见错误及解决方案
❌ 错误 1:忘记检查默认值
// 错误:未提供默认值
String username = prefs.getString("username", null); // 可能返回 null
editText.setText(username); // NullPointerException!
// 正确:提供默认值
String username = prefs.getString("username", "游客");
editText.setText(username);
❌ 错误 2:使用 commit() 阻塞主线程
// 错误:同步写入,可能卡顿
prefs.edit().putString("user_input", text).commit();
// 正确:使用 apply() 异步写入
prefs.edit().putString("user_input", text).apply();
❌ 错误 3:键名拼写错误
// 错误:键名不一致
prefs.edit().putString("user_input", text).apply();
// 获取时拼错
String saved = prefs.getString("user_input2", ""); // 会返回默认值
✅ 解决方案:使用常量定义键名
public static final String KEY_USER_INPUT = "user_input";
// 保存
prefs.edit().putString(KEY_USER_INPUT, text).apply();
// 读取
String saved = prefs.getString(KEY_USER_INPUT, "");
📊 五、SharedPreferences vs Bundle:关键区别
| 特性 | SharedPreferences | Bundle |
|---|---|---|
| 生命周期 | 永久存储(应用关闭后仍保留) | 临时存储(仅在 Activity 生命周期内有效) |
| 数据范围 | 应用级全局(所有 Activity 可访问) | 组件级(仅在当前 Activity/Fragment 内有效) |
| 使用场景 | 保存用户偏好、登录状态等 | 屏幕旋转时临时保存数据 |
| 数据大小 | 无严格限制(但建议 <100KB) | 限制 ~1MB |
| 恢复时机 | 应用启动时自动恢复 | Activity 重建时(如 onCreate/onSaveInstanceState) |
🌟 六、最佳实践
✅ 1. 使用常量定义键名(避免拼写错误)
public class MainActivity extends AppCompatActivity {
public static final String PREFS_NAME = "app_prefs";
public static final String KEY_USER_INPUT = "user_input";
@Override
protected void onCreate(Bundle savedInstanceState) {
// 从 SharedPreferences 读取
SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
String savedInput = prefs.getString(KEY_USER_INPUT, "");
editText.setText(savedInput);
}
}
✅ 2. 优先使用 apply()(避免主线程阻塞)
// ✅ 正确:异步写入
SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
prefs.edit().putString(KEY_USER_INPUT, editText.getText().toString()).apply();
✅ 3. 保存前检查数据
// ✅ 安全写入
if (editText != null) {
String text = editText.getText().toString();
prefs.edit().putString(KEY_USER_INPUT, text).apply();
}
✅ 4. 读取时提供默认值
// ✅ 安全读取
String savedInput = prefs.getString(KEY_USER_INPUT, "");
🔥 八、高级技巧:原子操作与线程安全
1. 为什么需要原子操作?
- 多个线程同时修改 SharedPreferences 会导致数据覆盖
- Android 4.0+ 的
apply()是线程安全的(但不是原子操作)
2. 保证原子性的方案
// 方案 1:使用 SharedPreferences.Editor 的 apply()(Android 4.0+ 推荐)
prefs.edit().putString("key", "value").apply();
// 方案 2:使用锁(仅当需要严格原子性时)
synchronized (prefs) {
prefs.edit().putString("key", "value").apply();
}
💡 重要提示:对于大多数应用,
apply()已足够安全,无需额外加锁。
📌 九、常见问题解答
❓ 问题 1:SharedPreferences 适合存储敏感数据吗?
❌ 不适合!它不加密,敏感数据应使用:
EncryptedSharedPreferences(Android Jetpack)- 数据库加密(Room)
❓ 问题 2:SharedPreferences 可以存储对象吗?
❌ 不能直接存储对象,但可以通过以下方式:
// 方式 1:序列化(不推荐,效率低)
String json = new Gson().toJson(user);
prefs.edit().putString("user", json).apply();
// 方式 2:使用 Parcelable(推荐,但需自己实现)
❓ 问题 3:如何删除 SharedPreferences?
// 删除整个 SharedPreferences
getSharedPreferences(PREFS_NAME, MODE_PRIVATE).edit().clear().apply();
// 删除单个键
getSharedPreferences(PREFS_NAME, MODE_PRIVATE).edit().remove("key").apply();
✅ 十、总结:SharedPreferences 的关键点
| 问题 | 答案 |
|---|---|
| 为什么用 SharedPreferences? | 保存小量、持久化的键值对数据(如用户偏好) |
| 如何避免崩溃? | 1. 检查 editText 是否为 null2. 提供默认值3. 使用常量键名 |
apply() vs commit() | 始终用 apply()(异步,不阻塞主线程) |
| 与 Bundle 的关系 | Bundle 临时保存,SharedPreferences 永久保存 |
| 安全使用 | 1. 用常量定义键名2. 提供默认值3. 使用 apply() |
📚 官方文档支持
Android Developers - SharedPreferences
"SharedPreferences is a simple way to store key-value pairs of primitive data types. It's ideal for storing small amounts of data, such as user preferences."
✅ 结论
SharedPreferences 是 Android 中最基础、最安全的持久化存储方案。
无需再担心 NullPointerException 或数据丢失问题!
💡 记住:
apply()+ 常量键名 + 默认值 = 安全可靠的 SharedPreferences 使用
最终代码
package com.example.singlenews;
import androidx.appcompat.app.AppCompatActivity;
import android.content.SharedPreferences;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.util.Log;
import android.widget.EditText;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "ActivityLifecycle";
public static final String PREFS_NAME = "app_prefs";
public static final String USER_INPUT = "user_input";
private EditText editText;
private MediaPlayer mediaPlayer;
// Activity类的onCreate方法重写。这是Activity生命周期的第一个被调用的核心方法
@Override // 当前方法覆盖父类(Activity)中定义的同名方法。重写生命周期方法,以实现自定义逻辑
protected void onCreate(Bundle savedInstanceState) { // onCreate当Activity首次创建,系统自动调用此方法。
/**
* Bundle savedInstanceState:
* 参数,用于恢复之前保存的状态(如屏幕旋转、内存不足时Activity被销毁重建)
* 如果Activity是首次启动,savedInstanceState 为 null
* 如果Activity是重建,系统会传入之前通过onSaveInstanceState()保存的数据
*
* Bundle 是 Android 开发中最基础、最常用的数据传递的键值对容器,用于在组件之间(Activity、Fragment、Service 等)通过 Intent安全地传递数据。
*/
super.onCreate(savedInstanceState); // 调用父类(Activity)的onCreate()方法。通过父类完成Activity的核心初始化。必须放在自定义之前。
setContentView(R.layout.activity_main); // 加载并设置Activity的UI布局
/**
* R:Android自动生成的资源引用类
* layout:资源类型(布局文件)
* activity_main:布局文件名(存在于res/layout目录下activity_main.xml文件)
*/
// Activity显示界面的唯一入口。系统将activity_main中的UI元素渲染到屏幕上。
// 后续操作(如初始化控件、设置事件监听)通常在此方法中继续编写。
// 所有Activity都必须重写onCreate!
Log.d(TAG, "onCreate()");
editText = findViewById(R.id.edit_text);
// 恢复数据
// SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE);
SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
// String savedInput = prefs.getString("user_input", "");
String savedInput = prefs.getString(USER_INPUT, "");
editText.setText(savedInput);
/**
* SharedPreferences
* SharedPreferences 是 Android 中最常用、最轻量级的本地存储方案,用于保存简单的键值对数据(如用户偏好、登录状态、应用设置等)。
*/
}
@Override
protected void onStart() {
super.onStart();
Log.d(TAG, "onStart()");
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume()");
// 恢复播放
if(mediaPlayer != null && !mediaPlayer.isPlaying()){
mediaPlayer.start();
}
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "onPause()");
// 保存输入框内容
// SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE);
SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
// editor.putString("user_input", editText.getText().toString());
editor.putString(USER_INPUT, editText.getText().toString());
editor.apply(); // apply() 是异步安全的,不会阻塞主线程
// 暂停播放
if(mediaPlayer != null && mediaPlayer.isPlaying()){
mediaPlayer.pause();
}
}
@Override
protected void onStop() {
super.onStop();
Log.d(TAG, "onStop()");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy()");
}
}