Kotlin跨平台iOS端SQLite加密迁移报SQLiteDatabaseCorruptException错误
报错根因说明
- 报错中出现Android包名属于SQLDelight的跨平台异常封装逻辑:SQLDelight在common层将所有平台的数据库损坏异常统一用
android/database/sqlite/SQLiteDatabaseCorruptException作为异常信息前缀抛出,iOS端实际触发的是原生SQLite的26错误码(文件不是数据库),并非真的调用了Android相关代码。 - upgrade逻辑的try/catch无法触发是因为崩溃发生在数据库初始化的
configConnection连接配置阶段,还未执行到升级回调逻辑。 - 核心错误原因:NativeSqliteDriver无法识别你迁移后的加密数据库文件,大概率是迁移代码存在bug、加密格式不兼容、依赖配置错误三类问题。
修复步骤
1. 修复迁移代码的bug
你的迁移逻辑存在重复释放数据库指针的问题,会导致加密文件写入异常,同时缺少加密有效性验证:
@ExperimentalUnsignedTypes override fun migrateToEncryptedDatabase(databasePath: String, temporaryDatabasePath: String, password: String) { val fileManager = NSFileManager.defaultManager() fileManager.createFileAtPath(temporaryDatabasePath, null, null) if (fileManager.fileExistsAtPath(databasePath)) { memScoped { val unencryptedDb: CPointerVar<sqlite3> = allocPointerTo() val encryptedDb: CPointerVar<sqlite3> = allocPointerTo() if (sqlite3_open(databasePath, unencryptedDb.ptr) == SQLITE_OK) { val exec1 = sqlite3_exec(unencryptedDb.value, "ATTACH DATABASE '$temporaryDatabasePath' AS encrypted KEY '$password';", null, null, null) val exec2 = sqlite3_exec(unencryptedDb.value, "SELECT sqlcipher_export('encrypted')", null, null, null) val exec3 = sqlite3_exec(unencryptedDb.value, "DETACH DATABASE encrypted;", null, null, null) val version = sqlite3_version sqlite3_close(unencryptedDb.value) // 第一次关闭未加密数据库 if (sqlite3_open(temporaryDatabasePath, encryptedDb.ptr) == SQLITE_OK) { sqlite3_key(encryptedDb.value, password.cstr, password.cstr.size) // 新增加密数据库验证逻辑,避免格式损坏 val stmt: CPointerVar<sqlite3_stmt> = allocPointerTo() if (sqlite3_prepare_v2(encryptedDb.value, "SELECT count(*) FROM sqlite_master", -1, stmt.ptr, null) == SQLITE_OK) { if (sqlite3_step(stmt.value) != SQLITE_ROW) { NSLog("加密数据库验证失败,密钥错误或格式损坏") } sqlite3_finalize(stmt.value) } sqlite3_close(encryptedDb.value) } // 删掉这行重复关闭的代码,此时unencryptedDb指针已经释放,会引发内存问题 // sqlite3_close(unencryptedDb.value) val error: ObjCObjectVar<NSError?> = alloc() val removeResult = fileManager.removeItemAtPath(databasePath, error.ptr) if (removeResult == false) { NSLog("Error removing db file: " + error.value) } val result = fileManager.moveItemAtPath(temporaryDatabasePath, databasePath, error.ptr) if (result == false) { NSLog("Error moving db file: " + error.value) } } else { NSLog("Failed to open the unencrypted DB with message: " + sqlite3_errmsg(unencryptedDb.value)) sqlite3_close(unencryptedDb.value) } } } }
2. 修复NativeSqliteDriver配置
给方法加@Throws注解避免直接崩溃,同时新增密钥有效性校验:
// 加@Throws注解,异常会作为NSError抛到Swift层,不会直接触发crash @Throws(Exception::class) fun initDatabaseDriver(password: String): NativeSqliteDriver { return try { NativeSqliteDriver(DatabaseConfiguration( name = DatabaseName, version = AppDatabase.Schema.version, create = { connection -> wrapConnection(connection) { AppDatabase.Schema.create(it) } }, upgrade = { connection, oldVersion, newVersion -> try { wrapConnection(connection) { NSLog("old version is ${oldVersion} new version is ${newVersion}") AppDatabase.Schema.migrate(it, oldVersion, newVersion) } } catch (exception: Exception) { NSLog("upgrade exception is ${exception.toString()}") throw exception // 异常需重抛,避免系统认为升级成功 } }, configConnection = { connection, _ -> val keyStatement = "PRAGMA key = \"$password\""; connection.withStatement(keyStatement) { stringForQuery() } // 新增加密校验,密钥错误时主动抛出可捕获的异常 val verifyStatement = "PRAGMA cipher_version" connection.withStatement(verifyStatement) { val cipherVersion = stringForQuery() if (cipherVersion.isNullOrEmpty()) { throw Exception("数据库密钥验证失败") } } } )) } catch (e: Exception) { NSLog("驱动初始化失败:${e.message}") throw e } }
3. 检查依赖与路径配置
- 确认gradle中引入的是SQLCipher版本的SQLDelight驱动:
app.cash.sqldelight:sqlite-driver-cipher,普通原生SQLite驱动无法识别SQLCipher加密格式,必然会报26错误码。 - 确认迁移逻辑中传入的
databasePath和NativeSqliteDriver读取的数据库路径完全一致,iOS端默认数据库存储路径为Documents目录,若你自定义了存储位置需要统一两边路径。
内容的提问来源于stack exchange,提问作者Ayohaych
相关产品推荐
相关产品推荐

