Android SharedPreferences 全面介绍

本文详细介绍了Android平台上的SharedPreferences存储机制,包括其定义、操作模式以及如何进行数据的读写操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 简介

SharedPreferences 是 Android 平台提供的一种轻量级本地持久化机制,主要用于保存键值对(Key-Value)格式的数据,如用户设置、登录状态、缓存标识等。它的底层表现形式是一个 XML 文件,默认存储路径为:/data/data/<应用包名>/shared_prefs/<文件名>.xml。 由于其简便高效、易于使用的特点,在需要保存少量数据时非常常用。它的功能类似于 Windows 的 INI 文件或 iOS 中的 UserDefaults。

同时要注意的是,SharedPreferences 适用于存储体积小、结构简单的数据,不适合用于存储大型数据或高频写入的场景。

2. 操作模式

在创建其对象调用 getSharedPreferences(String name, int mode) 方法时,可以指定文件的访问模式,常见有以下几种:

Context.MODE_PRIVATE

  1. 默认模式。
  2. 文件只能被当前应用访问。
  3. 重复写入会覆盖旧内容(非追加)。
  4. 推荐使用,适用于大多数应用场景。

Context.MODE_APPEND(已废弃)

  1. 如果文件存在,将追加内容;否则创建新文件。
  2. 已废弃,并不适用于 SharedPreferences,更常用于 FileOutputStream。
  3. 不推荐使用。

Context.MODE_WORLD_READABLE Context.MODE_WORLD_WRITEABLE(已废弃)

  1. 分别表示该文件可被其他应用读取或写入。
  2. 自 Android 4.2 开始已被废弃,出于安全性考虑,Google 官方强烈不推荐使用。
  3. 可以使用 ContentProvider 实现跨进程通信替代这些模式。

Context.MODE_MULTI_PROCESS(已废弃)

  1. 允许多个进程访问同一个 SharedPreferences 文件。
  2. 实现依赖进程间内存同步,但容易出问题(如缓存不同步)。
  3. 在 Android 6.0 以后基本无效,Google 推荐使用 ContentProvider 或 DataStore 替代。

3. 如何使用 SharedPreferences

3.1 获取 SharedPreferences 实例

// 获取默认的 SharedPreferences(基于包名+_preferences)
val prefs = PreferenceManager.getDefaultSharedPreferences(context)

// 获取自定义名称的 SharedPreferences
val prefs = context.getSharedPreferences("my_prefs", Context.MODE_PRIVATE)

3.2 读取数据

val isFirstLaunch = prefs.getBoolean("first_launch", true)
val username = prefs.getString("username", null)
val age = prefs.getInt("age", 0)

3.3 写入数据

prefs.edit()
.putBoolean("first_launch", false)
.putString("username", "Mahu")
.putInt("age", 39)
.apply() // or .commit()

apply 和 commit 区别:

方法

是否异步

是否有返回值

应用场景

apply()

✅ 是

❌ 无    

推荐使用,效率高,不阻塞主线程

commit()

❌ 否

✅ 有(返回成功/失败)

用于需要立即确认是否写入成功的场景

 3.4 监听数据变化

val listener = SharedPreferences.OnSharedPreferenceChangeListener { prefs, key ->
    Log.d("PrefsChanged", "Key $key changed")
}

// 注册监听
prefs.registerOnSharedPreferenceChangeListener(listener)

// 使用完后解除注册
prefs.unregisterOnSharedPreferenceChangeListener(listener)

3.5 其他常用方法

// 是否存在某个键
prefs.contains("username")
// 移除某个键
prefs.edit().remove("username").apply()
// 清空所有数据
prefs.edit().clear().apply()

4. 源码简析

在 Android 系统中,SharedPreferences 的默认实现类是 SharedPreferencesImpl(源码路径是: frameworks/base/core/java/android/app/SharedPreferencesImpl.java)。它的核心原理是:

  1. 使用 内存中的 HashMap <String, Object>来缓存数据
  2. 使用文件系统中的 XML 文件持久化存储
  3. 通过 apply() 和 commit() 实现数据写入
  4. 使用锁机制确保 线程安全

4.1 数据结构:HashMap 内存缓存

SharedPreferencesImpl 维护一个成员变量 mMap 用于缓存当前内存中的数据内容:

@GuardedBy("mLock")
private Map<String, Object> mMap;  // 实际类型是 HashMap
  1. 每次调用 getXXX() 方法时,都会从 mMap 中读取对应的值。
  2. 初次加载 XML 文件时,SharedPreferencesImpl 会通过 loadFromDisk() 方法将文件内容解析成 Map。
private void startLoadFromDisk() {
    synchronized (mLock) {
        mLoaded = false;
    }
    sLoadExecutor.execute(() -> {
        loadFromDisk();
    });
}

private void loadFromDisk() {
    // ……
    Map<String, Object> map = null;
    // ……
        map = (Map<String, Object>) XmlUtils.readMapXml(str);
    // ……
    synchronized (mLock) {
        // ……
                if (map != null) {
                    mMap = map;
                    mStatTimestamp = stat.st_mtim;
                    mStatSize = stat.st_size;
                } else {
                    mMap = new HashMap<>();
                }
        // ……
    }
}

4.2 写操作流程:Editor 内部机制

当调用 edit() 方法时,会返回一个 EditorImpl 对象,它会记录修改意图,真正的写入操作由 apply() 或 commit() 决定。

@Override
public Editor edit() {
    synchronized (mLock) {
        awaitLoadedLocked();
    }
    return new EditorImpl();
}

4.3 apply() 的执行流程(异步写入)

@Override
public void apply() {
    // 获取 mMap 的锁、将变更应用到 mMap 中、获取变更差异结果
    final MemoryCommitResult mcr = commitToMemory();
    final Runnable awaitCommit = new Runnable() {
            @Override
            public void run() {
                try {
                    mcr.writtenToDiskLatch.await();
                } catch (InterruptedException ignored) { }
                // …
            }
        };
    // …
    // 异步写入
    SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);

    // 通知监听器
    notifyListeners(mcr);
}

apply() 的特点:

  1. 使用线程池异步写入磁盘(不阻塞调用线程)
  2. 依然保证写入前对 mMap 的同步处理(线程安全)
  3. 不等待写入完成便直接通知监听器

4.4 commit() 的执行流程(同步写入)

@Override
public boolean commit() {
    // 获取 mMap 的锁、将变更应用到 mMap 中、获取变更差异结果
    MemoryCommitResult mcr = commitToMemory();
   // 将 mMap 写入 XML 文件、写入完成后释放锁 
    SharedPreferencesImpl.this.enqueueDiskWrite(mcr, null);
    try {
        // 等待写入完成
        mcr.writtenToDiskLatch.await();
    } catch (InterruptedException e) {
        return false;
    }
// 通知监听器
    notifyListeners(mcr);
// 返回写入结果
    return mcr.writeToDiskResult;
}

commit() 的特点:

  1. 同步写入磁盘,会阻塞调用线程
  2. 必须明确写入完成后再将结果通知监听器

4.5 锁机制与线程安全保障

为了避免多线程读写冲突,SharedPreferencesImpl 使用两个关键锁:

锁对象

作用说明

mLock

用于保护 mMap 的初始化、读取和同步写入等

mWritingToDiskLock

用于控制磁盘写操作,防止多线程同时写文件

此外,MemoryCommitResult 对象通过 CountDownLatch 实现 commit() 的同步等待效果。

5. 总结

目前 Google 官方已经推荐使用 Jetpack DataStore 替代 SharedPreferences,以解决其线程安全性差、缺乏类型安全等问题。 但是 SharedPreferences 也并非一无是处,它的显著的优点如:

  1. API 简单易懂、上手快:写法简单、门槛低,适合初学者或快速开发需求;
  2. 兼容性好:Android 1.0 起就内置支持,兼容所有版本;
  3. 性能满足小数据场景:对于配置项、布尔值、小型缓存等,小巧高效;
  4. 不依赖额外依赖库:无需额外引入 Jetpack 或 ProtoBuf,项目体积更小。

因此,在以下场景中,SharedPreferences 依然是合适的选择:

  1. 项目对数据同步和线程安全要求不高;
  2. 仅需保存简单的配置项或布尔标志位;
  3. 项目体积敏感、依赖较少。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值