2. SharedPreferences 原理与局限:键值对存储、线程安全、同步问题

SharedPreferences(以下简称 SP)是 Android 最经典的本地轻量级存储方案,以键值对形式存储应用配置或状态数据。在架构选型中,SP 通常被归类为“单进程、异步或同步写、全量加载”的存储引擎。本章将从数据结构、读写流程、线程与进程安全边界以及已知的同步陷阱进行剖析,帮助你在项目中准确评估其适用边界。

2.1 方案边界与分层职责

适用边界:SP 用于保存少量(通常 < 50 个 key)、结构简单、不追求强一致性的配置类数据,如用户偏好开关、上次启动时间、应用引导状态。
不适用场景:跨进程共享配置、频繁写入(每秒 > 10 次)、大文本或 blob 存储(> 100 KB)、需要事务回滚的场景。

2.2 写入流程与数据流

SP 写入操作分为 commit()(同步写)和 apply()(异步写)。其内部数据流可通过以下结构描述。

SharedPreferences 写入与落盘流程 应用层 edit() / putXX() EditorImpl 内存 Map 缓存 commit() 同步写磁盘 + 通知 apply() 异步写磁盘 + 回调 XML 磁盘文件 全量写入 .bak 备份文件 原子写入保障 getXX() 直接从内存返回 虚线表示:读操作不经过磁盘 核心风险:apply() 未落盘前进程被杀 → 数据丢失 commit() 在主线程调用 → 超过 16ms 可能 ANR

2.3 线程安全现状

SP 在单进程内的 getXX() 和 putXX() 操作在 EditorImpl 中使用了 ReentrantReadWriteLock,因此读读不互斥,读写与写写互斥。但这里的“线程安全”仅针对单进程、单 SP 实例而言。

陷阱 1:如果通过 Context.getSharedPreferences(name, MODE_PRIVATE) 获取了多个实例操作同一文件,锁不共享,可能出现并发覆盖。
陷阱 2:apply() 的异步写回发生在 QueuedWork 线程池中,但 ActivityThread 的 handleStopActivity 会等待所有 apply() 完成,这个等待如果超时(典型场景:主线程大量 apply() 未刷盘),会导致 ANR。

2.4 同步问题与经典崩溃场景

场景表现根因
多线程并发 apply()偶发数据覆盖QueuedWork 提交顺序非严格 FIFO
主线程 commit() 时 IO 慢界面卡顿 / ANRcommit() 直接调用 FileOutputStream 写入
进程被杀时 apply() 未回调数据丢失异步写盘未完成,系统未保证持久化
跨进程 MODE_MULTI_PROCESS (已废弃)读到的数据为旧值多进程无锁同步机制
Android 8+ 原子写入异常XML 文件损坏边写边读导致 half-write

2.5 选型权衡与风险点清单

选型建议:
- 配置项 < 20 个,key 长度 < 64 字符,使用 apply() 即可;
- 需要可靠持久化的开关(如是否同意隐私协议),建议配合 commit() 并在后台线程调用;
- 多进程共享数据 → 改用 ContentProvider / DataStore / MMKV;
- 频繁写入(如日志打点计数)→ 不要用 SP,会阻塞其他读操作。

2.6 落地步骤与验收标准

落地步骤

  1. 确认目标数据是否满足“轻量、单进程、不要求强一致性”的边界;
  2. 使用 getSharedPreferences(name, MODE_PRIVATE) 获取实例,避免传入 MODE_MULTI_PROCESS;
  3. 所有写操作统一在后台线程池(如 Dispatchers.IO)中执行 commit();
  4. 避免在 onPause / onStop 中调用 apply(),防止 ANR;
  5. 定期评估数据量,超过 50 个 key 或单 key 大于 1 KB 时考虑迁移至 DataStore。

验收标准

  • 配置写入后立即读取,100% 返回最新值;
  • 在主线程不做任何 commit() 调用,无卡顿报告;
  • 进程正常关闭时,最后一条 apply() 在 500ms 内完成落盘;
  • 并发写入 100 次后数据完整无丢失(可通过 instrumentation 测试验证)。

2.7 与 DataStore 的简单对比(预判)

作为本章收尾,下表列出 SP 与替代方案 DataStore 在关键维度的差异,为后续章节的迁移决策提供依据。

维度SharedPreferencesDataStore (Preferences)
数据结构XML 全量加载Protocol Buffers,增量读取
写入方式全量覆写 XML事务性增量写入
异步模型apply() 不可取消Flow / suspend 可取消
线程安全部分安全(需注意实例单例)内置协程,完全线程安全
主线程风险commit() 直接 ANR所有操作异步,无 ANR
本章小结:SharedPreferences 是 Android 历史遗留的轻量存储方案,其读写流程简单但存在明显的同步风险与数据丢失隐患。在架构设计中,SP 应作为“只读配置缓存”而非“可靠持久化”方案。理解其原理与局限,是后续评估 DataStore、MMKV、Robust 等进阶方案的基础。