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
相关产品推荐
相关产品推荐

