You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android开发:如何校验用户选择的.db数据库文件是否合法有效

SQLite数据库文件有效性校验实现方案

1. 优化文件选择器,降低用户选错概率

首先修改文件选择逻辑,限制可选文件的MIME类型,优先筛选SQLite数据库文件,减少用户误选其他文件的可能:

private fun selectDbFile() {
    val selectIntent = Intent(Intent.ACTION_GET_CONTENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        // 优先匹配SQLite数据库的MIME类型,添加兼容类型适配不同文件管理器
        type = "application/x-sqlite3"
        putExtra(Intent.EXTRA_MIME_TYPES, arrayOf(
            "application/x-sqlite3", 
            "application/octet-stream",
            "application/sqlite"
        ))
    }
    val intent = Intent.createChooser(selectIntent, "选择数据库文件")
    intentLauncher.launch(intent)
}

2. 多层校验逻辑实现(核心修改)

校验逻辑遵循「先校验再替换」的原则,全程不直接修改原数据库文件,避免校验失败导致原数据库损坏,共分三层校验:

  • 第一层:校验SQLite文件固定魔数
  • 第二层:校验文件是否可以正常打开为SQLite数据库
  • 第三层:校验数据库表结构与应用要求匹配

3. 修改后的替换逻辑完整实现

fun replaceCurrentDb(context: Context, newDbUri: Uri?, completion: () -> Unit) {
    newDbUri ?: return
    CoroutineScope(Dispatchers.IO).launch {
        try {
            val contentResolver = context.contentResolver
            val src = contentResolver.openInputStream(newDbUri) ?: throw IOException("无法读取选中的文件")

            // 第一层校验:检查SQLite文件头魔数(前16字节固定为"SQLite format 3\0")
            val magicHeader = ByteArray(16)
            val readCount = src.read(magicHeader)
            val sqliteStandardHeader = "SQLite format 3\u0000".toByteArray()
            if (readCount < 16 || !magicHeader.contentEquals(sqliteStandardHeader)) {
                withContext(Dispatchers.Main) {
                    Toast.makeText(context, "选中的文件不是有效的SQLite数据库文件", Toast.LENGTH_LONG).show()
                }
                src.close()
                return@launch
            }

            // 把选中的文件复制到缓存目录的临时文件,用于后续校验,不直接修改原数据库
            val tempDbFile = File(context.cacheDir, "temp_verify_db.db")
            if (tempDbFile.exists()) tempDbFile.delete()
            val tempOutStream = FileOutputStream(tempDbFile)
            src.copyTo(tempOutStream)
            src.close()
            tempOutStream.close()

            // 第二层校验:尝试打开数据库,检查是否损坏
            var tempDb: SQLiteDatabase? = null
            try {
                tempDb = SQLiteDatabase.openDatabase(tempDbFile.absolutePath, null, SQLiteDatabase.OPEN_READONLY)
            } catch (e: SQLiteException) {
                withContext(Dispatchers.Main) {
                    Toast.makeText(context, "数据库文件已损坏,无法打开", Toast.LENGTH_LONG).show()
                }
                tempDbFile.delete()
                return@launch
            }

            // 第三层校验:检查应用需要的表是否都存在,确保结构匹配
            // 把你应用里必须的表名替换到下面的数组里
            val requiredTables = arrayOf("table1", "table2", "table3")
            var allTablesExist = true
            for (tableName in requiredTables) {
                val cursor = tempDb.rawQuery(
                    "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?",
                    arrayOf(tableName)
                )
                cursor.moveToFirst()
                val count = cursor.getInt(0)
                cursor.close()
                if (count == 0) {
                    allTablesExist = false
                    break
                }
            }
            tempDb.close()
            if (!allTablesExist) {
                withContext(Dispatchers.Main) {
                    Toast.makeText(context, "数据库文件结构与应用不匹配", Toast.LENGTH_LONG).show()
                }
                tempDbFile.delete()
                return@launch
            }

            // 所有校验通过,替换原数据库文件
            val originalDbFile = context.getDatabasePath(MenuSQLiteHelper.DATABASE_NAME)
            // 先关闭原数据库连接,避免替换失败
            MenuSQLiteHelper.getInstance(context).close()
            tempDbFile.copyTo(originalDbFile, overwrite = true)
            tempDbFile.delete()

            // 回到主线程回调成功
            withContext(Dispatchers.Main) {
                completion()
            }
        } catch (e: IOException) {
            withContext(Dispatchers.Main) {
                Toast.makeText(context, "操作失败:${e.message}", Toast.LENGTH_LONG).show()
            }
            Log.e("DB_REPLACE", "替换数据库异常", e)
        }
    }
}

额外注意事项

  • 替换数据库成功后,需要重新初始化你的数据库实例,避免应用仍使用旧的缓存连接报错
  • 不推荐使用GlobalScope,建议根据实际场景替换为viewModelScope或者lifecycleScope,避免内存泄漏

内容的提问来源于stack exchange,提问作者Badr Bujbara

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 10:27:03