1. SharedPreferences 概述
使用 SharedPreferences 可以将数据持久化。
适用场景:
- SharedPreferences 可以用来保存相对较小键值对集合。适合保存用户偏好设置(如主题、语言)、简单配置等轻量级键值对数据。
- 不适合存储列表、对象等复杂数据(建议使用 SQLite 或 Room)。
SharedPreferences 对象指向包含键值对的 XML 文件,并提供读写这些键值对的简单方法,支持多种不同数据类型的存储。很方便对某一指定的数据进行读写操作。
每个 SharedPreferences 文件均由框架进行管理,可以是私有文件,也可以是共享文件。
注意: DataStore 是一种现代数据存储解决方案,应代替 SharedPreferences。它基于 Kotlin 协程和 Flow 构建,并克服了 SharedPreferences 的许多缺点。
2. SharedPreferences 的使用
2.1 获取 SharedPreferences 对象
使用 Context 的 getSharedPreferences 方法:
SharedPreferences prefs = context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE);
public abstract SharedPreferences getSharedPreferences(String name, @PreferencesMode int mode);
name:文件标识符,此处使用包名作为标识符。
SharedPreferences 文件存放在
/data/data/\<package name>/shared_prefs/目录下。mode:文件的创建模式,默认模式为 MODE_PRIVATE,创建的文件只能由调用应用程序(或共享同一用户 ID 的所有应用程序)访问。
还可以通过 Activity 的 getPreferences 方法获取,Activity 级别独立配置(文件名为 Activity类名.xml):
public SharedPreferences getPreferences(@Context.PreferencesMode int mode) {
return getSharedPreferences(getLocalClassName(), mode);
}
该方法就是对 SharedPreferences 方法进行了封装。
2.2 SharedPreferences 存储数据
获取 SharedPreferences.Editor 对象,并存储数据
SharedPreferences prefs = context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE);
// 存储不同类型数据
prefs.edit()
.putBoolean("isDarkMode", true)
.putInt("userAge", 25)
.putFloat("rating", 4.5f)
.putLong("lastLogin", System.currentTimeMillis())
.putString("username", "john_doe")
.putStringSet("tags", new HashSet<>(Arrays.asList("sports", "music")))
.apply(); // 异步提交(推荐常规使用)
// .commit(); // 同步提交(返回boolean结果)
得到 SharedPreferences 对象后,调用 SharedPreferences 对象的 edit()方法获取一个 SharedPreferences.Editor 对象,然后就可以向 SharedPreferences.Editor 对象中添加数据。
最后调用 apply 方法将添加的数据提交。apply 方法是异步写入,还可以调用 commit 方法进行同步写入。
2.3 从 SharedPreferences 中读取数据
一系列 get 方法。第一个参数是键,第二个参数是默认值,找不到返回默认值:
SharedPreferences pref = getSharedPreferences("data",MODE_PRIVATE);
boolean isDark = prefs.getBoolean("isDarkMode", false);
int age = prefs.getInt("userAge", 0);
String name = prefs.getString("username", "default_name");
Set<String> tags = prefs.getStringSet("tags", new HashSet<>());
2.4 SharedPreferences 数据变化监听
// 实现监听接口
SharedPreferences.OnSharedPreferenceChangeListener listener =
(sharedPreferences, key) -> {
if ("username".equals(key)) {
String newName = sharedPreferences.getString(key, "");
// 处理用户名变更
}
};
// 注册监听
prefs.registerOnSharedPreferenceChangeListener(listener);
// 取消监听(重要!防止内存泄漏)
@Override
protected void onPause() {
super.onPause();
prefs.unregisterOnSharedPreferenceChangeListener(listener);
}
2.5 SharedPreferences 批量操作与事务处理
SharedPreferences.Editor editor = prefs.edit();
// 批量操作
editor.putBoolean("notifications", true)
.putInt("notification_count", 5)
.remove("obsolete_key") // 删除指定键
.clear(); // 清空所有数据(慎用!)
// 事务提交
if(editor.commit()) { // 同步提交需要结果判断
// 提交成功处理
}
3. SharedPreferences 相关问题分析
3.1 SharedPreferences 是否阻塞主线程?
可能阻塞主线程,取决于使用的提交方法,提交方法有两种 commit() 和 apply()
commit() 方法:
- 同步写入磁盘:调用该方法会立即在当前线程执行磁盘写入操作。
- 可能阻塞主线程:如果在主线程调用该方法,且写入耗时较长(如数据量大或磁盘 I/O 繁忙),会导致主线程卡顿甚至 ANR。
- 返回布尔值
apply() 方法:
- 异步写入磁盘:该方法会先将修改提交到内存,再通过后台线程异步写入磁盘。
- 不直接阻塞主线程:但
apply()的异步写入仍会通过QueuedWork在主线程的ActivityThread中触发Handler任务,可能间接影响主线程性能(如高频调用时)。 - 主线程卡顿:在生命周期事件(如
onPause())中,系统调用QueuedWork.waitToFinish(),强制等待所有异步写入完成,若文件过大可能导致卡顿。 - 无返回值
避免在主线程调用 commit,优先使用 apply。对于高频或者大数据量的操作,不建议使用 SharedPreferences。
3.2 SharedPreferences 是否线程安全?
内部同步机制:SharedPreferences 是线程安全的。读写操作通过 synchronized 关键字和锁保证线程安全。
3.3 SharedPreferences 是否支持跨进程?
SharedPreferences 不适合跨进程场景,默认模式下,不同进程的 SharedPreferences 实例无法同步数据。
3.4 SharedPreferences 存储支持的数据类型?
SharedPreferences 支持存储基本数据类型,和 set<String> 集合数据类型。
SharedPreferences 不支持对象的存储。
3.5 SharedPreferences 是否类型安全?
SharedPreferences 没有编译时类型检查,开发者需自行保证类型一致。
3.6 SharedPreferences 是否能监听数据变化?
支持监听数据变化,但需注意限制。
- 内存泄漏:需在组件生命周期结束时调用
unregisterOnSharedPreferenceChangeListener()注销监听。 - 主线程回调:监听器的
onSharedPreferenceChanged()默认在主线程触发,需避免耗时操作。