Room 与 DataStore 简介

这一章我们来聊聊 Android 本地存储里两个最常用的家伙——Room 和 DataStore。说白了,它们都是帮你在设备上存数据的东西,但各自的脾气秉性完全不同。我见过不少团队在这两个库之间反复横跳,花了一堆时间最后还是没选对。嗯,今天我就把我的经验掰开揉碎讲给你听。

Room:关系型数据库的老牌玩家

Room 是 Google 官方出的 ORM 框架,底层就是 SQLite。你想想看,SQLite 在移动端已经跑了十几年了,稳定性不用怀疑。Room 帮你把 SQL 表映射成 Kotlin/Java 对象,省去了写大量样板代码的麻烦。

核心特点:强关系、事务性、查询灵活(支持 SQL)、编译时校验。

我个人习惯用 Room 来处理有复杂关联关系的数据,比如订单+商品明细、用户+收藏列表这类场景。举个例子:你在电商 App 里看到的“我的订单”页面,订单表和商品表有一对多关系,用 Room 的 @Relation 注解可以一句查询就拉出来。

@Entity(tableName = "orders")
data class Order(
    @PrimaryKey val id: Long,
    val customerId: Long,
    val totalAmount: Double,
    val status: String
)

@Entity(tableName = "order_items")
data class OrderItem(
    @PrimaryKey val id: Long,
    val orderId: Long,
    val productName: String,
    val quantity: Int
)

data class OrderWithItems(
    @Embedded val order: Order,
    @Relation(
        parentColumn = "id",
        entityColumn = "orderId"
    )
    val items: List<OrderItem>
)

@Dao
interface OrderDao {
    @Transaction
    @Query("SELECT * FROM orders WHERE id = :orderId")
    suspend fun getOrderWithItems(orderId: Long): OrderWithItems
}

避坑注意:我曾经在项目里把联表查询写成了 N+1 问题——Room 默认的懒加载会逐条拉数据,性能直接崩。后来加了个 @Transaction 才解决。记住,凡是涉及嵌套对象查询,务必挂上这个注解。

Room 的另一个优势是支持 增量迁移。什么意思?就是你的数据库表结构变了,Room 可以自动帮你执行迁移脚本,不至于丢数据。我接手过一个老项目,SQLite 迁移全靠手写 onUpgrade(),每次上线都要手动跑脚本,现在换了 Room,省心多了。

DataStore:轻量级键值对的新贵

DataStore 是 SharedPreferences 的替代品。你问为什么谷歌要换掉 SharedPreferences?那玩意儿同步读写会卡 UI、没有类型安全、还容易引发 ANR。DataStore 一上来就用 Kotlin 协程和 Flow,天生就是异步的。

DataStore 分两种:Preferences DataStore 和 Proto DataStore。

类型存储形式适用场景类型安全
Preferences DataStore键值对(可 null)简单配置、用户偏好否(key 用字符串)
Proto DataStoreProtocol Buffers 序列化需要结构化的少量数据是(生成 Kotlin 类)

我建议:如果你的 App 只需要存几十个布尔值或字符串,比如“是否已阅读条款”“通知开关”,直接用 Preferences 就行。但如果要存“用户的主题配色方案”(包含主色、辅色、字体大小)这种稍微结构化的东西,Proto 更合适。

一致性保障:两条路完全不同

这里要敲黑板了。Room 和 DataStore 对“一致性”的理解不太一样。

  • Room 用 SQLite 的事务保证 ACID。你写一条数据,要么全部成功要么全部失败,不存在中间状态。这在离线场景下太重要了——比如用户离线时提交了一条订单,Room 可以保证订单头和明细同时写入,不会出现“有订单无商品”的脏数据。
  • DataStore 不提供多键原子性。它保存和读取都是“整体快照”——你写入时传入一个 Transform 函数,函数内部可以修改多个 key,但整个写入是原子性的。不过注意,它没有跨记录的事务概念。

个人经验:我做过一个离线笔记 App,用户离线时创建笔记和标签。如果用 DataStore 存标签列表,笔记内容用 Room 存,就得自己搞一套分布式事务——别提多坑了。后来我把笔记和标签全放 Room 里,用一个事务搞定。DataStore 只存用户设置和缓存 ,这才安全。

选型快照:谁该上场?

为了让你一眼看清楚,我整理了一个对比表格:

维度RoomDataStore
数据模型关系型(表 + 关系)键值对 / 结构化对象
数据量几十万条没问题建议几百条以内
事务支持ACID 事务,跨表单次写入原子,无跨键事务
同步方式Flow + 协程,支持监听变更Flow 推,协程写
迁移能力版本号 + 迁移脚本无(销毁重写或 Json 迁移)
启动速度略慢(Schema 建立)极快
典型场景离线数据缓存、业务实体用户偏好、配置、轻量缓存

你想想看,如果你的需求是“离线优先”的——比如用户在没网时也能创建数据,等有网了再同步——那我建议用 Room 做主存储,因为它的事务能力和查询灵活性是 DataStore 给不了的。DataStore 更多是用来存“状态”而非“业务实体”。

一句话总结:需要 CRUD + 关系 + 离线写入一致性 → Room;需要简单配置 + 快速读写 + 少量数据 → DataStore。别混用,但可以共存。

离线优先本地存储选型:Room vs DataStore Room 主导(业务数据) DataStore 主导(配置/状态) 离线业务数据 用户订单、笔记、消息等实体 ACID 事务 跨表原子写入,保证数据一致性 关联查询(@Relation) 一对多、多对多分支查询 版本迁移 Schema 变更 + 迁移脚本 用户偏好 / 配置 通知开关、主题色、缓存令牌 异步 + Flow 推送 不阻塞 UI,自动监听变化 整体快照原子性 单次 Transform 内多 key 原子 轻量无Schema 启动极快,无需建表 核心区别:Room 提供跨表 ACID 事务 → 适合离线业务写入 DataStore 只保证单次 Transform 原子性 → 适合配置快照

最后聊一句我的真实感受。有一回我们团队用 DataStore 存离线待办列表,结果用户同时写两条待办时,出现了部分丢失——因为 DataStore 的写入是合并式,不是追加式。后来我全都迁到了 Room,用 @Insert(onConflict = REPLACE) 解决问题。所以说,选型不能只看“简单”,数据一致性在离线场景里永远是第一位的。