首页 > 解决方案 > Android:检查选定的数据库 .db 文件是否有效

问题描述

在我的应用程序中,我可以用用户选择的另一个文件替换当前数据库。

我只是想要一种方法来检查用户选择的文件是否是有效的数据库文件。

我让用户选择这样的文件:

    private fun selectDbFile() {
    val intent: Intent
    val selectIntent = Intent(Intent.ACTION_GET_CONTENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        type = "*/*" 
    }
    intent = Intent.createChooser(selectIntent, "Choose a file")
    intentLauncher.launch(intent)
}

并且 intentLauncher 定义如下:

    private var intentLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
    if (result.resultCode == Activity.RESULT_OK) { 
        val data: Intent? = result.data
        val uri = data?.data
        uri?.let{
            UtilityKt.replaceCurrentDb(requireContext(), uri) {
                showSuccessAlert()
            }
        } 
    }
}

用新的替换旧的db文件的方法是这样的:

    fun replaceCurrentDb(context: Context, newDbUri: Uri?, completion: () -> Unit) {
    newDbUri?.let {  newUri ->
        GlobalScope.launch(Dispatchers.IO) {
            // Do background work
            try {
                val currentDb = context.getDatabasePath(MenuSQLiteHelper.DATABASE_NAME)
                if (currentDb.exists()) {
                    val src = context.contentResolver.openInputStream(newUri)
                    val dst = FileOutputStream(currentDb)
                    val buffer = ByteArray(8 * 1024)
                    src?.copyTo(dst, buffer.size)
                    src?.close()
                    dst.close()
                    // Back to main thread
                    launch(Dispatchers.Main) {
                        completion()
                    }
                }
            }
            catch (e: IOException) {
                Toast.makeText(context, "Operation failed.", Toast.LENGTH_LONG).show()
                Log.e("DOWNLOAD FAIL!!!", " EXCEPTION IS:", e)
            }
        }
    }
}

只有当用户选择一个有效的 .db 文件时它才能正常工作,但是如果他选择了一个图像,例如,应用程序会崩溃,因为错误的数据被写入了目的地。

标签: androidkotlinandroid-sqlite

解决方案


我只是想要一种方法来检查用户选择的文件是否是有效的数据库文件。

如果您的意思是任何 SQLite 数据库,那么您可以检查标题,前 16 个字节将是"SQLite format 3\000"。这是检查此功能的示例:-

private fun isFileSQLiteDatabase(f: File): Boolean {
    val SQLITEFILEHEADER = "SQLite format 3\u0000"
    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
        } else {
            fis.close()
        }
    } catch (e: IOException) {
        return false
    }
    return true
}

但是,如果您想确保它不是任何SQLite 数据库,例如遵循一组模式的数据库,那么您需要进行更深入的检查,例如检查表和/或其他实体(触发器,索引,视图)与预期的一样。

例如,以下函数通过询问模式(即 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
}
  • 请注意,这会调用上一个函数。

推荐阅读