Activity重建后填充RecyclerView静态列表触发数组越界崩溃排查
问题描述
应用在Activity重建后频繁崩溃,崩溃堆栈如下:
Choreographer.java line 911 Fatal Exception: java.lang.IndexOutOfBoundsException: Invalid index 0, size is 0 at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255) at java.util.ArrayList.get(ArrayList.java:308) at android.animation.ObjectAnimator.start(ObjectAnimator.java:817) at android.animation.AnimatorSet$DependencyListener.startIfReady(AnimatorSet.java:799) at android.animation.AnimatorSet$DependencyListener.onAnimationEnd(AnimatorSet.java:753) at android.animation.ValueAnimator.endAnimation(ValueAnimator.java:1239) at android.animation.ValueAnimator.cancel(ValueAnimator.java:1140) at android.animation.ObjectAnimator.start(ObjectAnimator.java:820) at android.animation.AnimatorSet$DependencyListener.startIfReady(AnimatorSet.java:799) at android.animation.AnimatorSet$DependencyListener.onAnimationEnd(AnimatorSet.java:753) at android.animation.ValueAnimator.endAnimation(ValueAnimator.java:1239) at android.animation.ValueAnimator.cancel(ValueAnimator.java:1140) at android.animation.ObjectAnimator.animateValue(ObjectAnimator.java:974) at android.animation.ValueAnimator.animationFrame(ValueAnimator.java:1384) at android.animation.ValueAnimator.doAnimationFrame(ValueAnimator.java:1427) at android.animation.ValueAnimator$AnimationHandler.doAnimationFrame(ValueAnimator.java:759) at android.animation.ValueAnimator$AnimationHandler$1.run(ValueAnimator.java:801) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:911) at android.view.Choreographer.doCallbacks(Choreographer.java:686) at android.view.Choreographer.doFrame(Choreographer.java:619) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:897) at android.os.Handler.handleCallback(Handler.java:739) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:148) at android.app.ActivityThread.main(ActivityThread.java:7409) at java.lang.reflect.Method.invoke(Method.java) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)
RecyclerView初始化代码:
private fun initRecyclerView() { val list = ArrayList<HomeFeature>() list.add(HomeFeature(id = 0, iconId = R.drawable.ic_home_collage, imageId = R.drawable.ic_home_bg_collage, title = globalActivity.resources.getString(R.string.home_title_1))) list.add(HomeFeature(id = 2, iconId = R.drawable.ic_home_edit, imageId = R.drawable.ic_home_bg_camera, title = globalActivity.resources.getString(R.string.home_title_3))) list.add(HomeFeature(id = 1, iconId = R.drawable.ic_home_camera, imageId = R.drawable.ic_home_bg_edit, title = globalActivity.resources.getString(R.string.home_title_2))) list.add(HomeFeature(id = 3, iconId = R.drawable.ic_home_filters, imageId = R.drawable.ic_home_bg_filters, title = globalActivity.resources.getString(R.string.home_title_4))) list.add(HomeFeature(id = 5, iconId = R.drawable.ic_home_settings, imageId = R.drawable.ic_home_bg_settings, title = globalActivity.resources.getString(R.string.home_title_6))) list.add(HomeFeature(id = 4, iconId = R.drawable.ic_home_saved, imageId = R.drawable.ic_home_bg_saved, title = globalActivity.resources.getString(R.string.home_title_5))) binding.incItemCollageHome.item = list[0] binding.incItemEditHome.item = list[1] binding.incItemCollageHome.itemClick = itemClick binding.incItemEditHome.itemClick = itemClick val adapter = AdapterHome(itemClick) binding.rvFeaturesHome.adapter = adapter val subList = list.subList(2, 6) adapter.submitList(subList) }
Adapter实现代码:
class AdapterHome(private val onItemClickListener: OnItemClickListener) : ListAdapter<HomeFeature, AdapterHome.ViewHolderHome>(diffUtilHome) { companion object { val diffUtilHome = object : DiffUtil.ItemCallback<HomeFeature>() { override fun areItemsTheSame(oldItem: HomeFeature, newItem: HomeFeature): Boolean { return oldItem.id == newItem.id } override fun areContentsTheSame(oldItem: HomeFeature, newItem: HomeFeature): Boolean { return oldItem == newItem } } } inner class ViewHolderHome(val binding: ListItemHomeBinding) : RecyclerView.ViewHolder(binding.root) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolderHome { val layoutInflater = LayoutInflater.from(parent.context) val binding = DataBindingUtil.inflate<ListItemHomeBinding>(layoutInflater, R.layout.list_item_home, parent, false) return ViewHolderHome(binding) } override fun onBindViewHolder(holder: ViewHolderHome, position: Int) { val homeFeature = getItem(position) holder.binding.apply { item = homeFeature itemClick = onItemClickListener } } }
崩溃原因分析
- 核心触发点:崩溃来自
ObjectAnimator尝试访问一个size为0的ArrayList的索引0,属于数组越界异常。 - 关联场景:仅在Activity重建后出现,结合代码逻辑,主要有两个可能:
- SubList生命周期问题:代码中使用
list.subList(2,6)作为Adapter数据源,而subList是原ArrayList的视图(非独立副本),依赖原list的引用。当原list作为局部变量在方法执行完毕后,若因内存回收或Activity重建导致原list失效,subList的内部状态会异常,size变为0,动画执行时触发越界。 - 残留动画干扰:Activity重建前,RecyclerView的默认ItemAnimator可能仍在执行动画,重建后旧的动画回调未被清理,此时旧数据源已销毁,动画回调访问空列表导致崩溃。
- SubList生命周期问题:代码中使用
解决方法
方法1:将SubList转为独立列表副本
切断SubList与原局部list的依赖,创建独立的ArrayList实例:
// 替换原subList代码 val subList = ArrayList(list.subList(2, 6)) adapter.submitList(subList)
方法2:清理Activity重建时的残留动画
在onDestroyView中主动停止RecyclerView的所有动画,解除旧Adapter关联:
override fun onDestroyView() { super.onDestroyView() binding.rvFeaturesHome.itemAnimator?.run { endAnimations() isRunning = false } binding.rvFeaturesHome.adapter = null }
方法3:复用Adapter避免重复创建
将Adapter作为Fragment/Activity的成员变量,重建时复用实例,仅更新数据:
// 定义成员变量 private lateinit var adapter: AdapterHome private fun initRecyclerView() { // ... 初始化list的代码 ... if (!::adapter.isInitialized) { adapter = AdapterHome(itemClick) binding.rvFeaturesHome.adapter = adapter } val subList = ArrayList(list.subList(2, 6)) adapter.submitList(subList) }
方法4:检查自定义视图动画
检查头部视图incItemCollageHome、incItemEditHome的布局或绑定逻辑,确认是否存在ObjectAnimator持有旧数据引用的情况,重建时重置相关动画。
内容的提问来源于stack exchange,提问作者Sohaib Ahmed
相关产品推荐
相关产品推荐

