版本:recyclerView1.3.0
RecyclerView越界异常:getSpaceForSpanRange导致的ArrayIndexOutOfBoundsException,这个异常并不常见 java.lang.ArrayIndexOutOfBoundsException: length=5; index=5 at androidx.recyclerview.widget.GridLayoutManager.getSpaceForSpanRange(GridLayoutManager.java:364)
详细日志:只是Android源码里的异常,并未打印出自己业务逻辑代码那里出现异常。
java.lang.ArrayIndexOutOfBoundsException: length=5; index=5 at androidx.recyclerview.widget.GridLayoutManager.getSpaceForSpanRange(GridLayoutManager.java:364) at androidx.recyclerview.widget.GridLayoutManager.measureChild(GridLayoutManager.java:744) at androidx.recyclerview.widget.GridLayoutManager.layoutChunk(GridLayoutManager.java:619) at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1622) at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:687) at androidx.recyclerview.widget.GridLayoutManager.onLayoutChildren(GridLayoutManager.java:182) at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4604) at androidx.recyclerview.widget.RecyclerView.onMeasure(RecyclerView.java:3981) at android.view.View.measure(View.java:23608) at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6960) at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1535) at android.widget.LinearLayout.measureHorizontal(LinearLayout.java:1187) at android.widget.LinearLayout.onMeasure(LinearLayout.java:706) at android.view.View.measure(View.java:23608) at android.widget.FrameLayout.onMeasure(FrameLayout.java:254) at android.view.View.measure(View.java:23608) at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6960) at android.widget.FrameLayout.onMeasure(FrameLayout.java:185) at android.view.View.measure(View.java:23608) at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6960) at android.widget.FrameLayout.onMeasure(FrameLayout.java:185) at android.view.View.measure(View.java:23608)
经过多方排查并未找到原因。于是问下GPT如何复现上面问题。经过多方修改,终于可以复现demo。
class MainActivity2 : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
val spanCount = 4 // Set the span count to 5 (crucial!)
val layoutManager = MyGrideManager(this, spanCount)
recyclerView.layoutManager = layoutManager
// 创建一个会导致错误的 SpanSizeLookup
layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
// 通过为最后一个项目返回无效的跨度大小来模拟错误
Log.d(TAG, "getSpanSize position:$position")
return when (position) {
3 -> 2 // 特殊跨度项
else -> 1
}
}
override fun getSpanIndex(position: Int, spanCount: Int): Int {
Log.d(TAG, "getSpanIndex position:$position")
// Alternate the starting column between 0 and 3
return when (position) {
3 -> spanCount - 1 // 关键修改:起始位置设为最大值 4-1=3--》
else -> 0
}
}
}
val data = (0..15).map { "Item $it" }.toMutableList()
val adapter = MyAdapter(data)
recyclerView.adapter = adapter
// 添加布局后动态修改参数
recyclerView.post {
// 制造测量冲突
recyclerView.layoutParams.width = 0 // 强制无效测量
}
}
}
class MyAdapter(private val data: MutableList<String>) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val textView: TextView = itemView.findViewById(android.R.id.text1)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(android.R.layout.simple_list_item_1, parent, false)
return MyViewHolder(itemView)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.textView.text = data[position]
}
override fun getItemCount(): Int {
return data.size
}
}
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginStart="50dp"/>
通过上面可以手动上下滑动模拟出上面异常。然后重写GridLayoutManager的getSpaceForSpanRange方法来解决越界问题。
class MyGridLayoutManager(
context: Context,
spanCount: Int
) : GridLayoutManager(context, spanCount) {
/**
* java.lang.ArrayIndexOutOfBoundsException: length=5; index=5
* at androidx.recyclerview.widget.GridLayoutManager.getSpaceForSpanRange(GridLayoutManager.java:364)
* 版本:recyclerView1.3.0
*/
override fun getSpaceForSpanRange(startSpan: Int, spanSize: Int): Int {
var range = 0
try {
range = super.getSpaceForSpanRange(startSpan, spanSize)
} catch (e: Exception) {
if (mOrientation == VERTICAL && isLayoutRTL()) {
return mCachedBorders[mSpanCount - startSpan]
-mCachedBorders[mSpanCount - startSpan - spanSize]
} else {
val cacheSize = mCachedBorders.size
var index1 = startSpan + spanSize
if (index1 >= cacheSize) {
index1 = cacheSize - 1
if (index1 > startSpan) {
range = mCachedBorders[index1] - mCachedBorders[startSpan]
}
} else {
range = mCachedBorders[index1] - mCachedBorders[startSpan]
}
return range
}
}
return range
}
}
上面是解决思路,为什么会这样解决呢?因为没有找到线上代码具体异常点。无论怎样测试都是正常的,只有在线上海量用户出现过,并且无论怎么处理代码都无法解决。于是想出这个方案。特别对于比较复杂的项目,防止闪退是第一要务,只要不闪退,下次更新GridLayoutManager的时候就自然就正常了。这种方案特别适合频繁更新布局异常的情况。但不是最终解决办法,等找到原因再补充...