博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
HeaderViewListAdapter.isEnabled数组越界问题分析
阅读量:6262 次
发布时间:2019-06-22

本文共 4722 字,大约阅读时间需要 15 分钟。

问题介绍

Android应用在bugly上偶现调用View.draw()时出现数据越界,根据bugly提供的解决方案如下,

1.遍历数组/字符串等集合前,要判断遍历对象的长度;2.操作数组/字符串等集合前,要检查角标是否在长度允许范围内;3.ListView操作不当也会引起该异常,这种情况下一般是由于List渲染的时候,外面的数据源发生变化导致的。举例如ListView滚动时点击刷新将会报错,解决方法是ListView滚动时将刷新置为不可点击。或者改变数据源之前调用adapter的notifyDataSetInvalidated()方法将原数据源设置为无效。

但由于无项目源码相关的堆栈提示信息,此问题一直被搁置。

分析思路

出错堆栈信息如下

1 java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255)2 java.util.ArrayList.get(ArrayList.java:308)3 android.widget.HeaderViewListAdapter.isEnabled(HeaderViewListAdapter.java:164)4 android.widget.ListView.dispatchDraw(ListView.java:3329)5 android.view.View.draw(View.java:16206)6 android.widget.AbsListView.draw(AbsListView.java:4166)7 android.view.View.updateDisplayListIfDirty(View.java:15200)8 android.view.View.draw(View.java:15973)//...49 android.view.ViewRootImpl.draw(ViewRootImpl.java:2615)50 android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2434)51 android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2067)52 android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1107)53 android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6013)//...60 android.os.Looper.loop(Looper.java:148)61 android.app.ActivityThread.main(ActivityThread.java:5417)62 java.lang.reflect.Method.invoke(Native Method)63 com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)64 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

这里重点关注第3~4行信息内容,根据第3行可知是ListView的dispatchDraw()方法异常

通过第3行知道是HeaderViewListAdapter.isEnabled方法抛出的
查询Android的ListView源码可知,

public void setAdapter(ListAdapter adapter) {    if (mAdapter != null && mDataSetObserver != null) {        mAdapter.unregisterDataSetObserver(mDataSetObserver);    }    resetList();    mRecycler.clear();    if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {        //注意这里:只有调用addHeaderView或者addFooterView时候才会构建HeaderViewListAdapter        mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, adapter);    } else {        mAdapter = adapter;    }    mOldSelectedPosition = INVALID_POSITION;    mOldSelectedRowId = INVALID_ROW_ID;    // AbsListView#setAdapter will update choice mode states.    super.setAdapter(adapter);    //…省略}

根据HeaderViewListAdapter类及堆栈信息可知出问题的肯定是ListView且有调用addHeaderView或者addFooterView的地方

补充说明:
1.Bugly的”跟踪数据”模块也能帮忙排除某些页面,大致定位出错的地方
2.注意有些页面是针对GridView或者RecyclerView自定义的add(Head/Foot)erView的地方,根据是ListView类报错也可以忽略

分析ListView.dispatchDraw方法

@Overrideprotected void dispatchDraw(Canvas canvas) {        final int count = getChildCount();        if (!mStackFromBottom) {            for (int i = 0; i < count; i++) {                                        if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader                                && (nextIndex >= headerCount)) && (isLastItem                                || adapter.isEnabled(nextIndex) && (footerDividers || !isFooter                                        && (nextIndex < footerLimit)))) {                            //...                        }                    }                }            }        }    }    // Draw the indicators (these should be drawn above the dividers) and children    super.dispatchDraw(canvas);}

可以看到,dispatchDraw是通过getChildCount去遍历

分析HeaderViewListAdapter.isEnable()方法

public boolean isEnabled(int position) {    // Header (negative positions will throw an IndexOutOfBoundsException)    int numHeaders = getHeadersCount();    if (position < numHeaders) {        return mHeaderViewInfos.get(position).isSelectable;    }    // Adapter    final int adjPosition = position - numHeaders;    int adapterCount = 0;    if (mAdapter != null) {        adapterCount = mAdapter.getCount();        if (adjPosition < adapterCount) {            return mAdapter.isEnabled(adjPosition);        }    }    // Footer (off-limits positions will throw an IndexOutOfBoundsException)    return mFooterViewInfos.get(adjPosition - adapterCount).isSelectable;}

HeaderViewListAdapter.isEnable的方法实现很简单,重点关注该方法使用的count=mAdapter.getCount()

结合上面两个方法的分析可以想到,isEnable()方法触发”java.lang.IndexOutOfBoundsException”只有两种可能:

1.调用remove(Header/Footer)View方法后没有及时触发更新adapter更新
2.数据源数据减少(不只有remove这一种可能,也有可能是赋值size更小的集合引用)后没有及时更新

结合案例分析

项目全局搜索addFooterView|addHeaderView,排除无关结果后如下:

1.“MenuFragment”中menuClsAdapter.modules有remove、clear或者集合重新赋值的地方均有显式调用notifyDataSetChanged方法通知ListView去更新
2.“AirTableFragment”中检查tableViewProcessor.mareaDBModelList在AirTableFragment中有remove、clear或者变更引用的地方
2.1 doDeleteArea:该方法中可能有问题,一开始以为是clear之后调用selectionArea方法中在notifyDataSetChanged之前有耗时操作,检查了下代码,排除该猜想
2.2 会不会是tableViewProcessor.mareaDBModelList在非AirTableFragment页面中被修改?检查发现loadLocalDatas()确实有传递tableViewProcessor对象引用且内部有变更mareaDBModelList数据。

PS:问题并非仅仅如此,具体原因与公司封装的base库相关,就不深入介绍了。

转载地址:http://kckpa.baihongyu.com/

你可能感兴趣的文章
5只蚂蚁走木棍问题
查看>>
iOS中3种正则表达式的使用与比较
查看>>
如果是繁體,Zzk搜不搜的到呢?
查看>>
系统设计 - 软件构件技术
查看>>
linux下配置SVN搭建 centos svn安装配置
查看>>
c#高级编程第七版 学习笔记 第一章 .NET体系结构
查看>>
黄聪:如何高效率存储微信中的 access_token
查看>>
HackerRank The Chosen One [预处理][gcd]
查看>>
封装获取连续数字的拼接
查看>>
gdb调试
查看>>
第一周 从C走进C++ 003 位运算
查看>>
k8s第一个实例创建redis集群服务
查看>>
Postgresql 查看建表语句 命令
查看>>
git操作
查看>>
技术文档翻译-------glove readme(1)
查看>>
编码格式
查看>>
Mybatis+mysql动态分页查询数据案例——配置映射文件(HouseDaoMapper.xml)
查看>>
poj 2828【线段树 单点更新】
查看>>
java构建二叉树和二叉树的遍历
查看>>
svn+jenkins+docker 发布 java 项目(maven)
查看>>