2. SharedPreferences 原理与局限:键值对存储、线程安全、同步问题
SharedPreferences(以下简称 SP)是 Android 最经典的本地轻量级存储方案,以键值对形式存储应用配置或状态数据。在架构选型中,SP 通常被归类为“单进程、异步或同步写、全量加载”的存储引擎。本章将从数据结构、读写流程、线程与进程安全边界以及已知的同步陷阱进行剖析,帮助你在项目中准确评估其适用边界。
2.1 方案边界与分层职责
适用边界:SP 用于保存少量(通常 < 50 个 key)、结构简单、不追求强一致性的配置类数据,如用户偏好开关、上次启动时间、应用引导状态。
不适用场景:跨进程共享配置、频繁写入(每秒 > 10 次)、大文本或 blob 存储(> 100 KB)、需要事务回滚的场景。
不适用场景:跨进程共享配置、频繁写入(每秒 > 10 次)、大文本或 blob 存储(> 100 KB)、需要事务回滚的场景。
2.2 写入流程与数据流
SP 写入操作分为 commit()(同步写)和 apply()(异步写)。其内部数据流可通过以下结构描述。
2.3 线程安全现状
SP 在单进程内的 getXX() 和 putXX() 操作在 EditorImpl 中使用了 ReentrantReadWriteLock,因此读读不互斥,读写与写写互斥。但这里的“线程安全”仅针对单进程、单 SP 实例而言。
陷阱 1:如果通过
陷阱 2:apply() 的异步写回发生在
Context.getSharedPreferences(name, MODE_PRIVATE) 获取了多个实例操作同一文件,锁不共享,可能出现并发覆盖。陷阱 2:apply() 的异步写回发生在
QueuedWork 线程池中,但 ActivityThread 的 handleStopActivity 会等待所有 apply() 完成,这个等待如果超时(典型场景:主线程大量 apply() 未刷盘),会导致 ANR。
2.4 同步问题与经典崩溃场景
| 场景 | 表现 | 根因 |
|---|---|---|
| 多线程并发 apply() | 偶发数据覆盖 | QueuedWork 提交顺序非严格 FIFO |
| 主线程 commit() 时 IO 慢 | 界面卡顿 / ANR | commit() 直接调用 FileOutputStream 写入 |
| 进程被杀时 apply() 未回调 | 数据丢失 | 异步写盘未完成,系统未保证持久化 |
| 跨进程 MODE_MULTI_PROCESS (已废弃) | 读到的数据为旧值 | 多进程无锁同步机制 |
| Android 8+ 原子写入异常 | XML 文件损坏 | 边写边读导致 half-write |
2.5 选型权衡与风险点清单
选型建议:
- 配置项 < 20 个,key 长度 < 64 字符,使用 apply() 即可;
- 需要可靠持久化的开关(如是否同意隐私协议),建议配合
- 多进程共享数据 → 改用 ContentProvider / DataStore / MMKV;
- 频繁写入(如日志打点计数)→ 不要用 SP,会阻塞其他读操作。
- 配置项 < 20 个,key 长度 < 64 字符,使用 apply() 即可;
- 需要可靠持久化的开关(如是否同意隐私协议),建议配合
commit() 并在后台线程调用;- 多进程共享数据 → 改用 ContentProvider / DataStore / MMKV;
- 频繁写入(如日志打点计数)→ 不要用 SP,会阻塞其他读操作。
2.6 落地步骤与验收标准
落地步骤
- 确认目标数据是否满足“轻量、单进程、不要求强一致性”的边界;
- 使用
getSharedPreferences(name, MODE_PRIVATE)获取实例,避免传入MODE_MULTI_PROCESS; - 所有写操作统一在后台线程池(如
Dispatchers.IO)中执行commit(); - 避免在
onPause/onStop中调用apply(),防止 ANR; - 定期评估数据量,超过 50 个 key 或单 key 大于 1 KB 时考虑迁移至 DataStore。
验收标准
- 配置写入后立即读取,100% 返回最新值;
- 在主线程不做任何 commit() 调用,无卡顿报告;
- 进程正常关闭时,最后一条 apply() 在 500ms 内完成落盘;
- 并发写入 100 次后数据完整无丢失(可通过 instrumentation 测试验证)。
2.7 与 DataStore 的简单对比(预判)
作为本章收尾,下表列出 SP 与替代方案 DataStore 在关键维度的差异,为后续章节的迁移决策提供依据。
| 维度 | SharedPreferences | DataStore (Preferences) |
|---|---|---|
| 数据结构 | XML 全量加载 | Protocol Buffers,增量读取 |
| 写入方式 | 全量覆写 XML | 事务性增量写入 |
| 异步模型 | apply() 不可取消 | Flow / suspend 可取消 |
| 线程安全 | 部分安全(需注意实例单例) | 内置协程,完全线程安全 |
| 主线程风险 | commit() 直接 ANR | 所有操作异步,无 ANR |
本章小结:SharedPreferences 是 Android 历史遗留的轻量存储方案,其读写流程简单但存在明显的同步风险与数据丢失隐患。在架构设计中,SP 应作为“只读配置缓存”而非“可靠持久化”方案。理解其原理与局限,是后续评估 DataStore、MMKV、Robust 等进阶方案的基础。