Android SharedPreferences 的使用

510 阅读4分钟

1. SharedPreferences 概述

使用 SharedPreferences 可以将数据持久化。

适用场景

  1. SharedPreferences 可以用来保存相对较小键值对集合。适合保存用户偏好设置(如主题、语言)、简单配置等轻量级键值对数据。
  2. 不适合存储列表、对象等复杂数据(建议使用 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() 方法

  1. 同步写入磁盘:调用该方法会立即在当前线程执行磁盘写入操作。
  2. 可能阻塞主线程:如果在主线程调用该方法,且写入耗时较长(如数据量大或磁盘 I/O 繁忙),会导致主线程卡顿甚至 ANR。
  3. 返回布尔值

apply() 方法

  1. 异步写入磁盘:该方法会先将修改提交到内存,再通过后台线程异步写入磁盘。
  2. 不直接阻塞主线程:但 apply() 的异步写入仍会通过 QueuedWork 在主线程的 ActivityThread 中触发 Handler 任务,可能间接影响主线程性能(如高频调用时)。
  3. 主线程卡顿:在生命周期事件(如 onPause())中,系统调用 QueuedWork.waitToFinish(),强制等待所有异步写入完成,若文件过大可能导致卡顿。
  4. 无返回值

避免在主线程调用 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() 默认在主线程触发,需避免耗时操作。