记录一个FlexboxLayoutManager内部崩溃的排查过程
首先看下崩溃log
Caused by: java.lang.ArrayIndexOutOfBoundsException: length=10; index=-1
at java.util.ArrayList.get(ArrayList.java:439)
at com.google.android.flexbox.FlexboxLayoutManager.f(FlexboxLayoutManager.java:4)
at com.google.android.flexbox.FlexboxLayoutManager.computeScrollOffset(FlexboxLayoutManager.java:4)
at com.google.android.flexbox.FlexboxLayoutManager.computeVerticalScrollOffset(FlexboxLayoutManager.java:1)
at androidx.recyclerview.widget.RecyclerView.computeVerticalScrollOffset(RecyclerView.java:2)
at android.view.View.canScrollVertically(View.java:19750)
at androidx.recyclerview.widget.RecyclerView$o.onInitializeAccessibilityEvent(RecyclerView.java:3)
at androidx.recyclerview.widget.RecyclerView$o.onInitializeAccessibilityEvent(RecyclerView.java:1)
at d.r.a.s.onInitializeAccessibilityEvent(RecyclerViewAccessibilityDelegate.java:5)
at d.h.i.a$a.onInitializeAccessibilityEvent(AccessibilityDelegateCompat.java:1)
****省略中间部分****
at android.os.Looper.loop(Looper.java:233)
at android.app.ActivityThread.main(ActivityThread.java:7892)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:656)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:967)
可以发现,崩溃是由于数据越界导致的,具体崩溃的代码,需要配合mapping文件来定位
项目采用R8混淆,mapping的代码会不太一样,先看下最终崩溃时候的log信息
ArrayIndexOutOfBoundsException: length=10; index=-1
ArrayList.get(ArrayList.java:439)
FlexboxLayoutManager.f(FlexboxLayoutManager.java:4)
FlexboxLayoutManager.computeScrollOffset(FlexboxLayoutManager.java:4)
可以知道,是computeScrollOffset
方法,调用混淆后的f
的方法,然后再调用arrayList触发越界崩溃
另外可以知道arraylist的size是10,获取的位置是-1,而导致的崩溃
先通过如下正则表达式,在mapping查询computeScrollOffset的混淆地方
computeScrollOffset.*->\scomputeScrollOffset
查出来的结果如下
1:1:int computeScrollOffset(androidx.recyclerview.widget.RecyclerView$State):2288:2288 -> computeScrollOffset
2:5:int computeScrollOffset(androidx.recyclerview.widget.RecyclerView$State):2291:2294 -> computeScrollOffset
6:9:int computeScrollOffset(androidx.recyclerview.widget.RecyclerView$State):2298:2301 -> computeScrollOffset
10:10:int computeScrollOffset(androidx.recyclerview.widget.RecyclerView$State):2300:2300 -> computeScrollOffset
11:11:int computeScrollOffset(androidx.recyclerview.widget.RecyclerView$State):2302:2302 -> computeScrollOffset
12:12:int computeScrollOffset(androidx.recyclerview.widget.RecyclerView$State):2306:2306 -> computeScrollOffset
13:16:int computeScrollOffset(androidx.recyclerview.widget.RecyclerView$State):2310:2310 -> computeScrollOffset
看上面的崩溃log,computeScrollOffset
后面跟的行数信息是java:4
,说明对应的是上面的mapping的第二行
2:5
对应的行数是2291:2294
,因为崩溃地方是4,所以对应的行数是2293,看下源码
原来是调用了findLastReferenceChild
方法,继续看这个方法的混淆信息
1:1:android.view.View findLastReferenceChild(int):1213:1213 -> f
2:5:android.view.View findLastReferenceChild(int):1217:1220 -> f
可以发现,混淆后确实是叫f
,跟崩溃log一致,通过log:FlexboxLayoutManager.f(FlexboxLayoutManager.java:4)
可以知道崩溃的地方的行数也是4,对应上面查的第二行
2:5
对应的源码行数是1217:1220
,所以崩溃的行数是1219,我们看下源码
可以知道是mFlexLines.get(lastFoundLinePosition)
导致的崩溃
由于有机子可以稳定复现,重新复现后,定位下崩溃时候的信息,debug看崩溃前一行的断点信息:mFlexboxHelper.mIndexToFlexLine[lastFoundPosition];
获取的lastFoundLinePosition
是4,所以拿到的结果是-1,从而导致的崩溃
接下来继续定位mIndexToFlexLine
赋值的地方,先是赋值-1的地方,通过源码,可以发现是用Arrays.fill
来赋值为-1,作为初始值
通过源码注释说明,可以知道mIndexToFlexLine
是保存每一个item所在的行数的信息,比如
mIndexToFlexLine[3] = 2;说明第4个item(第一个item位置是0),是展示在第二行
接下来是赋值行数的地方
断点发现,行数i
没有走到4这个崩溃的值,只走到了3,我们往方法前面继续追溯,找到了罪魁祸首
因为这个view是Gone,没有赋行数值
找到原因后,继续看下项目源码
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is SearchRecordViewHolder) {
holder.binding.title = data[position]
} else {
if (showExpandView) {
**此处省略**
} else {
holder.binding.rootview.visibility = View.GONE
}
}
}
果然把itemview设置为gone了,找到原因后,修改就很简单了
holder.binding.rootview.visibility = View.INVISIBLE
至此,问题得以修复
在使用FlexboxLayoutManager
的时候,不要把itemview设置为Gone,其实这个问题,在FlexboxLayoutManager控件内部,也很容易修复规避,希望后续的版本可以修复吧