首页 > 解决方案 > 如何在从它恢复之前验证 sqlite 文件是否属于我的 android 应用程序

问题描述

很久以前,我在我的应用程序中实现了简单的备份/恢复解决方案。原理很简单,创建备份时将内部数据库复制到外部存储,从备份恢复时 - 将外部 db-file 复制到内部数据库。

但是当我尝试从另一个应用程序(显然不包含我的应用程序需要的表)的备份文件中恢复时,问题就来了。我如何验证somefile.db或者somefile.sqlite实际上是我的应用程序的备份?它确实包含所需的架构。因为如果我从随机 file.db 恢复,我的应用程序崩溃并显示数据库不包含所需表的消息

fun restoreDbFromUri(uri: Uri): Boolean {
    try {
        val cr = context.contentResolver

        cr.openFileDescriptor(uri, "r")
            ?.use { pfd ->
                FileInputStream(pfd.fileDescriptor)
                    .use { fis -> // TODO before coping, I need verify that this file is actually correct database, with all needed tables
                        dbOpenHelper.close()
                        internalDbFile.transferFrom(fis)                           

                        return true
                    }
            }
    } catch (e: Exception) {
        presenter.toastPresenter.onShowErrorToast(e.localizedMessage)
        e.printStackTrace()
    }

    presenter.toastPresenter.onShowErrorToast()
    return false
}

val internalDbFile: File
    get() = context.getDatabasePath("prana_breath.sqlite")

@Throws(IOException::class)
fun File.transferFrom(srcInput: FileInputStream) {
    FileOutputStream(this).use { dstOut ->
        dstOut.channel.use { dstChannel ->
            srcInput.channel.use { srcChannel ->
                dstChannel.transferFrom(srcChannel)
            }
        }
    }
}

@Throws(IOException::class)
fun FileChannel.transferFrom(srcChannel: FileChannel) {
    this.transferFrom(srcChannel, 0, srcChannel.size())
}

标签: androidsqlitebackup

解决方案


您可以先打开文件并检查标头的前 16 个字节,它必须是"SQLite format 3\000"

例如:-

const val SQLITEFILEHEADER = "SQLite format 3\u0000"
private fun isFileSQLiteDatabase(f: File): Boolean {
    val fis: InputStream
    if (!f.isFile) return false
    val header = ByteArray(16)
    try {
        fis = FileInputStream(f)
        fis.read(header)
        if (String(header) != SQLITEFILEHEADER) {
            fis.close()
            return false
        }
    } catch (e: IOException) {
        return false
    }
    return true
}

这将消除几乎所有非数据库文件。然后检查数据库是否具有预期的表(和/或其他实体),您可以使用 SQliteDatabase 的openDatabase方法之一将文件作为 SQLiteDatabase打开,然后查询sqlite_master表以检查预期的表(和/或其他实体)按预期存在,确保您关闭数据库。

所以你可以有一些类似的东西: -

private fun isDBValid(f: File, entityList: Array<String>): Boolean {

    var matchcount = 0
    if (!isFileSQLiteDatabase(f)) return false
    try {
        val db = SQLiteDatabase.openDatabase(f.path, null, SQLiteDatabase.OPEN_READWRITE)
        val csr = db.query("sqlite_master", null, null, null, null, null, null)
        while (csr.moveToNext()) {
            for (s in entityList) {
                if (s.toLowerCase() == csr.getString(csr.getColumnIndex("name")).toLowerCase()) {
                    matchcount++
                    break
                }
            }
        }
        csr.close()
        db.close()
    } catch (e: SQLiteException) {
        return false;
    }
    return matchcount != entityList.size
}
  • 传递的字符串数组将是必须找到的表(或其他实体、索引、视图、触发器等)的列表。

然后你可以使用: -

fun restore DbFromUri(uri: Uri): Boolean {
    //<<<<<  DB VERIFICATION >>>>>
    if (isDBValid(File(uri.path), requiredEntities)) {
    } else {
        return false
    }
    try {
        val cr = context.contentResolver

        cr.openFileDescriptor(uri, "r")
            ?.use { pfd ->
                FileInputStream(pfd.fileDescriptor)
                    .use { fis -> // TODO before coping, I need verify that this file is actually correct database, with all needed tables !!!!DONE ABOVE!!!!
                        dbOpenHelper.close()
                        internalDbFile.transferFrom(fis)                           

                        return true
                    }
            }
    } catch (e: Exception) {
        presenter.toastPresenter.onShowErrorToast(e.localizedMessage)
        e.printStackTrace()
    }

    presenter.toastPresenter.onShowErrorToast()
    return false
}
  • 请注意,以上是原则代码,未经测试或运行,因此可能包含一些错误。

推荐阅读