跳到主要内容

数据迁移实战

场景

App 升级版本后需要迁移本地数据库(Room 版本升级)或从 SharedPreferences 迁移到 DataStore。

方案

1. Room 数据库升级

// 版本 1 → 版本 2:新增 column
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE user ADD COLUMN avatar TEXT DEFAULT ''")
}
}

// 版本 2 → 版本 3:新增表
val MIGRATION_2_3 = object : Migration(2, 3) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("""
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY NOT NULL,
value TEXT NOT NULL
)
""")
}
}

val db = Room.databaseBuilder(context, AppDatabase::class.java, "app.db")
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
.build()
危险操作

不要在 Migration 中使用 Room DAO,因为 DAO 对应的是新 Schema,而迁移时数据库还是旧 Schema。只能用原始 SQL。

2. SharedPreferences → DataStore

val Context.dataStore by preferencesDataStore(
name = "settings",
produceMigrations = { context ->
listOf(SharedPreferencesMigration(context, "old_prefs"))
}
)
// 迁移会自动将 old_prefs 中的数据复制到 DataStore,
// 迁移完成后删除旧的 SharedPreferences 文件

3. 迁移注意事项

要点说明
向后兼容新版 App 必须能处理旧数据格式
可回滚迁移失败时保留旧数据,不要先删后写
测试对每个版本升级路径写 Migration Test
大数据量分批迁移,避免 ANR
跳版本升级支持 v1 → v3 直接迁移(链式 Migration)
// Room Migration 测试
@RunWith(AndroidJUnit4::class)
class MigrationTest {
@get:Rule
val helper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
AppDatabase::class.java,
)

@Test
fun migrate1To2() {
// 创建 v1 数据库并插入数据
helper.createDatabase("app.db", 1).apply {
execSQL("INSERT INTO user VALUES (1, 'Alice')")
close()
}
// 执行迁移
helper.runMigrationsAndValidate("app.db", 2, true, MIGRATION_1_2)
}
}

面试答题要点

  1. Room 迁移用 Migration 类 + 原始 SQL
  2. SharedPreferences → DataStore 有内置迁移 API
  3. 必须为每个升级路径写 Migration Test
  4. 大数据量分批迁移,失败可回滚

相关链接