如何使用Dagger Hilt实现Proto DataStore的数据迁移?
DataStore迁移问题排查与Proto DataStore结合Hilt实现指南
一、当前Json DataStore迁移失效的原因
- 反序列化失败直接触发重置:当新增/重命名字段时,旧数据的Json结构与新
AppSettings不匹配,readFrom会抛出SerializationException进而触发CorruptionException,此时corruptionHandler直接返回默认值,完全没进入迁移流程——迁移是成功读取当前数据后才会执行的逻辑,不负责处理数据损坏。 - 迁移无版本判断逻辑:
shouldMigrate(currentData) = true会导致每次初始化DataStore都执行迁移,但你的migrate方法只是原样返回数据,既没处理字段变更,也无法区分哪些数据需要升级。 - 迁移未覆盖字段变更场景:比如新增字段时未补充默认值、重命名字段时未完成值的转移,导致旧数据无法适配新结构。
二、Json DataStore正确迁移实现方案
1. 给数据类添加版本标识
修改AppSettings,加入版本字段用于判断迁移时机:
@Serializable data class AppSettings( val version: Int = 1, // 初始版本为1,字段变更时递增 val prepareSeconds: Long = 10L, val workSeconds: Long = 90L, val restSeconds: Long = 30L, val totalSets: Int = 3, val newFeatureEnabled: Boolean = false // 新增字段,版本升级到2 ) { companion object { fun getDefaultInstance() = AppSettings() } }
2. 调整Serializer兼容旧数据
在readFrom中处理旧版本无version字段的情况,避免直接抛出异常:
object AppSettingsSerializer : Serializer<AppSettings> { override val defaultValue: AppSettings get() = AppSettings() override suspend fun readFrom(input: InputStream): AppSettings { return try { val jsonString = input.readBytes().decodeToString() val jsonElement = Json.parseToJsonElement(jsonString) // 兼容旧版本无version字段的情况 val version = jsonElement.jsonObject["version"]?.jsonPrimitive?.intOrNull ?: 1 when (version) { 1 -> { // 从旧版本数据构造新版本对象 val prepareSeconds = jsonElement.jsonObject["prepareSeconds"]?.jsonPrimitive?.longOrNull ?: 10L val workSeconds = jsonElement.jsonObject["workSeconds"]?.jsonPrimitive?.longOrNull ?: 90L val restSeconds = jsonElement.jsonObject["restSeconds"]?.jsonPrimitive?.longOrNull ?: 30L val totalSets = jsonElement.jsonObject["totalSets"]?.jsonPrimitive?.intOrNull ?: 3 AppSettings( version = 2, prepareSeconds = prepareSeconds, workSeconds = workSeconds, restSeconds = restSeconds, totalSets = totalSets, newFeatureEnabled = false // 新增字段默认值 ) } else -> Json.decodeFromString(AppSettings.serializer(), jsonString) } } catch (e: SerializationException) { throw CorruptionException("Unable to read AppSettingsPrefs", e) } } @Suppress("BlockingMethodInNonBlockingContext") override suspend fun writeTo(t: AppSettings, output: OutputStream) { output.write( Json.encodeToString( serializer = AppSettings.serializer(), value = t ).encodeToByteArray() ) } }
3. 基于版本的DataMigration实现
如果需要通过DataMigration而非Serializer处理升级,调整Hilt模块中的迁移逻辑:
@Module @InstallIn(SingletonComponent::class) object AppModule { private const val DATA_STORE_FILE_NAME = "app_settings_preferences.pb" @Singleton @Provides fun provideAppSettingsDataStore( @ApplicationContext appContext: Context, @ApplicationScope coroutineScope: CoroutineScope, ): DataStore<AppSettings> { return DataStoreFactory.create( serializer = AppSettingsSerializer, corruptionHandler = ReplaceFileCorruptionHandler { AppSettings.getDefaultInstance() }, scope = coroutineScope, produceFile = { appContext.dataStoreFile(DATA_STORE_FILE_NAME) }, migrations = listOf(object : DataMigration<AppSettings> { // 仅当前数据版本低于目标版本时执行迁移 override suspend fun shouldMigrate(currentData: AppSettings) = currentData.version < 2 override suspend fun migrate(currentData: AppSettings): AppSettings { // 处理版本升级:新增字段赋值默认值,或重命名字段等 return currentData.copy( version = 2, newFeatureEnabled = false ) } override suspend fun cleanUp() = Unit }) ) } }
三、Proto DataStore结合Dagger Hilt的迁移实现
1. 定义Proto文件
在app/src/main/proto下创建app_settings.proto:
syntax = "proto3"; option java_package = "com.yourpackage.app"; option java_multiple_files = true; message AppSettings { int32 version = 1; // 版本标识,用于迁移 int64 prepare_seconds = 2; int64 work_seconds = 3; int64 rest_seconds = 4; int32 total_sets = 5; bool new_feature_enabled = 6; // 新增字段,版本升级到2 }
2. 配置Proto编译(build.gradle.kts)
在app模块的build.gradle.kts中添加依赖与配置:
plugins { id("com.android.application") id("kotlin-android") id("kotlin-kapt") id("dagger.hilt.android.plugin") id("com.google.protobuf") } android { sourceSets { getByName("main") { proto.srcDir("src/main/proto") } } } dependencies { // Proto DataStore implementation("androidx.datastore:datastore-core:1.0.0") implementation("androidx.datastore:datastore-proto:1.0.0") // Protobuf implementation("com.google.protobuf:protobuf-javalite:3.21.12") // Hilt implementation("com.google.dagger:hilt-android:2.44") kapt("com.google.dagger:hilt-android-compiler:2.44") } protobuf { protoc { artifact = "com.google.protobuf:protoc:3.21.12" } generateProtoTasks { all().forEach { task -> task.builtins { create("java") { option("lite") } } } } }
3. 实现迁移与Hilt注入
@Module @InstallIn(SingletonComponent::class) object AppModule { private const val DATA_STORE_FILE_NAME = "app_settings.pb" @Singleton @Provides fun provideAppSettingsDataStore( @ApplicationContext appContext: Context, @ApplicationScope coroutineScope: CoroutineScope, ): DataStore<AppSettings> { val serializer = AppSettingsSerializer() return DataStoreFactory.create( serializer = serializer, corruptionHandler = ReplaceFileCorruptionHandler { AppSettings.getDefaultInstance() }, scope = coroutineScope, produceFile = { appContext.dataStoreFile(DATA_STORE_FILE_NAME) }, migrations = listOf(object : DataMigration<AppSettings> { override suspend fun shouldMigrate(currentData: AppSettings): Boolean { // 版本1升级到版本2 return currentData.version < 2 } override suspend fun migrate(currentData: AppSettings): AppSettings { return currentData.toBuilder() .setVersion(2) .setNewFeatureEnabled(false) // 新增字段设置默认值 .build() } override suspend fun cleanUp() = Unit }) ) } // Proto DataStore的Serializer实现 class AppSettingsSerializer : Serializer<AppSettings> { override val defaultValue: AppSettings = AppSettings.getDefaultInstance() override suspend fun readFrom(input: InputStream): AppSettings { return try { AppSettings.parseFrom(input) } catch (e: InvalidProtocolBufferException) { throw CorruptionException("Unable to read AppSettings", e) } } override suspend fun writeTo(t: AppSettings, output: OutputStream) { t.writeTo(output) } } }
4. 关键注意事项
- Proto字段的标签号(如
version = 1中的1)绝对不能修改,否则会导致旧数据无法解析;新增字段使用全新的标签号即可。 - 迁移时通过
toBuilder()修改旧数据,确保保留原有字段的值,仅调整需要变更的部分。 - 每次版本升级需递增
version字段,并在shouldMigrate中判断是否需要执行迁移。
内容的提问来源于stack exchange,提问作者Vikash Singh
相关产品推荐
相关产品推荐

