删除RecyclerView最后一项时应用崩溃报IndexOutOfBoundsException的原因与解决
问题根因定位
- 崩溃触发链路完全匹配堆栈日志:
IndexOutOfBoundsException: Index: 0, Size: 0直接来自MainActivity删除按钮点击时的错误调用逻辑 - 核心错误:删除操作中临时创建了全新的
LevelAdapter实例调用删除方法,而非使用当前绑定到RecyclerView上的真实Adapter - 越界逻辑拆解:
- 执行顺序先删除SQLite条目,再通过
getItemList()重新读取数据库,此时返回的列表已不包含刚删除的条目 - 当删除最后一个条目时,数据库被清空,
getItemList()返回size=0的空列表,传入临时创建的新Adapter - 对空列表执行
removeAt(0)操作,直接触发数组越界异常
- 执行顺序先删除SQLite条目,再通过
- 次要逻辑缺陷:Adapter的
deleteItem方法存在冗余刷新逻辑,空列表处理位置错误
修复方案
1. 重构MainActivity的Adapter持有逻辑
将RecyclerView绑定的Adapter提升为类成员变量,避免临时创建实例造成的数据源错位:
// 类成员变量声明 private lateinit var levelAdapter: LevelAdapter // 修正后的RecyclerView初始化方法 private fun recyclerViewSetUp() { // 固定设置LayoutManager,无需重复创建 binding.rvLevel.layoutManager = GridLayoutManager(this, 2) // 初始化Adapter时一次性绑定全量数据 levelAdapter = LevelAdapter(getItemList(), this) binding.rvLevel.adapter = levelAdapter }
2. 修正删除流程的调用逻辑
替换原有的错误删除代码,避免删除后重复读库、重复初始化列表:
tvDelete.setOnClickListener { // 从当前Adapter持有的数据源中获取对应位置的条目ID,无需重复查库 val targetId = levelAdapter.todoList[position].id // 执行数据库删除 MyDBHelper(this).deleteLabel(LevelModelClass(targetId, "")) // 操作当前展示的Adapter实例执行列表删除 levelAdapter.deleteItem(position) customDialog.dismiss() }
3. 修正Adapter的deleteItem方法
去掉冗余逻辑,增加边界判断,简化刷新流程:
fun deleteItem(position: Int) { // 增加越界保护 if (position < 0 || position >= todoList.size) return todoList.removeAt(position) // 仅做局部刷新即可,无需调用全量notifyDataSetChanged notifyItemRemoved(position) }
优化建议
- 不要为了简化逻辑反复替换RecyclerView的Adapter实例,这种写法会造成不必要的性能损耗,还容易引发数据源错位的崩溃
- 局部更新操作搭配
notifyItemRemoved时,不要重复调用notifyDataSetChanged(),否则会覆盖列表的删除动画、重置所有视图的绑定状态 - 所有涉及列表位置的操作,必须先校验position的合法性,避免因为RecyclerView视图复用、数据异步更新导致的位置错位问题
内容的提问来源于stack exchange,提问作者Mark Lee
相关产品推荐
相关产品推荐

