java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling

今天在項目中遇到了這個問題,在此記錄下,以防日后遺忘。順帶回憶下Handler機制。

一、先上解決方法

將更新數據的操作使用Handler去執(zhí)行。

// 你需要確保這個Handler的Looper是主線程的Looper
// 也就是說,如果下列code塊是在主線程中執(zhí)行,請忽略。
// 如果不是,則需要你 Handler handler = new Handler(Looper.getMainLooper());
Handler handler = new Handler();
handler.post(new Runnable() {
    @Override
    public void run() {
        notifyDataSetChanged();
    }
});

二、為什么會出現這個CRASH?</br>

從CRASH的字面看,是因為在RecyclerView layout或scroll 過程中,調用notifyDataSetChanged方法導致的。
而在項目中的具體行為則是:左滑或右滑刪除Item的時候,調用了notifyDataSetChanged來刷新數據源。

看看具體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中,當RecyclerView的Adapter更新數據時,按照流程會執(zhí)行assertNotInLayoutOrScroll()這個方法。
assertNotInLayoutOrScroll()這個方法從方法名即可看出,用來檢測是否正在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這個變量或者說是標志位會在開始繪制的時候mLayoutOrScrollCounter++;在退出繪制的時候mLayoutOrScrollCounter--;

而從log中看,我的方法onItemClear()正好在繪制流程之中,也就是此時的mLayoutOrScrollCounter是非0的。故而在onItemClear()中直接調用notifyDataSetChanged()方法會CRASH。

三、為什么使用Handler處理就可以?</br>

了解Android消息機制的同學應該知道,Android中的UI的刷新其實是通過消息機制實現的。通過對消息的
分發(fā),從而進行不同處理。

從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方法將一個Runnable方法塊放入MessageQueue[先進先出,后進后出]中去等待執(zhí)行。而主線程的Looper則會在循環(huán)取此隊列中的消息。一般情況下,這個流程是串行的。所以當使用Handle.post的時候,UI的更新消息會先被消化掉,等被post的消息被Looper從MessageQueue中取出的時候,【數據源更新】的操作才會被執(zhí)行。

但是此時RecyclerView的繪制已經完成,mLayoutOrScrollCounter也已經被置為0,故而能夠成功更新數據源

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容