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

今天在项目中遇到了这个问题,在此记录下,以防日后遗忘。顺带回忆下Handler机制。

一、先上解决方法

将更新数据的操作使用Handler去执行。

// 你需要确保这个Handler的Looper是主线程的Looper
// 也就是说,如果下列code块是在主线程中执行,请忽略。
// 如果不是,则需要你 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更新数据时,按照流程会执行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的刷新其实是通过消息机制实现的。通过对消息的
分发,从而进行不同处理。

从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[先进先出,后进后出]中去等待执行。而主线程的Looper则会在循环取此队列中的消息。一般情况下,这个流程是串行的。所以当使用Handle.post的时候,UI的更新消息会先被消化掉,等被post的消息被Looper从MessageQueue中取出的时候,【数据源更新】的操作才会被执行。

但是此时RecyclerView的绘制已经完成,mLayoutOrScrollCounter也已经被置为0,故而能够成功更新数据源

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容