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

GreenDao查询报CursorWindowAllocationException(-12)崩溃求助

问题背景

解析JSON后向GreenDao自动生成的数据表存入约3000行无多媒体类型字段的数据,调用如下checkGroupVisibility函数查询时触发崩溃:

@Synchronized
private fun checkGroupVisibility(
    dependencyList: ArrayList<Long>,
    dependencyType: String,
    mInspectionId: Long,
    isFromPhilosophyFlow: Boolean,
    daoSession: DaoSession?,
    string: String?
): Boolean {
    var show = true
//        Log.d("TAG", "checkGroupVisibility: lock testing 2"+string)
    if (!DataSanityUtils.isListEmpty(dependencyList)) {
        //check further in database if dependency question is answered
        val inspectionReportCarParList:List<InspectionReportCarPart>? = daoSession?.inspectionReportCarPartDao?.queryBuilder()
            ?.where(InspectionReportCarPartDao.Properties.InspectionID.eq(mInspectionId))
            ?.where(InspectionReportCarPartDao.Properties.QuestionId.`in`(dependencyList))
            ?.list()

        val inspectionReportCheckpointList:List<InspectionReportCheckpoint>? = daoSession?.inspectionReportCheckpointDao?.queryBuilder()
            ?.where(InspectionReportCheckpointDao.Properties.InspectionID.eq(mInspectionId))
            ?.where(InspectionReportCheckpointDao.Properties.ReportCheckpointVerdict.eq(CommonConstants.CHECKPOINT_VERDICT_NOT_OKAY))
            ?.list()

        for (checkPointId in dependencyList) {
            if (!isFromPhilosophyFlow && philosophyIdCheckPointMap.containsKey(checkPointId)) {
                show = false
                break
            }
            val isCarPartFound:InspectionReportCarPart?= inspectionReportCarParList?.find {
                it.questionId==checkPointId
            }

            if (isCarPartFound==null) {
                val isFound= inspectionReportCheckpointList?.find {
                    it.optionId==checkPointId
                }
                if (isFound == null) {
                    show = false
                    if (CommonConstants.DEPENDENCY_RELATION_AND.equals(
                            dependencyType,
                            ignoreCase = true
                        )
                    ) {
                        break
                    }
                } else if (CommonConstants.DEPENDENCY_RELATION_OR.equals(
                        dependencyType,
                        ignoreCase = true
                    )
                ) {
                    show = true
                    break
                }
            } else if (CommonConstants.DEPENDENCY_RELATION_OR.equals(
                    dependencyType,
                    ignoreCase = true
                )
            ) {
                show = true
                break
            }
        }
    }
//        Log.d("TAG", "checkGroupVisibility: lock testing 3:"+string)
    return show
}

崩溃精准触发于为inspectionReportCarParList赋值的第一条查询逻辑,报错信息为android.database.CursorWindowAllocationException: Could not allocate CursorWindow of size 104857600 due to error -12,完整堆栈如下:

android.database.CursorWindowAllocationException: Could not allocate CursorWindow '/data/user/0/com.example.visor/databases/report.json-db' of size 104857600 due to error -12.
    at android.database.CursorWindow.nativeCreate(CursorWindow.java)
    at android.database.CursorWindow.<init>(CursorWindow.java:139)
    at android.database.CursorWindow.<init>(CursorWindow.java:120)
    at android.database.AbstractWindowedCursor.clearOrCreateWindow(AbstractWindowedCursor.java:202)
    at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:149)
    at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:142)
    at de.greenrobot.dao.AbstractDao.loadAllFromCursor(AbstractDao.java:371)
    at de.greenrobot.dao.AbstractDao.loadAllAndCloseCursor(AbstractDao.java:184)
    at de.greenrobot.dao.InternalQueryDaoAccess.loadAllAndCloseCursor(InternalQueryDaoAccess.java:21)
    at de.greenrobot.dao.query.Query.list(Query.java:130)
    at de.greenrobot.dao.query.QueryBuilder.list(QueryBuilder.java:353)
    at com.example.inspectionreport.utils.ReportUtils.checkGroupVisibility(ReportUtils.kt:31)

当前项目使用GreenDao版本为de.greenrobot:greendao:2.0.0,预期GreenDao会自动处理所有Cursor操作、查询完成后自动关闭Cursor,实际仍出现CursorWindow内存占用过高导致崩溃。

根因分析
  • 报错里的错误码-12是系统内存不足的标准错误码,申请的CursorWindow大小达到100MB,远超过Android系统默认给单个CursorWindow分配的2MB阈值,本质是单次查询加载的结果集过大,申请内存时直接失败。
  • GreenDao 2.0.0的list()方法确实会自动关闭Cursor,但这个关闭动作是全量查询结果全部加载到内存之后才执行的,大结果集加载过程中需要申请的大块内存在加载阶段就会触发OOM,和Cursor最终是否关闭没有关系。
  • 现有查询逻辑存在严重的性能浪费:把指定InspectionID下所有匹配的CarPart记录、所有NOT_OKAY状态的Checkpoint记录全量拉到内存,再在内存里遍历dependencyList做匹配,把本该数据库层完成的筛选逻辑挪到应用层执行,平白放大了内存开销。只要对应InspectionID下关联的记录数达到几万条,哪怕单条记录没有多媒体字段,全量加载也会直接撑爆CursorWindow。
  • 若传入in条件的dependencyList长度过大,会生成超长SQL语句,进一步拖慢查询效率、提升内存占用。
修复方案
  • 把匹配逻辑下推到数据库层,禁止全量拉取大结果集到内存做遍历。原逻辑只需要判断对应ID的记录是否存在,直接用count()查询替代全量实体查询即可,不需要加载任何完整表数据,内存开销可以降低几个数量级,优化后的代码如下:
@Synchronized
private fun checkGroupVisibility(
    dependencyList: ArrayList<Long>,
    dependencyType: String,
    mInspectionId: Long,
    isFromPhilosophyFlow: Boolean,
    daoSession: DaoSession?,
    string: String?
): Boolean {
    var show = true
    if (DataSanityUtils.isListEmpty(dependencyList)) {
        return show
    }
    val carPartDao = daoSession?.inspectionReportCarPartDao
    val checkpointDao = daoSession?.inspectionReportCheckpointDao
    val isAndRelation = CommonConstants.DEPENDENCY_RELATION_AND.equals(dependencyType, ignoreCase = true)
    val isOrRelation = CommonConstants.DEPENDENCY_RELATION_OR.equals(dependencyType, ignoreCase = true)

    for (checkPointId in dependencyList) {
        if (!isFromPhilosophyFlow && philosophyIdCheckPointMap.containsKey(checkPointId)) {
            show = false
            break
        }
        // 仅查询匹配条数判断存在性,不加载全量实体
        val carPartExistCount = carPartDao?.queryBuilder()
            ?.where(InspectionReportCarPartDao.Properties.InspectionID.eq(mInspectionId))
            ?.where(InspectionReportCarPartDao.Properties.QuestionId.eq(checkPointId))
            ?.count() ?: 0L

        if (carPartExistCount == 0L) {
            val checkpointExistCount = checkpointDao?.queryBuilder()
                ?.where(InspectionReportCheckpointDao.Properties.InspectionID.eq(mInspectionId))
                ?.where(InspectionReportCheckpointDao.Properties.ReportCheckpointVerdict.eq(CommonConstants.CHECKPOINT_VERDICT_NOT_OKAY))
                ?.where(InspectionReportCheckpointDao.Properties.OptionId.eq(checkPointId))
                ?.count() ?: 0L

            if (checkpointExistCount == 0L) {
                show = false
                if (isAndRelation) break
            } else if (isOrRelation) {
                show = true
                break
            }
        } else if (isOrRelation) {
            show = true
            break
        }
    }
    return show
}
  • 若业务场景确实需要批量查询列表数据,必须通过limit()、offset()做分页,单次查询返回的结果条数控制在500条以内,避免单次申请过大的CursorWindow内存块。
  • 给查询用到的筛选字段加数据库索引:为InspectionReportCarPart表的InspectionID、QuestionId字段,InspectionReportCheckpoint表的InspectionID、ReportCheckpointVerdict、OptionId字段建立索引,可大幅提升查询速度,降低查询过程中的临时内存开销。
  • 如果dependencyList长度超过1000,把in查询拆分为多次小批量查询,避免生成超长SQL导致SQLite执行异常、内存占用飙升。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 14:06:25