解Caused by java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing

今天遇到一个crash,贴下报错日志:

Caused by java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling android.support.v7.widget.GrindrPagedRecyclerView{b15cd5 VFED..... .F.....D 0,0-1080,1530 #7f1102ac app:id/fragment_cascade_list}, adapter:com.nnapp.android.adapter.CascadeAdapter@74c3b7e, layout:android.support.v7.widget.GridLayoutManager@a27c5a, context:com.nnapp.android.activity.HomeActivity@46fff24
       at android.support.v7.widget.RecyclerView.assertNotInLayoutOrScroll(RecyclerView.java:2769)
       at android.support.v7.widget.RecyclerView$RecyclerViewDataObserver.onChanged(RecyclerView.java:5177)
       at android.support.v7.widget.RecyclerView$AdapterDataObservable.notifyChanged(RecyclerView.java:11785)
       at android.support.v7.widget.RecyclerView$Adapter.notifyDataSetChanged(RecyclerView.java:6961)
       at com.nnapp.android.adapter.CascadeAdapter.onVideoRewardFooterExpiredEvent(CascadeAdapter.java:358)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.squareup.otto.EventHandler.handleEvent(EventHandler.java:89)
       at com.squareup.otto.Bus.dispatch(Bus.java:385)
       at com.squareup.otto.Bus.dispatchQueuedEvents(Bus.java:368)
       at com.squareup.otto.Bus.post(Bus.java:337)
       at com.nnapp.android.view.VideoRewardMoreGuysFooterViewHolder.onBind(VideoRewardMoreGuysFooterViewHolder.java:27149)
       at com.nnapp.android.adapter.RealmAdapter.onBindViewHolder(RealmAdapter.java:63)
       at com.nnapp.android.adapter.RealmAdapter.onBindViewHolder(RealmAdapter.java:30)
       at android.support.v7.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:6673)
       at android.support.v7.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:6714)
       at android.support.v7.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:5647)
       at android.support.v7.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5913)
       at android.support.v7.widget.GapWorker.prefetchPositionWithDeadline(GapWorker.java:285)
       at android.support.v7.widget.GapWorker.flushTaskWithDeadline(GapWorker.java:342)
       at android.support.v7.widget.GapWorker.flushTasksWithDeadline(GapWorker.java:358)
       at android.support.v7.widget.GapWorker.prefetch(GapWorker.java:365)
       at android.support.v7.widget.GapWorker.run(GapWorker.java:396)
       at android.os.Handler.handleCallback(Handler.java:751)
       at android.os.Handler.dispatchMessage(Handler.java:95)
       at android.os.Looper.loop(Looper.java:154)
       at android.app.ActivityThread.main(ActivityThread.java:6692)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1468)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1358)

看日志说的很明白了,就是在一个ViewHolder.onBind()里面用通过bus发消息去通知刷新列表

notifyDataSetChanged(),这个时候刚好列表在滚动或者在layout(),那么就会报这个错。

public class RecyclerView extends ViewGroup implements ScrollingView,NestedScrollingChild2{ 
  ...  
    
      /**
     * Checks if RecyclerView is in the middle of a layout or scroll and throws an
     * {@link IllegalStateException} if it <b>is not</b>.
     *
     * @param message The message for the exception. Can be null.
     * @see #assertNotInLayoutOrScroll(String)
     */
    void assertInLayoutOrScroll(String message) {
        if (!isComputingLayout()) {
            if (message == null) {
                throw new IllegalStateException("Cannot call this method unless RecyclerView is "
                        + "computing a layout or scrolling" + exceptionLabel());
            }
            throw new IllegalStateException(message + exceptionLabel());

        }
    }

    public boolean isComputingLayout() {
       return mLayoutOrScrollCounter > 0;
    }

    public abstract static class LayoutManager {
               /**
         * Checks if RecyclerView is in the middle of a layout or scroll and throws an
         * {@link IllegalStateException} if it <b>is not</b>.
         *
         * @param message The message for the exception. Can be null.
         * @see #assertNotInLayoutOrScroll(String)
         */
        public void assertInLayoutOrScroll(String message) {
            if (mRecyclerView != null) {
                mRecyclerView.assertInLayoutOrScroll(message);
            }
        }
    }
  ...  
} 

通过上面代码片段可以知道通过assertInLayoutOrScroll()可以知道RecyclerView是否正在滚动或者layout,但是这个方法

不是public的且还有一个参数,就算这个方法是public的在notifyDataSetChanged()之前加上判断也不是好的方案,

虽然可以解决crash,但是想做的刷新动作就终止了。

这里有一种比较好的解决方案,就是利用Handler, 在需要刷新的地方加上比如

new Handler().post(new Runnable() {
            @Override
            public void run() {
                // 刷新操作
                notifyDataSetChanged();
            }
        });

这样就能避免crash且正常刷新。

原理:

主线程刷新UI是通过消息队列,当列表正在滚动或者layout时调用notifyDataSetChanged(),

那么notifyDataSetChanged()里面的代码是和正在滚动或者layout同一消息里面的,如果加上Handler.post(),

那么就是新建立消息放入消息队列末尾,这样两个刷新不在同一个消息,就完美避开了这个问题。

大笑又可以愉快的玩耍了


猜你喜欢

转载自blog.csdn.net/msn465780/article/details/80375817