【问题标题】:How to prepopopulate Room database if there is already existing instance of it?如果已经存在它的实例,如何预填充 Room 数据库?
【发布时间】:2021-05-03 11:56:24
【问题描述】:

我希望我的用户能够将房间数据库保存在谷歌驱动器上,然后从它加载到另一台设备上,第一部分已经实现,但我正在努力用新文件填充房间数据库。

这是我的单例数据库:

companion object {
    @Volatile
    private var INSTANCE: AppRoomDataBase? = null
    fun getDatabase(context: Context): AppRoomDataBase {
        val tempInstance = INSTANCE
        if (tempInstance != null) {
            return tempInstance
        }
        synchronized(this) {
            val instance = Room.databaseBuilder(
                context.applicationContext,
                AppRoomDataBase::class.java,
                "product_database"
            ).build()
            INSTANCE = instance
            return instance
        }
    }
}

然后当用户想从谷歌驱动器加载数据库时:

 private fun loadFileFromDrive() {
    val file = File(getExternalFilesDir(null), "product_database")
    mDriveServiceHelper.queryFiles().addOnSuccessListener { fileList ->
       val database = fileList.files.first()
        mDriveServiceHelper.downloadFile(file,database.id)?.addOnSuccessListener {
                prepopulateRoomDatabaseWithFile(file)
        }
    }
}


private fun prepopulateRoomDatabaseWithFile(file : File){
    Room.databaseBuilder(application,AppRoomDataBase::class.java,"product_database")
        .createFromFile(file)
        .build()
}

但这不起作用,只有关于如何最初从文件创建数据库的文档,但我找不到任何关于如何仅在需要时用新的 Room 数据库覆盖当前 Room 数据库的信息,这可能吗?

【问题讨论】:

    标签: android kotlin android-room


    【解决方案1】:

    我尝试了 MikeT 解决方案,但它不起作用,因为我有很多不同的地方使用 ROOM 数据库,并且在每个地方我都必须传递一个信息,说明应该使用新的数据库。

    对我来说更好的解决方案是通过删除前一个文件并将下载的文件移动到旧文件的目录来切换数据库文件,如下所示:

    private fun swapDatabaseFiles(file: File){
        AppRoomDataBase.getDatabase(applicationContext).close()
        databaseFile.delete()
        copyFile(file.absolutePath,databaseFile.name,databaseFile.absolutePath)
        prepopulateRoomDatabaseWithFile(file)  // TO OPEN DATABASE
        val mainActivity = Intent(applicationContext,MainActivity::class.java)  // NOW RESTARTING ACTIVITY SO DATABASE IS REFRESHED
        mainActivity.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
        startActivity(mainActivity)
    }
    
    
    
     private fun copyFile(inputPath: String, inputFileName: String, outputPath: String) {
        var inputStream: InputStream?
        var out: OutputStream?
        try {
            inputStream = FileInputStream(inputPath)
            out = FileOutputStream(outputPath + inputFileName)
            val buffer = ByteArray(1024)
            var read: Int
            while (inputStream.read(buffer).also { read = it } != -1) {
                out.write(buffer, 0, read)
            }
            inputStream.close()
            // write the output file (You have now copied the file)
            out.flush()
            out.close()
        } catch (fnfe1: FileNotFoundException) {
            fnfe1.message?.let { Log.e("tag fnfe1", it)
            }
        } catch (e: Exception) {
            Log.e("tag exception", e.message!!)
        }
    }
    

    在单例数据库中交换数据库函数:

    fun swapDatabase(context: Context,file : File){
                INSTANCE?.close()
                INSTANCE = null
                synchronized(this){
                val instance = Room.databaseBuilder(context.applicationContext,AppRoomDataBase::class.java,"product_database")
                    .createFromFile(file)
                    .build()
                INSTANCE = instance
            }
            }
    

    编辑:重构代码和修复错误异常。现在可以正常工作了。

    【讨论】:

      【解决方案2】:

      相信你需要先关闭原来的数据库,然后再建第二个数据库设置INSTANCE。

      以下是一个示例(仅通过关闭然后重新打开单个数据库进行测试,参见下面的示例)。

      @Database(entities = [UserEntity::class],version = 1)
      abstract class UserDatabase: RoomDatabase() {
          abstract fun getDao(): AllDao
      
          companion object {
              @Volatile
              private var instance: UserDatabase? = null
              private val DBNAME = "user.db"
              fun getInstance(context: Context, dbName: String = DBNAME): UserDatabase {
                  if (instance == null) {
                      synchronized(this) {
                          instance = Room.databaseBuilder(
                              context,
                              UserDatabase::class.java,
                              dbName
                          ).allowMainThreadQueries().build()
                      }
                      return instance as UserDatabase
                  }
                  return instance as UserDatabase
              }
              fun swapDatabase(context: Context, databaseFile: File): UserDatabase {
                  // If we need to swap then (instance is not null) close the database if it is open
                  if (instance != null) {
                      if (instance!!.isOpen) {
                          instance!!.close() // should commit database
                          instance == null
                      }
                  }
                  // instance should now be null and the original database closed
                  val dbpath = context.getDatabasePath(DBNAME) // just showing how to get path where database should exist
                  //copy database here from file
                  return getInstance(context, dbpath.name) // note in theory a different file name (database file name) could be used 
              }
          }
      }
      
      • 为了方便和简洁,使用了 .allowMainThreadQueries。

      示例

      下面的例子利用了上面的内容(尽管交换了 SAME 数据库):-

      class MainActivity : AppCompatActivity() {
      
          lateinit var db: UserDatabase
          lateinit var dao: AllDao
          override fun onCreate(savedInstanceState: Bundle?) {
              super.onCreate(savedInstanceState)
              setContentView(R.layout.activity_main)
              db = UserDatabase.getInstance(this)
              dao = db.getDao()
              dao.insertOneUser(UserEntity(0L,"UserFred","Fred@email.com","Fred","Bloggs","password"))
              for(u: UserEntity in dao.getAllUsers()) {
                  Log.d("USERINFODB1","UserId = ${u.userid} UserName = ${u.userName}")
              }
              // SWAP THE DATABASE (in this case to the same database)
              db = UserDatabase.swapDatabase(this,this.getDatabasePath("xxx"))
              for(u: UserEntity in dao.getAllUsers()) {
                  Log.d("USERINFODB2","UserId = ${u.userid} UserName = ${u.userName}")
              }
          }
      }
      

      运行时日志包括:-

      D/USERINFODB1: UserId = 0 UserName = UserFred
      D/USERINFODB2: UserId = 0 UserName = UserFred
      

      【讨论】:

      • 您的解决方案是正确的,但在我的情况下不是,首先,如果您使用不同的数据库,您会发现它不起作用,因为 dao 没有更新,其次是这种方法我需要在我使用它的每个地方存储对新数据库的引用。
      猜你喜欢
      • 1970-01-01
      • 2021-04-24
      • 2019-09-08
      • 2018-06-27
      • 2020-01-29
      • 2017-10-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多