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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 10:15:03