Android应用在bugly上偶现调用View.draw()时出现数据越界,根据bugly提供的解决方案以下,java
1.遍历数组/字符串等集合前,要判断遍历对象的长度; 2.操做数组/字符串等集合前,要检查角标是否在长度容许范围内; 3.ListView操做不当也会引发该异常,这种状况下通常是因为List渲染的时候,外面的数据源发生变化致使的。举例如ListView滚动时点击刷新将会报错,解决方法是ListView滚动时将刷新置为不可点击。或者改变数据源以前调用adapter的notifyDataSetInvalidated()方法将原数据源设置为无效。
但因为无项目源码相关的堆栈提示信息,此问题一直被搁置。android
出错堆栈信息以下canvas
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类报错也能够忽略app
@Override protected 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去遍历ide
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()oop
结合上面两个方法的分析能够想到,isEnable()方法触发”java.lang.IndexOutOfBoundsException”只有两种可能:
1.调用remove(Header/Footer)View方法后没有及时触发更新adapter更新
2.数据源数据减小(不仅有remove这一种可能,也有多是赋值size更小的集合引用)后没有及时更新spa
项目全局搜索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数据。code
PS:问题并不是仅仅如此,具体缘由与公司封装的base库相关,就不深刻介绍了。orm