跳转到正文
YGJoon 技术博客

Android Kotlin DataStore 配置与迁移

发布于:
·849 字 · 约 2 分钟·作者:
DataStore 双形态:Preferences 存键值、Proto 存结构化数据,SharedPreferences 存量数据迁移的完整步骤和回滚预案。
Android Kotlin DataStore 配置与迁移文章封面
目录

DataStore 简介

Preferences DataStore

val Context.settingsDataStore by preferencesDataStore(name = "settings")

object SettingsKeys {
    val DARK_MODE = booleanPreferencesKey("dark_mode")
}

suspend fun setDarkMode(enabled: Boolean, context: Context) {
    context.settingsDataStore.edit { prefs ->
        prefs[SettingsKeys.DARK_MODE] = enabled
    }
}

val darkModeFlow: Flow<Boolean> = context.settingsDataStore.data
    .map { prefs -> prefs[SettingsKeys.DARK_MODE] ?: false }

Proto DataStore

1. 定义 proto

user_prefs.proto

syntax = "proto3";

option java_package = "com.example.datastore";

message UserPreferences {
  string userId = 1;
  string locale = 2;
}

2. 配置 Gradle

plugins {
    id("com.google.protobuf") version "0.9.4"
}

protobuf {
    protoc { artifact = "com.google.protobuf:protoc:3.25.3" }
    generateProtoTasks {
        all().forEach { task ->
            task.builtins {
                create("java")
                create("kotlin")
            }
        }
    }
}

3. 使用 ProtoDataStore

val Context.userPreferencesDataStore: DataStore<UserPreferences> by dataStore(
    fileName = "user_prefs.pb",
    serializer = UserPreferencesSerializer
)

object UserPreferencesSerializer : Serializer<UserPreferences> {
    override val defaultValue: UserPreferences = UserPreferences.getDefaultInstance()

    override suspend fun readFrom(input: InputStream): UserPreferences =
        try {
            UserPreferences.parseFrom(input)
        } catch (e: InvalidProtocolBufferException) {
            throw CorruptionException("Cannot read proto.", e)
        }

    override suspend fun writeTo(t: UserPreferences, output: OutputStream) = t.writeTo(output)
}

SharedPreferences 迁移

val Context.migratedDataStore by dataStore(
    fileName = "settings.pb",
    serializer = SettingsSerializer,
    produceMigrations = { context ->
        listOf(SharedPreferencesMigration(context, "old_settings"))
    }
)

迁移逻辑可在 SharedPreferencesMigration.migrate 中自定义。

多模块集成

@Module
@InstallIn(SingletonComponent::class)
object DataStoreModule {
    @Provides
    fun provideUserPreferences(@ApplicationContext context: Context): DataStore<UserPreferences> {
        return context.userPreferencesDataStore
    }
}

测试与调试

@RunWith(AndroidJUnit4::class)
class DataStoreTest {
    @get:Rule
    val tmpFolder = TemporaryFolder()

    private val testContext = ApplicationProvider.getApplicationContext<Context>()
        .createDeviceProtectedStorageContext()

    @Test
    fun writeAndRead() = runTest {
        val store = PreferenceDataStoreFactory.create(
            scope = TestScope(UnconfinedTestDispatcher()),
            produceFile = { tmpFolder.newFile("settings.preferences_pb") }
        )
        store.edit { it[booleanPreferencesKey("flag")] = true }
        assertTrue(store.data.first()[booleanPreferencesKey("flag")] == true)
    }
}

常见问题

问题原因解决方案
数据读取卡住未消费 Flow使用 collect 或 first 获取数据
Proto 解析失败序列化字段缺失升级 schema 并提供默认值
UI 不更新未切换到主线程flowOn(Dispatchers.IO) + collect 在 Main

实战案例:SharedPreferences 迁移是”开弓没有回头箭”

Google 官方博客《Prefer Storing Data with Jetpack DataStore》里,迁移 API 长这样:

val dataStore: DataStore<Preferences> = context.createDataStore(
    name = "settings",
    migrations = listOf(SharedPreferencesMigration(context, "settings_preferences"))
)

看起来很美好,但有三个实战细节文档不会大声告诉你:

  1. 迁移只跑一次。SharedPreferencesMigration 在 DataStore 第一次被访问时执行,成功后就把旧 XML 文件删了。这意味着:迁移代码有 bug,线上用户的数据就没了,没有第二次机会。必须先在测试环境用真实的老版本数据跑一遍。
  2. 键名必须对上。DataStore 的 stringPreferencesKey("username") 和 SharedPreferences 的 "username" 是同一个字符串,改名即丢失。重构时顺手”优化”键名是迁移失败的头号原因。
  3. 别在迁移里做耗时操作。迁移跑在 DataStore 初始化路径上,卡住就等于 App 启动卡住。

我在 Flutter 项目里(某物流冷链项目)token 还存在 SharedPreferences 明文里——按 Android 的标准答案,早该迁到 DataStore(加密版走 Tink)。知道和做到之间差的是排期,但方向是明确的:新项目直接上 DataStore,别再写 SharedPreferences。

参考:Android Developers Blog《Prefer Storing Data with Jetpack DataStore》

总结

  1. DataStore 更适合现代 Kotlin 异步流程,与 Flow、协程配合紧密。
  2. Preferences 模式简单易用,Proto 模式提供类型安全与扩展性。
  3. 合理设计迁移方案,可平滑替代旧的 SharedPreferences,保障数据连续性。

分享这篇文章



相关文章

上一篇
Android Kotlin Flow 数据流与状态管理
下一篇
Android Kotlin Jetpack Compose 实战指南
YGJoon 的头像

YGJoon

记录 Flutter、Web 与原生移动端工程实践。更多背景见关于页,源码与项目见GitHub。