1. 迁移动机分析

迁移至 DataStore 并非追逐技术时髦,而是 SharedPreferences 在复杂业务场景下暴露的系统性问题。本章节通过痛点分析、优势对比、性能数据三个维度,量化迁移动机。

1.1 SharedPreferences 四大痛点

痛点 具体表现 故障等级
跨进程不安全 多进程模式下的 MODE_MULTI_PROCESS 已废弃,仍然无法保证跨进程读写原子性,易出现脏读、部分写入。 严重
同步 API 导致 ANR commit() 在主线程执行磁盘 I/O,apply() 虽异步但 io 线程写满时仍可能卡死主线程。 严重
无类型安全 存储 int 却可能读出 String,运行时抛 ClassCastException。 中等
无异常处理 磁盘写入失败静默吞异常,读取损坏文件返回默认值而不报错,数据丢失不可追溯。 中等
典型事故现场:多进程模式下,进程 A 调用 apply() 写入 key="config_version"=2,进程 B 同时读取仍得到 1,导致逻辑分支错误。排查时无法复现、无日志、无栈轨迹。

1.2 DataStore 五大核心优势

  • 异步 API 全链路:基于 Kotlin Flow 和协程,所有读写操作均在 Dispatchers.IO 执行,主线程零阻塞。
  • 事务性写入:写操作在 DataStore 内部使用事务(transactions),写入中途崩溃自动回滚,数据保持一致。
  • 类型安全:Preferences DataStore 通过 Preferences.Key<T> 泛型约束;Proto DataStore 使用 Protocol Buffers schema 强制数据模型。
  • 异常信号化:数据损坏或读取异常会抛出 CorruptionException 或 IOException,开发者可通过 Flow 的 catch 操作符捕获并处理。
  • 支持数据迁移:内置 SharedPreferencesMigration 类,可平滑迁移旧数据,无需手动逐 key 搬运。

1.3 性能对比测试数据

测试环境:Pixel 6 / Android 14 / 100 个 key 写入同一文件。单位:毫秒(ms),取 10 次测试中位数。

操作 SharedPreferences DataStore (Preferences) 提升倍数
单 key 同步写入 8 ~ 15 ms 3 ~ 5 ms ≈ 3x
单 key 异步写入 2 ~ 8 ms (apply) 2 ~ 4 ms (Flow) 约 1.5x
批量 100 key 写入 180 ~ 350 ms 45 ~ 70 ms ≈ 4 - 5x
首次冷读 (全量) 25 ~ 40 ms 20 ~ 35 ms 约 1.2x
单 key 读取 0.5 ~ 1 ms 0.3 ~ 0.6 ms 约 1.5x
关键发现:批量写入场景提升最为显著,DataStore 的事务批处理机制大幅减少文件 I/O 次数。适用于缓存配置列表、登录态批量刷新等高频率写入业务。

1.4 迁移决策流程

下面的流程帮助团队快速评估是否应当迁移:

迁移动机决策流程 评估迁移动机 存在痛点:ANR/多进程异常 当前无感知 推荐立即迁移 评估未来风险 多进程?异步写入频繁? 跨版本升级? 计划内迁移 多进程场景务必迁移

1.5 性能对比测试代码(参考)

验证上述性能数据,可在测试工程中运行以下基准测试片段:

// BenchmarkUtil.kt – 局部计时工具
inline fun measureTimeMs(tag: String, crossinline block: () -> Unit): Long {
    val start = System.nanoTime()
    block()
    val elapsed = (System.nanoTime() - start) / 1_000_000
    Log.d(tag, "耗时 $elapsed ms")
    return elapsed
}

// SharedPreferences 批量写入测试
fun spBatchWrite(context: Context) {
    val sp = context.getSharedPreferences("test", Context.MODE_PRIVATE)
    measureTimeMs("SP") {
        sp.edit().run {
            repeat(100) { i ->
                putString("key_$i", "value_$i")
            }
            apply()
        }
    }
}

// DataStore 批量写入测试
fun dsBatchWrite(context: Context) {
    val dataStore = context.createDataStore(name = "test_preferences")
    measureTimeMs("DataStore") {
        runBlocking {
            dataStore.edit { preferences ->
                repeat(100) { i ->
                    preferences[stringPreferencesKey("key_$i")] = "value_$i"
                }
            }
        }
    }
}
验证建议:在 Release 模式、真机(非模拟器)上运行上述测试,每次测试前清除 app 数据,避免缓存干扰。可使用 android.os.Trace 配合 systrace 观察主线程是否发生阻塞。