一、问题背景
项目使用 Room 作为本地 ORM 框架,数据库采用自定义外置 SD 存储路径 。当前数据库版本为 version = 5,仅编写了 3→4、4→5 两段迁移脚本。
问题来了:部分老用户从 V1 / V2 低版本直接覆盖安装新版 App 时,由于缺少 1→2、2→3 的升级 Migration 脚本,App 启动时直接抛出异常闪退:
text
java.lang.IllegalStateException: A migration from X to Y was required but not found
项目现状
| 维度 | 现状 |
|---|---|
| 数据库版本 | 5 |
| 已有迁移脚本 | MIGRATION_3_4、MIGRATION_4_5 |
| 存储位置 | 外部存储(脱离 App 私有目录) |
| 历史遗留问题 | 早期迭代未留存建表 SQL,无法快速补全完整迁移脚本 |
二、两种兜底解决方案对比
方案一:Application 全局捕获异常 + 手动删库自愈(已废弃)
实现思路
在 Application 全局崩溃捕获器中,通过异常关键字匹配「迁移缺失」报错,捕获后手动删除 .db、.wal、.shm 数据库文件,然后杀掉进程重启 App 重建数据库。
缺点
| 问题 | 说明 |
|---|---|
| 删库易失效 | 数据库为自定义外置路径,极易因路径编码、目录变更导致删库失败 |
| 用户体验差 | 崩溃后先闪退再重启,用户感知明显 |
| 事后补救 | 属于崩溃后置处理,无法在 Room 初始化阶段拦截异常 |
接入官方方案后,该部分自定义代码已全部删除。
方案二:官方 API fallbackToDestructiveMigration()(最终上线方案)
1. 作用说明
当出现以下三种场景时,Room 不再抛出崩溃异常,而是自动删除旧库并创建全新的空数据库,保障 App 正常启动:
- 新旧版本数据库不匹配,缺失 Migration 迁移脚本
- 数据库文件物理损坏
- 跨大版本升级且无迁移方案
⚠️ 注意 :此方案的代价是本地缓存数据会被清空,但服务端云端数据不受任何影响。
2. 接入代码(仅需新增一行,无需修改 Version 号)
java
databaseInstance = Room.databaseBuilder(context.getApplicationContext(),
RoomHelper.class, name)
.addMigrations(MIGRATION_3_4, MIGRATION_4_5)
// 缺少升级脚本 / 库文件损坏时自动重建数据库,规避启动闪退
.fallbackToDestructiveMigration()
.allowMainThreadQueries()
.build();
✅ 重要 :数据库
version保持原有数值不变,不需要随意升级版本。
三、进阶衍生 API 说明
1. fallbackToDestructiveMigrationFrom(int... startVersions)
指定低版本号,仅当指定版本升级缺失迁移时才自动删库,高版本仍正常走 Migration 迁移,最大限度保留用户数据。
java
// 只有 1、2 版本升级无迁移时自动删库,3 及以上版本正常执行迁移脚本
.fallbackToDestructiveMigrationFrom(1, 2)
2. fallbackToDestructiveMigrationOnDowngrade()
仅在版本降级安装时触发删库重建,版本升级时优先执行 Migration。适合版本回退场景。
3. Migration 与 Destructive 共存逻辑
| 场景 | 行为 |
|---|---|
| 存在完整 Migration | 优先执行 SQL 迁移,完整保留本地数据 |
| 无对应 Migration | 触发自动删库重建逻辑 |
📌 规范建议 :日常迭代优先新增 Migration,Destructive 只作为异常兜底防线。
4. 数据库文件损坏额外防护(可选扩展)
重写 onCorruption 方法,在数据库文件损坏时主动清理损坏文件:
java
@Override
public void onCorruption(SupportSQLiteDatabase db) {
super.onCorruption(db);
db.close();
File dbFile = new File(DATABASE_PATH, DATABASE_NAME);
if (dbFile.exists()) {
dbFile.delete();
new File(dbFile.getAbsolutePath() + "-wal").delete();
new File(dbFile.getAbsolutePath() + "-shm").delete();
}
databaseInstance = null;
}
四、上线落地规范
- ✅ 保留现有
addMigrations迁移配置,高版本有表变更时正常新增 Migration - ✅ 配置
fallbackToDestructiveMigration()作为全场景兜底 - ✅ 删除 Application 中所有自定义异常删库、重启逻辑
- ✅ 版本号跟随表结构变更正常递增,不随意改动 Version 用于测试
五、总结
| 方案类型 | 推荐做法 |
|---|---|
| 最优方案 | 补全 Migration,完整保留用户数据 |
| 兜底方案 | fallbackToDestructiveMigration(),保证 App 可用 |
核心要点
- 自定义外置数据库路径的项目,尽量避免手写文件删除逻辑,路径错误隐患极高
- 生产环境标准配置:正常 Migration 迁移 + 官方 Destructive 兜底,兼顾数据安全与 App 可用性