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

Android Room中Blob最大尺寸?文档位置及运行时获取方法

Android Room存储Blob超出尺寸导致检索失败的问题及解决方案

问题现象

使用Android Room将ByteArray存储为Blob时,当Blob超过特定尺寸,检索行会失败,报错如下:

E/CursorWindow: The size of (0, 2) is too big (4194304), so replace the value to NULL

实际崩溃原因:

java.lang.NullPointerException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkNotNullParameter, parameter bytes

可见当Blob超过某一尺寸时,CursorWindow无法加载该Blob并返回null,而DAO方法因实体字段不可空会抛出NPE。

通过实验确定当前环境下该上限为4194304字节(4096*1024)。

疑问点

  • 该限制的相关官方文档在哪里?
  • 它是否会因设备、Room/Sqlite版本等因素变化?
  • 能否在运行时获取该值?

希望在插入数据前强制执行合适的字节数组限制以避免错误,正在寻找可靠的方法确定实际限制。

更新:最优解决方案

@MikeT的方案是目前最优解。android.database.sqlite可检索的最大行尺寸基本无官方文档;多数情况约为2MB,但在Android 12环境下测得的上限有时约为4MB。以下是该方案的Kotlin版本,无论环境是对超大行抛出异常还是返回null(如当前环境,此时Room实体非空字段会导致DAO方法抛出NPE),该方案均适用:

fun checkCursorWindowSize() {
    val context = this
    val TEST_TABLE_NAME = "tcw"
    val TEST_DATABASE_NAME = "tcw"
    val TEST_COLUMN_NAME = "testblob"
    val reducer = 1024 * 1024 / 16 /* 每次迭代减小的BLOB尺寸 */
    val allowance =
        1 /* 所有提取操作中额外减小的尺寸(避免刚好卡在2的幂边界上) */
    val ba = ByteArray(1024 * 1024 * 4) /* 存储时的初始BLOB尺寸 */
    var csr: Cursor
    val tcwDbPath: String = context.getDatabasePath(TEST_DATABASE_NAME).path
    val dbFile: File = context.getDatabasePath(TEST_DATABASE_NAME)
    val dbDirectory: File = dbFile.parentFile!!

    /* 删除已存在的测试数据库 */
    if (dbFile.exists()) {
        dbFile.delete()
    }
    /* 确保数据库目录存在,否则openOrCreate会失败 */
    if (!dbDirectory.exists()) {
        Log.d(
            "DEBUGINFO",
            "数据库目录 ${dbDirectory.path} 不存在,正在创建"
        )
        dbDirectory.mkdirs()
    }

    /* 创建测试数据库 */
    val db = SQLiteDatabase.openOrCreateDatabase(tcwDbPath, null, null)
    /* 在事务内执行所有操作以减少写入次数 */
    db.beginTransaction()
    /* 创建存储BLOB的测试表 */
    db.execSQL("CREATE TABLE IF NOT EXISTS $TEST_TABLE_NAME ($TEST_COLUMN_NAME BLOB)")
    /* 插入超大尺寸的BLOB */
    val cv = ContentValues()
    cv.put(TEST_COLUMN_NAME, ba)
    db.insert(TEST_TABLE_NAME, null, cv)
    /* 准备循环提取逐渐缩小的BLOB片段 */
    var retrieveSize = ba.size /* 首次尝试提取完整BLOB */
    var caught = 0 /* 捕获到的异常次数 */
    /* 最多尝试1024次缩小尺寸 */
    for (i in 0..1024) {
        Log.d("DEBUG", "第 ${i + 1} 次迭代,尝试提取尺寸:$retrieveSize")
        try {
            /* 使用SQLite的substr函数提取指定长度的BLOB片段
               仅获取一行数据(测试表中只有一行)
               注意:查询超大BLOB本身不会失败,失败发生在调用getBlob读取数据时
             */
            csr = db.query(
                TEST_TABLE_NAME,
                arrayOf("substr($TEST_COLUMN_NAME,1,${retrieveSize - allowance}) AS $TEST_COLUMN_NAME"),
                null,
                null,
                null,
                null,
                null,
                "1"
            )
            var test: ByteArray? = null
            while (csr.moveToNext()) {
                test = csr.getBlob(0) /* 读取第一列的Blob数据 */
                Log.d("DEBUG", "成功获取数据尺寸:${test?.size}")
            }
            if (test != null) {
                break /* 成功获取到数据,退出循环 */
            }

        } catch (e: SQLException) {
            caught++
            //e.printStackTrace();
        } finally {
            retrieveSize -= reducer /* 减小提取尺寸,准备下一次迭代 */
            if (retrieveSize < 0) break
        }
    }
    Log.d("DEBUGINFO", "共捕获 $caught 次SQLite异常")
    /* 清理数据库资源 */
    db.endTransaction()
    db.close()
    File(tcwDbPath).delete()
    /* 修正最终的最大尺寸(因为最后一次循环已经减小了尺寸) */
    Log.d("DEBUGINFO", "最大CursorWindow支持尺寸 = ${(retrieveSize + reducer).toLong()}")
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 12:06:30