经过广泛的有条不紊的测试,我可以复制您的(需要 1-3 次)失败的唯一方法是排除 fallbackToDestructiveMigation。在这种情况下,如果迁移是从 1 到 3 或迁移是 3 到 1(即 Asset Version 为 3 但 Room 版本为 1),则会发生异常
我怀疑您不知何故无意中引入了上述两个扫描仪之一。我没有测试的是替代房间库版本。上述内容已按照 2.4.0-alpha04 进行了测试:-
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
implementation 'androidx.room:room-ktx:2.4.0-alpha04'
implementation 'androidx.room:room-runtime:2.4.0-alpha04'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
kapt 'androidx.room:room-compiler:2.4.0-alpha0
为了测试,我有两个资产文件副本,一个在版本 1,另一个在版本 2(v1dbbase.db 和 v3dbbase.db),公共列中的数据指示数据是否适用于版本 3。实际使用的资产文件在测试前被删除,并将相应的版本复制粘贴到database.db
我有两个实体 Object1 和 Object2,并且可以在其中一个额外的列中添加或注释掉。例如:-
/*TESTING INCLUDE FOR V2+ >>>>>*///, @ColumnInfo(name = COL_EXTRAV2, defaultValue = "x") val object1_extra: String
- as above it is excluded
/*TESTING INCLUDE FOR V2+ >>>>>*/, @ColumnInfo(name = COL_EXTRAV2, defaultValue = "x") val object1_extra: String
- with the two //'s before the comma now included
- 两个额外的列都被注释掉 = 版本 1
- 包含 Object1 的额外列 = 版本 3
- 包含 Object1 和 Object2 的额外列 = 版本 3
- 未考虑包含 Object2 的额外列,但未考虑 Object1 的额外列。
添加了一些常量来满足日志记录。
另外,为了记录一个回调函数(.addCallback),onOpen、onCreate 和 onDestructiveMigration 都被覆盖以记录房间版本和数据库版本。
为了进一步增强日志记录,添加了两个函数,从 sqlite 数据库头中获取版本。一个用于资产文件,另一个用于数据库。在数据库构建之前调用/调用的函数。
运行测试意味着:-
- 确保设备具有适当级别的应用程序。
- 删除 database.db 资产
- 将适当的资产文件复制并粘贴为 database.db(来自 v1dbbase.db 或 v3dbbase.db)
- 修改 Object1 类以包含/排除额外的列(如上所述)
- 修改 Object2 类以包含/排除额外的列(如上所述)
- 将房间版本修改为适当的级别。
用于测试的代码:-
对象1
@Entity(tableName = TABLE_NAME)
data class Object1(
@PrimaryKey
@ColumnInfo(name = COL_ID)
val object1_id: Long,
@ColumnInfo(name = COL_NAME)
val object1_name: String
/*TESTING INCLUDE FOR V2+ >>>>>*///, @ColumnInfo(name = COL_EXTRAV2, defaultValue = "x") val object1_extra: String
) {
companion object {
const val TABLE_NAME = "object1"
const val COL_ID = TABLE_NAME + "_object1_id"
const val COL_NAME = TABLE_NAME + "_object1_name"
const val COL_EXTRAV2 = TABLE_NAME + "_object1_extrav2"
}
}
对象2
@Entity(tableName = TABLE_NAME)
data class Object2(
@PrimaryKey
@ColumnInfo(name = COL_ID)
val object2_id: Long,
@ColumnInfo(name = COL_NAME)
val object2_name: String
/*TESTING INCLUDE FOR V3>>>>>*///, @ColumnInfo(name = COL_EXTRAV3, defaultValue = "x") val object3_extrav3: String
) {
companion object {
const val TABLE_NAME = "object2"
const val COL_ID = TABLE_NAME + "_object2_id"
const val COL_NAME = TABLE_NAME + "_object2_name"
const val COL_EXTRAV3 = TABLE_NAME + "_object2_extrav3"
}
}
道
@Dao
abstract class Dao {
@Insert
abstract fun insert(object1: Object1): Long
@Insert
abstract fun insert(object2: Object2): Long
@Query("SELECT * FROM ${Object1.TABLE_NAME}")
abstract fun getAllFromObject1(): List<Object1>
@Query("SELECT * FROM ${Object2.TABLE_NAME}")
abstract fun getAllFromObject2(): List<Object2>
}
应用数据库
@Database(
entities = [Object1::class, Object2::class],
version = AppDatabase.DBVERSION,
autoMigrations = [AutoMigration (from = 1, to = 2)],
exportSchema = true
)
abstract class AppDatabase : RoomDatabase() {
abstract fun dao(): Dao
companion object {
private const val DB_NAME = "database.db"
private const val DB_FILENAME = "AppDB.db" //<<<<< ADDED for getting header
const val TAG = "DBINFO" //<<<< ADDED for logging
const val DBVERSION = 1 //<<<<<ADDED for logging
@Volatile
private var instance: AppDatabase? = null
fun getInstance(context: Context): AppDatabase {
return instance ?: synchronized(this) {
//ADDED>>>>> to get database version from dbfile and assets before building the database
Log.d(TAG,
"AssetDB Version =${getAssetDBVersion(context, DB_NAME)} " +
"Database Version = ${getDBVersion(context, DB_FILENAME)} " +
"Room Version = ${DBVERSION}")
instance ?: buildDatabase(context).also { instance = it }
}
}
private fun buildDatabase(context: Context): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java, DB_FILENAME)
.fallbackToDestructiveMigration()
.createFromAsset(DB_NAME)
.allowMainThreadQueries()
.addCallback(rdc)
.build()
}
/* Call Backs for discovery */
object rdc: RoomDatabase.Callback(){
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
Log.d(TAG,"onCreate called. DB Version = ${db.version}, Room Version is ${DBVERSION}")
}
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
Log.d(TAG,"onOpen called. DB Version = ${db.version}, Room Version is ${DBVERSION}")
}
override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
super.onDestructiveMigration(db)
Log.d(TAG,"onDestructiveMigration called. DB Version = ${db.version}, Room Version is ${DBVERSION}")
}
}
fun getAssetDBVersion(context: Context, assetFilePath: String): Int {
var assetFileHeader = ByteArray(100)
try {
var assetFileStream = context.assets.open(assetFilePath)
assetFileStream.read(assetFileHeader,0,100)
assetFileStream.close()
} catch (e: IOException) {
return -2 // Indicates file not found (no asset)
}
return ByteBuffer.wrap(assetFileHeader,60,4).getInt()
}
fun getDBVersion(context: Context, dbFileName: String): Int {
var SQLiteHeader = ByteArray(100)
val dbFile = context.getDatabasePath(dbFileName)
if(dbFile.exists()) {
var inputStream = dbFile.inputStream()
inputStream.read(SQLiteHeader, 0, 100)
inputStream.close()
return ByteBuffer.wrap(SQLiteHeader, 60, 4).getInt()
} else {
return -1 // Indicates no database file (e.g. new install)
}
}
}
}
- 您可能希望考虑包括上面的日志记录,它可以很容易地检测到正在使用的版本的问题。
MainActivity
class MainActivity : AppCompatActivity() {
lateinit var db: AppDatabase
lateinit var dao: Dao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = AppDatabase.getInstance(this)
dao = db.dao()
for(o1: Object1 in dao.getAllFromObject1()) {
logObject1(o1)
}
for(o2: Object2 in dao.getAllFromObject2()) {
logObject2(o2)
}
}
fun logObject1(object1: Object1) {
Log.d(TAG,"ID is ${object1.object1_id}, Name is ${object1.object1_name}")
}
fun logObject2(object2: Object2) {
Log.d(TAG,"ID is ${object2.object2_id}, Name is ${object2.object2_name}")
}
companion object {
const val TAG = AppDatabase.TAG
}
}
除了利用上述代码并确保完成 6 项任务外,我还保留了版本和结果的电子表格,例如:-
上一个答案(测试后不是这样)
我认为您的问题可能与预填充的数据库有关,因为它的版本号 (user_version) 尚未更改为 3。
- 您可以使用 SQL 更改版本(来自 SQlite 工具)
PRAGMA user_version = 3;
documentation 说:-
在这种情况下会发生以下情况:
- 由于您的应用中定义的数据库是版本 3,而设备上已安装的数据库实例是版本 2,因此需要进行迁移。
- 由于没有实施从版本 2 到版本 3 的迁移计划,因此迁移是回退迁移。
- 因为调用了fallbackToDestructiveMigration() builder方法,所以回退迁移是破坏性的。 Room 删除设备上安装的数据库实例。
- 由于版本 3 中有一个预打包的数据库文件,Room 会重新创建数据库并使用预打包的数据库文件的内容填充它。
-
另一方面,如果您预打包的数据库文件使用的是版本 2,那么 Room 会注意到它与目标版本不匹配,并且不会将其用作后备迁移的一部分。