1. DataStore 离线缓存概述与迁移动机

Android DataStore 是 Google 推出的新一代本地持久化存储方案,用于替代历史久远的 SharedPreferences。它基于 Kotlin 协程与 Flow 构建,天然支持异步、事务性写入,且默认提供 类型安全 的 Protobuf 版本(DataStore)以及简单键值版本(Preferences DataStore)。DataStore 从 Android 12(API 31) 开始成为官方首选存储组件,Google 明确建议新项目优先使用 DataStore,并提供了从 SharedPreferences 到 DataStore 的 迁移适配器。

迁移的核心动机在于 SharedPreferences 的 同步写入 机制会在 UI 线程引发 ANR(Application Not Responding),且不支持 事务性批量写入、数据一致性保证和运行时类型安全。DataStore 通过 协程+Flow 将读写操作移至后台线程,并利用 事务性写入 确保数据要么全部写入,要么全部回滚,避免脏数据。

1.1 DataStore vs SharedPreferences 关键差异

对比维度 SharedPreferences DataStore(Preferences)
写入方式 同步(commit)或异步(apply),apply 仍可能阻塞 异步,完全协程驱动,无 UI 线程阻塞
数据一致性 无事务支持,并发写入可能覆盖 事务性写入,失败自动回滚
类型安全 运行时强转,依赖开发者注意 Preferences 通过扩展属性提供类型安全;Proto 版通过编译期生成
错误处理 异常捕获困难,部分写入失败不通知 抛出 IOException 等检查异常,可通过 Flow 捕获
生命周期关联 进程销毁前不保证写入完成 与协程生命周期绑定,可安全取消
多进程支持 不支持(官方不推荐) 不支持(需单独改造)
迁移路径 无 内置 SharedPreferencesMigration,可一键迁移

1.2 离线缓存场景与迁移收益

DataStore 特别适合以下 离线缓存场景:

  • 用户偏好设置:主题、语言、通知开关等非敏感轻量配置。
  • 应用状态持久化:上次登录用户、引导页已展示标志、首次启动时间戳。
  • 离线任务队列元数据:如缓存键、最后同步时间、分页游标。
  • 联网带宽受限时的关键配置:如服务器端点缓存、日志级别等。

迁移收益一览:

  • 消除 ANR 风险:所有磁盘 I/O 均在 Dispatchers.IO 执行。
  • 数据完整性提升:事务写入确保原子性,崩溃后不会出现半写入状态。
  • 类型安全:编译期即可发现 NullPointerException 和类型转换错误。
  • 可测试性:DataStore 接口易于 mock,且提供 FakeDataStore 用于单元测试。
  • 更明确的错误管理:写入失败通过 Flow 传播,非静默失败。
  • 与 Jetpack 生态融合:天然支持 Lifecycle、Coroutine、ViewModel 和 WorkManager。

1.3 适用版本与兼容性

DataStore 从版本 1.0.0 起稳定,支持 Android API 14(Ice Cream Sandwich) 及以上设备。迁移适配器 SharedPreferencesMigration 作为独立 artifact 提供,无需修改原 SP 文件即可完成迁移。Google Play Services 和 Jetpack 库均已适配。

推荐版本:截至课程编写时,稳定版本为 1.0.0,但建议项目使用 1.1.0 或更高以获取最新的性能与兼容性修复。Gradle 依赖如下:

// Preferences DataStore
implementation "androidx.datastore:datastore-preferences:1.0.0"

// 如需 Proto 支持
implementation "androidx.datastore:datastore-core:1.0.0"

// 迁移适配器(用于从 SharedPreferences 迁移)
implementation "androidx.datastore:datastore-migration:1.0.0"

1.4 迁移前代码示例(SharedPreferences 风格)

以下是一段典型的 SharedPreferences 读写代码,后续章节将演示如何迁移到 DataStore:

// 写入(同步,可能阻塞 UI)
val prefs = getSharedPreferences("app_settings", Context.MODE_PRIVATE)
prefs.edit().putBoolean("is_dark_mode", true).apply() // apply 异步但仍有风险

// 读取(同步,UI 线程执行)
val isDarkMode = prefs.getBoolean("is_dark_mode", false)

// 监听变化(效率低,需轮询或手动注册)
prefs.registerOnSharedPreferenceChangeListener { sharedPreferences, key ->
    // 回调中处理变化
}

1.5 迁移后代码示例(DataStore 风格预览)

// 定义 DataStore(通常在单例或顶层属性中)
val Context.settingsDataStore by preferencesDataStore(name = "app_settings")

// 定义类型安全的键
val IS_DARK_MODE = booleanPreferencesKey("is_dark_mode")

// 写入(协程,原子事务)
suspend fun setDarkMode(context: Context, enabled: Boolean) {
    context.settingsDataStore.edit { preferences ->
        preferences[IS_DARK_MODE] = enabled
    }
}

// 读取(返回 Flow,可观察变化)
val darkModeFlow: Flow = context.settingsDataStore.data.map { preferences ->
    preferences[IS_DARK_MODE] ?: false
}

注意:DataStore 的 edit 和 data 必须在协程作用域内调用(如 viewModelScope、lifecycleScope 或 runBlocking 测试中)。切勿在 UI 线程直接调用挂起函数或收集 Flow。

1.6 迁移流程总览架构图

DataStore 离线缓存迁移流程架构图 1. 现状分析 清单 SP 键值 & 类型 2. 添加 Gradle 依赖 datastore-preferences 3. 创建 DataStore preferencesDataStore 委托 4. 配置迁移适配器 SharedPreferencesMigration 5. 逐步替换读写逻辑 Flow 替换同步读取 6. 验证 & 清理 删除 SP 文件 🔍 离线缓存典型场景: • 用户主题/语言偏好 • 登录令牌缓存 • 引导标志位 • 本地配置开关 • 离线任务队列元数据 • 设备标识符 • 最后同步时间戳 • 非敏感缓存控制

1.7 什么时候必须迁移?什么时候可以暂缓?

判定条件 建议
应用已出现 ANR 或 UI 卡顿,且堆栈与 SP 写入相关 立即迁移,首选低风险策略(迁移适配器 + 分步替换)
新项目开发,或现有项目大规模重构 优先使用 DataStore,不要引入 SP 新代码
项目已稳定,且对性能无明确痛点 可暂缓,但应在长期技术规划中排入迁移任务
多进程共享数据需求(如多进程 Service) 谨慎评估,DataStore 不原生支持多进程,需自行实现 ContentProvider 或继承 FileAccessCoordinator

推荐迁移节奏:对存量项目采用 适配器先行 + 逐步替换 策略。先通过 SharedPreferencesMigration 将 SP 数据导入 DataStore,期间旧 SP 读写仍可工作;随后逐一将 SP 读取替换为 Flow 收集,最后移除 SP 代码和文件。整个过程可分模块、分团队并行推进,风险可控。