有關List的throwIndexOutOfBoundsException的index 0, size 0 問題

Wechat :DSGtalk1989

throwIndexOutOfBoundsException各位應該見的比較多了,通常的問題都是處在ArrayList中本身的size只有x, 結果你取了x + N位的值,導致系統(tǒng)直接叫你滾。見得比較多的是我們沒有及時的處理刷新的問題,或者輪詢刪減的時候沒有處理好,再或者說你就是寫了個bug(這么說肯定對的。。)。
今天介紹的是開發(fā)過程中碰到比較詭異的技能盲點,聽我道來。

接手了之前革命先烈留下的一個項目,使用的是ListView,并且項目中多處使用到,千絲萬縷,短時間之內難以重構成RecyclerView,因此之中的header,footer坑就特別難纏。

經(jīng)過一輪修復,發(fā)現(xiàn)基本解決了header和footer的問題。但是bugly上依然有很多用戶出現(xiàn)如下的問題

java.lang.IndexOutOfBoundsException
Invalid index 0, size is 0
java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255)
不知道各位對bug fix這個過程是如何看待的,我個人認為解bug的過程與開發(fā)過程的收益比基本維持在3:1。
記得之前在一家公司,有個哥們專門申請希望可以轉職清理bug,但是領導好像沒有批。。

在bug fix的道理上,最坑的是沒有l(wèi)og,其次是so崩潰,再其次是有l(wèi)og無定位,上面的情況是第三種,前兩種更惡心的之后有空跟大家一起聊聊。

如果你直接希望能從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,如果你放棄了,那我們繼續(xù)。

ArrayList使用到最多的地方應該就是列表的data類,我們再看一下,出現(xiàn)問題的這一行:

Invalid index 0, size is 0

size已經(jīng)為空了,我們還去取它的第一位值。我想你肯定不會去這么寫,到底在什么情況下會出現(xiàn)這樣的問題
換一種說法,列表里什么都沒有了,你還想要去里面看看有什么。有這兩種可能:

  • 列表里什么都沒有設置,你就先去取了。
  • 列表清空了,你不知道。

我認為兩種假設的性質是一樣的,咱們基本都不會去做,但是不知道你們是否會在代碼中使用到如下的adapter調用方式

list.clear();
list.addAll(items);
adapter.notifyDataSetChanged();

乍一看沒什么問題,我們?yōu)榱吮3至斜碇袛?shù)據(jù)的新鮮度,基本在很多情況下都會去將整個列表清空再重新加載,然后去刷新列表,除非你有專門針對做過數(shù)據(jù)diff,從而來刷新指定item,否則大家都會這么去玩。
本身無可厚非,但是在極端情況下就會出現(xiàn)上面所說的情況。

一旦clear和addAll方法放在了異步線程中處理,那么就給了ListView有機可趁,讓他隨時有機會來訪問我們沒有上鎖的數(shù)據(jù),面上看起來,清空了數(shù)據(jù),再把新數(shù)據(jù)都加進去,然后通知界面更新。

實際上,當我們在進行clear操作了之后還沒有addAll之前,如果ListView調用了draw方法,那就會立馬崩潰。

所以我們一旦出現(xiàn)需要對listView的數(shù)據(jù)源進行操作,必須要放在UI線程中,或者拋出message交由handler放在handleMessage中進行處理,保證數(shù)據(jù)源處理和ListView的方法在同一個線程中同步執(zhí)行,就不會出現(xiàn)這種奇怪的數(shù)組越界錯誤了。


總結

ListView的數(shù)據(jù)改變也需要遵從同步的原則,盡量避免在異步線程中操作與UI線程有關的,或者會被UI線程調用的數(shù)據(jù)。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容