throwIndexOutOfBoundsException各位应该见的比较多了,通常的问题都是处在ArrayList中本身的size只有x, 结果你取了x + N位的值,导致系统直接叫你滚。见得比较多的是我们没有及时的处理刷新的问题,或者轮询删减的时候没有处理好,再或者说你就是写了个bug(这么说肯定对的。。)。
今天介绍的是开发过程中碰到比较诡异的技能盲点,听我道来。
接手了之前革命先烈留下的一个项目,使用的是ListView,并且项目中多处使用到,千丝万缕,短时间之内难以重构成RecyclerView,因此之中的header,footer坑就特别难缠。
经过一轮修复,发现基本解决了header和footer的问题。但是bugly上依然有很多用户出现如下的问题
java.lang.IndexOutOfBoundsException
Invalid index 0, size is 0
java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255)
不知道各位对bug fix这个过程是如何看待的,我个人认为解bug的过程与开发过程的收益比基本维持在3:1。
记得之前在一家公司,有个哥们专门申请希望可以转职清理bug,但是领导好像没有批。。
在bug fix的道理上,最坑的是没有log,其次是so崩溃,再其次是有log无定位,上面的情况是第三种,前两种更恶心的之后有空跟大家一起聊聊。
如果你直接希望能从log里面找出什么端倪,我可以贴出来你试一试。
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:3430)
5 android.view.View.draw(View.java:15627)
6 android.widget.AbsListView.draw(AbsListView.java:4644)
7 android.view.View.updateDisplayListIfDirty(View.java:14504)
8 android.view.View.getDisplayList(View.java:14533)
9 android.view.View.draw(View.java:15324)
10 android.view.ViewGroup.drawChild(ViewGroup.java:3536)
11 android.view.ViewGroup.dispatchDraw(ViewGroup.java:3329)
12 android.view.View.updateDisplayListIfDirty(View.java:14496)
13 android.view.View.getDisplayList(View.java:14533)
14 android.view.View.draw(View.java:15324)
15 android.view.ViewGroup.drawChild(ViewGroup.java:3536)
16 android.view.ViewGroup.dispatchDraw(ViewGroup.java:3329)
17 android.view.View.updateDisplayListIfDirty(View.java:14496)
18 android.view.View.getDisplayList(View.java:14533)
19 android.view.View.draw(View.java:15324)
20 android.view.ViewGroup.drawChild(ViewGroup.java:3536)
21 android.view.ViewGroup.dispatchDraw(ViewGroup.java:3329)
22 android.view.View.draw(View.java:15627)
23 android.support.v4.view.ViewPager.draw(ViewPager.java:2341)
24 android.view.View.updateDisplayListIfDirty(View.java:14504)
25 android.view.View.getDisplayList(View.java:14533)
26 android.view.View.draw(View.java:15324)
27 android.view.ViewGroup.drawChild(ViewGroup.java:3536)
28 android.view.ViewGroup.dispatchDraw(ViewGroup.java:3329)
29 android.view.View.updateDisplayListIfDirty(View.java:14496)
30 android.view.View.getDisplayList(View.java:14533)
31 android.view.View.draw(View.java:15324)
32 android.view.ViewGroup.drawChild(ViewGroup.java:3536)
33 android.view.ViewGroup.dispatchDraw(ViewGroup.java:3329)
34 android.view.View.updateDisplayListIfDirty(View.java:14496)
35 android.view.View.getDisplayList(View.java:14533)
36 android.view.View.draw(View.java:15324)
37 android.view.ViewGroup.drawChild(ViewGroup.java:3536)
38 android.view.ViewGroup.dispatchDraw(ViewGroup.java:3329)
39 android.view.View.updateDisplayListIfDirty(View.java:14496)
40 android.view.View.getDisplayList(View.java:14533)
41 android.view.View.draw(View.java:15324)
42 android.view.ViewGroup.drawChild(ViewGroup.java:3536)
43 android.view.ViewGroup.dispatchDraw(ViewGroup.java:3329)
44 android.view.View.updateDisplayListIfDirty(View.java:14496)
45 android.view.View.getDisplayList(View.java:14533)
46 android.view.View.draw(View.java:15324)
47 android.view.ViewGroup.drawChild(ViewGroup.java:3536)
48 android.view.ViewGroup.dispatchDraw(ViewGroup.java:3329)
49 android.view.View.updateDisplayListIfDirty(View.java:14496)
50 android.view.View.getDisplayList(View.java:14533)
51 android.view.View.draw(View.java:15324)
52 android.view.ViewGroup.drawChild(ViewGroup.java:3536)
53 android.view.ViewGroup.dispatchDraw(ViewGroup.java:3329)
54 android.view.View.updateDisplayListIfDirty(View.java:14496)
55 android.view.View.getDisplayList(View.java:14533)
56 android.view.View.draw(View.java:15324)
57 android.view.ViewGroup.drawChild(ViewGroup.java:3536)
58 android.view.ViewGroup.dispatchDraw(ViewGroup.java:3329)
59 com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchDraw(PhoneWindow.java:2845)
60 android.view.View.draw(View.java:15627)
61 android.widget.FrameLayout.draw(FrameLayout.java:658)
62 com.android.internal.policy.impl.PhoneWindow$DecorView.draw(PhoneWindow.java:2820)
63 android.view.View.updateDisplayListIfDirty(View.java:14504)
64 android.view.View.getDisplayList(View.java:14533)
65 android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:279)
66 android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:285)
67 android.view.ThreadedRenderer.draw(ThreadedRenderer.java:335)
68 android.view.ViewRootImpl.draw(ViewRootImpl.java:2987)
69 android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2801)
70 android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2412)
71 android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1320)
72 android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6733)
73 android.view.Choreographer$CallbackRecord.run(Choreographer.java:800)
74 android.view.Choreographer.doCallbacks(Choreographer.java:603)
75 android.view.Choreographer.doFrame(Choreographer.java:572)
76 android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:786)
77 android.os.Handler.handleCallback(Handler.java:815)
78 android.os.Handler.dispatchMessage(Handler.java:104)
79 android.os.Looper.loop(Looper.java:194)
80 android.app.ActivityThread.main(ActivityThread.java:5869)
81 java.lang.reflect.Method.invoke(Native Method)
82 java.lang.reflect.Method.invoke(Method.java:372)
83 com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1019)
84 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:814)
OK,如果你放弃了,那我们继续。
ArrayList使用到最多的地方应该就是列表的data类,我们再看一下,出现问题的这一行:
Invalid index 0, size is 0
size已经为空了,我们还去取它的第一位值。我想你肯定不会去这么写,到底在什么情况下会出现这样的问题
换一种说法,列表里什么都没有了,你还想要去里面看看有什么。有这两种可能:
- 列表里什么都没有设置,你就先去取了。
- 列表清空了,你不知道。
我认为两种假设的性质是一样的,咱们基本都不会去做,但是不知道你们是否会在代码中使用到如下的adapter调用方式
list.clear();
list.addAll(items);
adapter.notifyDataSetChanged();
乍一看没什么问题,我们为了保持列表中数据的新鲜度,基本在很多情况下都会去将整个列表清空再重新加载,然后去刷新列表,除非你有专门针对做过数据diff
,从而来刷新指定item,否则大家都会这么去玩。
本身无可厚非,但是在极端情况下就会出现上面所说的情况。
一旦clear和addAll方法放在了异步线程中处理,那么就给了ListView有机可趁,让他随时有机会来访问我们没有上锁的数据,面上看起来,清空了数据,再把新数据都加进去,然后通知界面更新。
实际上,当我们在进行clear操作了之后还没有addAll之前,如果ListView调用了draw方法,那就会立马崩溃。
所以我们一旦出现需要对listView的数据源进行操作,必须要放在UI线程中,或者抛出message交由handler放在handleMessage中进行处理,保证数据源处理和ListView的方法在同一个线程中同步执行,就不会出现这种奇怪的数组越界错误了。
总结
ListView的数据改变也需要遵从同步的原则,尽量避免在异步线程中操作与UI线程有关的,或者会被UI线程调用的数据。
网友评论