- 寫在前面
最近忙着趕項目進度,都不知道這次博客寫點啥好了,前兩天碰巧遇到一個奇怪的bug,項目中未發現與異常相關的類,於是去百度、谷歌搜索,發現這是一個早就可能被寫爛吐槽的RecyclerView的bug.
- 透過現象看本質
不知道你們遇見沒有,在RecyclerView被推的如火如荼的時候,你喜歡它,你默默用它,青睞於它的健壯性。你覺得,這玩意兒都出來這么久了,一定沒問題。然而,在某一次快速滑動中,Boom,崩潰了!
先來看下logcat打的崩潰日志:
1 java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 6(offset:6). 2 at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:3300) 3 at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:3258) 4 at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:1803) 5 at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1302) 6 at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1265) 7 at android.support.v7.widget.LinearLayoutManager.scrollBy(LinearLayoutManager.java:1093) 8 at android.support.v7.widget.LinearLayoutManager.scrollVerticallyBy(LinearLayoutManager.java:956) 9 at android.support.v7.widget.RecyclerView$ViewFlinger.run(RecyclerView.java:2715) 10 at android.view.Choreographer$CallbackRecord.run(Choreographer.java:725) 11 at android.view.Choreographer.doCallbacks(Choreographer.java:555) 12 at android.view.Choreographer.doFrame(Choreographer.java:524) 13 at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:711) 14 at android.os.Handler.handleCallback(Handler.java:615) 15 at android.os.Handler.dispatchMessage(Handler.java:92) 16 at android.os.Looper.loop(Looper.java:137) 17 at android.app.ActivityThread.main(ActivityThread.java:4921) 18 at java.lang.reflect.Method.invokeNative(Native Method) 19 at java.lang.reflect.Method.invoke(Method.java:511) 20 at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1027) 21 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:794) 22 at dalvik.system.NativeStart.main(Native Method)
數組越界?這日志看上去根本就跟我們代碼無關呀。多番Google發現,這貌似是Google程序員的鍋?內部bug?這TM官方的問題,關你何事?要不不用RecyclerView了吧?
你是一個優秀的程序猿,不應該總是逃避問題,而應該思考如何去解決它。不過這說明了一個問題,人非聖賢孰能無過,連Google程序員那么牛逼的存在都會出問題。
崩潰的原因比較清楚,如果綁定的集合List中的數據和RecycerView的數據不一致的時候,調用更新方法的時候會復現。
-
臨時解決辦法
有人這么說,造成崩潰的原因極有可能是當clear了之后,迅速上滑,但由於新數據還沒來,導致RecyclerView需要更新加載下面的Item的時候,找不到數據源,導致了崩潰的發生。
所以,既然如此,一定可以通過讓Clear的時候,禁止RecyclerView的滑動來解決它。代碼如下:
1 private boolean mIsRefreshing=false; 2 mRecyclerView.setOnTouchListener( 3 new View.OnTouchListener() { 4 @Override 5 public boolean onTouch(View v, MotionEvent event) { 6 if (mIsRefreshing) { 7 return true; 8 } else { 9 return false; 10 } 11 } 12 } 13 ); 14 //當刷新時設置 15 //mIsRefreshing=true; 16 //刷新完畢后還原為false 17 //mIsRefreshing=false;
-
別人怎么說
人,想法,總是千奇百怪。
造成崩潰的原因其實很明顯,如果你更新集合List后,調用RVAdapter的notifyXXXX方法時,adapter的更新預期接口和實際集合更新結果不同,就會出現這個異常!不信你可以隨便模擬這個情況的發生。
所以有人就得到了這樣的結論:
1、RVAdapter的notifyDataSetChanged方法執行后,在一定時間內,如果你更新了你的集合(無論是否在主線程更新集合),那么這個更新會實時反應到控件上,也就是說你的控件顯示也會更新。
2、調用諸如notifyItemRangeInserted這樣的方法之前,考慮清楚你的集合到底更新成什么樣了!要注意參考結論1,結論1會影響你的判斷。
-
靠譜的解決辦法
顯然,上面的方法都不太好用,繼續研究發現,直接采用下面的方法可以很好的解決。
經過多番研究發現,直接像下面這樣,可以完美解決我們的問題。
保證Adapter內的list和獲取到的數據list不是同一個list就好.
Class MyAdapter extends RecyclerView.Adapter{ private List<Object>mList; ... public void notifySetListDataChanged(List<Object>list){ this.mList = list; notifySetDataChanged(); } }
每次數據更新(只要有變動都認為是更新)都調用
adapter.notifySetListDataChanged(list);這里的list是變動更新后的數據list;
-
寫在最后
細心和耐心在編程之路顯得尤為重要,只有抱着對bug0容忍的態度和決心,我們才能越過高山,發現更高更遠的天空