第1章 持久化方案全景与选型依据
Android 提供了多种持久化方案,每种方案在数据量级、读写性能、类型安全、线程安全与开发成本上各有侧重。选型的核心是在“数据复杂度”、“访问频率”、“并发场景”和“生命周期”四个维度上做权衡。本章通过全景对比、性能基线与数据量级限制,帮助建立选型决策框架。
核心原则:持久化方案的选择应在需求阶段确定,尽量避免项目中期从 SharedPreferences 迁移到 Room 或从 SQLite 迁移到 DataStore 带来的重构成本。
1.1 主流持久化方案一览
下表列出了 Android 开发中最常用的六种持久化方式、核心实现机制、数据格式与典型用途。
| 方案 | 底层机制 | 数据类型 | 典型用途 | 线程安全 |
|---|---|---|---|---|
| SharedPreferences | XML 文件(键值对) | primitive + String | 轻量配置、用户偏好 | 不支持多进程 |
| DataStore | Protocol Buffers / 文件 | 键值对(Preferences)或 protobuf 对象 | 替代 SP,协程原生支持 | 单进程安全 |
| SQLite | C 库,嵌入式关系数据库 | 关系型表 | 结构化数据、复杂查询 | 需手动管理 |
| Room | SQLite 之上的 ORM 层 | 实体对象(@Entity) | MVP/MVVM 架构下数据库访问 | 内置协程/Flow |
| 文件存储 | 内部/外部存储文件 | 二进制 / 文本 / JSON | 大文件、多媒体、日志 | 无内置保护 |
| 对象存储 | 序列化框架(Kryo/ kotlinx.serialization) | 任意 Java/Kotlin 对象 | 缓存完整业务对象 | 通常不保证 |
1.2 性能基线对比
实测数据(测试设备 Pixel 7,Android 14,ART 运行时)显示不同方案的读写延迟差异可达两个数量级。以下为单条写入/读取操作的近似基线:
| 方案 | 单次写入 (ms) | 单次读取 (ms) | 批量写入 1000 条 (ms) | 内存占用 (基准值) |
|---|---|---|---|---|
| SharedPreferences | 0.5 – 2 | 0.1 – 0.5 | 800 – 1500 | 低(全量加载到内存) |
| DataStore (Preferences) | 0.3 – 1.5 | 0.1 – 0.3 | 400 – 900 | 低 |
| SQLite (WAL 模式) | 0.2 – 1 | 0.1 – 0.5 | 50 – 200 | 中(分页 + 缓存) |
| Room (@Insert) | 0.3 – 2 | 0.2 – 1 | 55 – 250 | 中(含 ORM 映射开销) |
| 文件存储 (写文件) | 1 – 5 | 0.5 – 3 | 300 – 1000 | 极低(直接 I/O) |
| 对象存储 (Kryo) | 0.5 – 2.5 | 0.3 – 1.5 | 200 – 600 | 低(序列化内存开销) |
观察:批量写入场景下 SQLite / Room 因为事务支持远优于键值存储;SharedPreferences 的 apply() 虽然是异步,但本质是单线程写入队列,大量写入仍会阻塞。DataStore 使用协程 + 原子写入解决了 SP 的部分痛点,但数据量超过 100K 键值对时序列化开销明显上升。
1.3 数据量级限制
每个方案在数据增长到一定门槛后会出现性能拐点或稳定性风险:
- SharedPreferences:单文件建议不超过 500 个键值对 或 100KB。超过后 apply() 延迟明显、主线程 ANR 风险升高。Android 8.0 以下版本还会导致全量 XML 解析。
- DataStore:单文件限制在 2MB 以内(实际取决于 Device 内存与序列化库),单键值对值大小不超过 1MB 且总条数建议 10K 以内。超过后首读延迟可能超过 2s。
- SQLite / Room:单库大小可达 2GB(受内核文件系统限制),单表行数建议 不超过 1000 万行 且使用合适索引。性能拐点在 500 万行左右(全表扫描退化)。
- 文件存储:单文件建议 不超过 1GB,超过后随机读写性能严重下降。频繁写日志请使用
FileOutputStream配合缓冲。 - 对象存储:单对象序列化后建议 不超过 10MB,超过后 GC 压力与 OOM 风险显著增加。适合缓存视图模型或 API 响应快照。
1.4 选型决策流程图
以下 SVG 通过分支决策逻辑指导开发者在不同场景下选择最合适的持久化方案。
1.5 常见选型陷阱
陷阱1:在需要复杂查询(JOIN、聚合)的场景中使用 SharedPreferences 或 DataStore,结果导致内存中全量数据过滤,性能差且不易维护。
陷阱2:在跨进程场景(如多进程 Service 共享数据)下使用 SharedPreferences。Android 官方明确不保证 SP 多进程安全,应使用 ContentProvider 或 MMKV。
陷阱3:在频繁写入的日志系统中使用 Room/数据库。事务导致写放大,文件存储 + 轮转日志更合适。
陷阱2:在跨进程场景(如多进程 Service 共享数据)下使用 SharedPreferences。Android 官方明确不保证 SP 多进程安全,应使用 ContentProvider 或 MMKV。
陷阱3:在频繁写入的日志系统中使用 Room/数据库。事务导致写放大,文件存储 + 轮转日志更合适。
1.6 实战建议:快速选型表
| 场景 | 推荐方案 | 补充说明 |
|---|---|---|
| 用户设置(<50项) | DataStore (Preferences) | 协程原生,替代 SP |
| 复杂搜索、过滤 | Room | 支持 SQL 与 Flow 查询 |
| 缓存 API 返回对象 | 对象存储 (Kryo / kotlinx.serialization) | 注意版本兼容与序列化效率 |
| 录音 / 图片 / 视频 | 文件存储 (内部/外部) | 善用 FileProvider 分享 |
| 本地日志系统(>100MB/天) | 文件存储 + 滚动日志 | 参考 Timber 的 DiskTree 实现 |
| 多进程配置共享 | MMKV 或 ContentProvider | 不要使用 SharedPreferences |
选型不是一次性的。随着业务数据量增长、并发需求变化,可能需要在 Room 和 DataStore 之间做混合方案(如 Room 存储核心业务数据,DataStore 存储动态配置)。第2章开始深入剖析各方案的实现原理与代码细节。