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

