前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >FlexboxLayoutManager崩溃记

FlexboxLayoutManager崩溃记

作者头像
韦东锏
发布2021-09-29 15:18:57
1.9K0
发布2021-09-29 15:18:57
举报
文章被收录于专栏:Android码农

记录一个FlexboxLayoutManager内部崩溃的排查过程

背景

  • 崩溃发生在FlexboxLayoutManager内部
  • 没有与项目代码直接关联的信息
  • 在小米11上容易复现,其他机型没有复现

首先看下崩溃log

代码语言:javascript
复制
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文件来定位

根据mapping定位崩溃代码

项目采用R8混淆,mapping的代码会不太一样,先看下最终崩溃时候的log信息

代码语言:javascript
复制
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

查出来的结果如下

代码语言:javascript
复制
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方法,继续看这个方法的混淆信息

代码语言:javascript
复制
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,没有赋行数值

找到原因后,继续看下项目源码

代码语言:javascript
复制
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了,找到原因后,修改就很简单了

代码语言:javascript
复制
holder.binding.rootview.visibility = View.INVISIBLE

至此,问题得以修复

总结

在使用FlexboxLayoutManager的时候,不要把itemview设置为Gone,其实这个问题,在FlexboxLayoutManager控件内部,也很容易修复规避,希望后续的版本可以修复吧

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-07-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Android码农 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 根据mapping定位崩溃代码
  • 崩溃溯源
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档