今天在項(xiàng)目中遇到了這個(gè)問題方援,在此記錄下,以防日后遺忘兵睛。順帶回憶下Handler機(jī)制肯骇。
一、先上解決方法
將更新數(shù)據(jù)的操作使用Handler去執(zhí)行祖很。
// 你需要確保這個(gè)Handler的Looper是主線程的Looper
// 也就是說,如果下列code塊是在主線程中執(zhí)行漾脂,請(qǐng)忽略假颇。
// 如果不是,則需要你 Handler handler = new Handler(Looper.getMainLooper());
Handler handler = new Handler();
handler.post(new Runnable() {
@Override
public void run() {
notifyDataSetChanged();
}
});
二骨稿、為什么會(huì)出現(xiàn)這個(gè)CRASH笨鸡?</br>
從CRASH的字面看姜钳,是因?yàn)樵赗ecyclerView layout或scroll 過程中,調(diào)用notifyDataSetChanged方法導(dǎo)致的形耗。
而在項(xiàng)目中的具體行為則是:左滑或右滑刪除Item的時(shí)候哥桥,調(diào)用了notifyDataSetChanged來刷新數(shù)據(jù)源。
看看具體CRASH
06-27 16:49:50.463 E/AndroidRuntime(20889): FATAL EXCEPTION: main
06-27 16:49:50.463 E/AndroidRuntime(20889): Process: a, PID: 20889
06-27 16:49:50.463 E/AndroidRuntime(20889): java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling
06-27 16:49:50.463 E/AndroidRuntime(20889): at android.support.v7.widget.RecyclerView.assertNotInLayoutOrScroll(RecyclerView.java:2349)
06-27 16:49:50.463 E/AndroidRuntime(20889): at android.support.v7.widget.RecyclerView$RecyclerViewDataObserver.onChanged(RecyclerView.java:4551)
06-27 16:49:50.463 E/AndroidRuntime(20889): at android.support.v7.widget.RecyclerView$AdapterDataObservable.notifyChanged(RecyclerView.java:10366)
06-27 16:49:50.463 E/AndroidRuntime(20889): at android.support.v7.widget.RecyclerView$Adapter.notifyDataSetChanged(RecyclerView.java:6044)
06-27 16:49:50.463 E/AndroidRuntime(20889): at a.gui.adapters.TrackAdapters.QueueTrackAdapter$TrackViewHolder.onItemClear(QueueTrackAdapter.java:522)
06-27 16:49:50.463 E/AndroidRuntime(20889): at a.gui.adapters.swipedrag.SimpleItemTouchHelperCallback.clearView(SimpleItemTouchHelperCallback.java:124)
06-27 16:49:50.463 E/AndroidRuntime(20889): at android.support.v7.widget.helper.ItemTouchHelper.onChildViewDetachedFromWindow(ItemTouchHelper.java:876)
06-27 16:49:50.463 E/AndroidRuntime(20889): at android.support.v7.widget.RecyclerView.dispatchChildDetached(RecyclerView.java:6234)
06-27 16:49:50.463 E/AndroidRuntime(20889): at android.support.v7.widget.RecyclerView.access$1200(RecyclerView.java:151)
06-27 16:49:50.463 E/AndroidRuntime(20889): at android.support.v7.widget.RecyclerView$5.removeViewAt(RecyclerView.java:651)
06-27 16:49:50.463 E/AndroidRuntime(20889): at android.support.v7.widget.ChildHelper.removeViewAt(ChildHelper.java:168)
06-27 16:49:50.463 E/AndroidRuntime(20889): at android.support.v7.widget.RecyclerView$LayoutManager.removeViewAt(RecyclerView.java:7092)
06-27 16:49:50.463 E/AndroidRuntime(20889): at android.support.v7.widget.RecyclerView$LayoutManager.scrapOrRecycleView(RecyclerView.java:7638)
06-27 16:49:50.463 E/AndroidRuntime(20889): at android.support.v7.widget.RecyclerView$LayoutManager.detachAndScrapAttachedViews(RecyclerView.java:7624)
06-27 16:49:50.463 E/AndroidRuntime(20889): at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:546)
06-27 16:49:50.463 E/AndroidRuntime(20889): at a.gui.activities.QueueActivity$WrapContentLinearLayoutManager.onLayoutChildren(QueueActivity.java:359)
06-27 16:49:50.463 E/AndroidRuntime(20889): at android.support.v7.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:3260)
06-27 16:49:50.463 E/AndroidRuntime(20889): at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:3069)
06-27 16:49:50.463 E/AndroidRuntime(20889): at android.support.v7.widget.RecyclerView.consumePendingUpdateOperations(RecyclerView.java:1478)
06-27 16:49:50.463 E/AndroidRuntime(20889): at android.support.v7.widget.RecyclerView.scrollByInternal(RecyclerView.java:1542)
06-27 16:49:50.463 E/AndroidRuntime(20889): at android.support.v7.widget.RecyclerView.onTouchEvent(RecyclerView.java:2649)
......
......
06-27 16:49:50.464 E/AndroidRuntime(20889): at android.os.Handler.handleCallback(Handler.java:836)
06-27 16:49:50.464 E/AndroidRuntime(20889): at android.os.Handler.dispatchMessage(Handler.java:103)
06-27 16:49:50.464 E/AndroidRuntime(20889): at android.os.Looper.loop(Looper.java:203)
06-27 16:49:50.464 E/AndroidRuntime(20889): at android.app.ActivityThread.main(ActivityThread.java:6347)
06-27 16:49:50.464 E/AndroidRuntime(20889): at java.lang.reflect.Method.invoke(Native Method)
06-27 16:49:50.464 E/AndroidRuntime(20889): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1063)
06-27 16:49:50.464 E/AndroidRuntime(20889): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:924)
在RecyclerView中激涤,當(dāng)RecyclerView的Adapter更新數(shù)據(jù)時(shí)拟糕,按照流程會(huì)執(zhí)行assertNotInLayoutOrScroll()這個(gè)方法。
assertNotInLayoutOrScroll()這個(gè)方法從方法名即可看出倦踢,用來檢測(cè)是否正在layout或者scroll送滞。
void assertInLayoutOrScroll(String message) {
if (!isComputingLayout()) {
if (message == null) {
throw new IllegalStateException("Cannot call this method unless RecyclerView is "
+ "computing a layout or scrolling");
}
throw new IllegalStateException(message);
}
}
在其中,是通過isComputingLayout()判斷
public boolean isComputingLayout() {
return mLayoutOrScrollCounter > 0;
}
而mLayoutOrScrollCounter這個(gè)變量或者說是標(biāo)志位會(huì)在開始繪制的時(shí)候mLayoutOrScrollCounter++;在退出繪制的時(shí)候mLayoutOrScrollCounter--;
而從log中看辱挥,我的方法onItemClear()正好在繪制流程之中犁嗅,也就是此時(shí)的mLayoutOrScrollCounter是非0的。故而在onItemClear()中直接調(diào)用notifyDataSetChanged()方法會(huì)CRASH晤碘。
三褂微、為什么使用Handler處理就可以?</br>
了解Android消息機(jī)制的同學(xué)應(yīng)該知道园爷,Android中的UI的刷新其實(shí)是通過消息機(jī)制實(shí)現(xiàn)的蕊梧。通過對(duì)消息的
分發(fā),從而進(jìn)行不同處理腮介。
從log中最后幾行也可以看出肥矢。
......
06-27 16:49:50.464 E/AndroidRuntime(20889): at android.os.Handler.handleCallback(Handler.java:836)
06-27 16:49:50.464 E/AndroidRuntime(20889): at android.os.Handler.dispatchMessage(Handler.java:103)
06-27 16:49:50.464 E/AndroidRuntime(20889): at android.os.Looper.loop(Looper.java:203)
......
通過Handler.post方法將一個(gè)Runnable方法塊放入MessageQueue[先進(jìn)先出,后進(jìn)后出]中去等待執(zhí)行叠洗。而主線程的Looper則會(huì)在循環(huán)取此隊(duì)列中的消息甘改。一般情況下,這個(gè)流程是串行的灭抑。所以當(dāng)使用Handle.post的時(shí)候十艾,UI的更新消息會(huì)先被消化掉,等被post的消息被Looper從MessageQueue中取出的時(shí)候腾节,【數(shù)據(jù)源更新】的操作才會(huì)被執(zhí)行忘嫉。
但是此時(shí)RecyclerView的繪制已經(jīng)完成,mLayoutOrScrollCounter也已經(jīng)被置為0案腺,故而能夠成功更新數(shù)據(jù)源