RecyclerView异常之:IllegalStateExce

2019-05-07  本文已影响0人  唐洪峰

一. assertNotInLayoutOrScroll引起的IllegalStateException

通过调用 notifyxxx 对适配器进行更新时,会调用到RecyclerView的assertNotInLayoutOrScroll,该方法可能会抛出IllegalStateException

异常抛出点:

void assertNotInLayoutOrScroll(String message) {
        if (this.isComputingLayout()) {
            if (message == null) {
                throw new IllegalStateException("Cannot call this method while RecyclerView is computing a layout or scrolling" + this.exceptionLabel());
            } else {
                throw new IllegalStateException(message);
            }
        } else {
            if (this.mDispatchScrollCounter > 0) {
                Log.w("RecyclerView", "Cannot call this method in a scroll callback. Scroll callbacks mightbe run during a measure & layout pass where you cannot change theRecyclerView data. Any method call that might change the structureof the RecyclerView or the adapter contents should be postponed tothe next frame.", new IllegalStateException("" + this.exceptionLabel()));
            }

        }
    }
问题:
1. Cannot call this method while RecyclerView is computing a layout or scrolling
   该问题直接原因是RecyclerView的isComputingLayout()为true导致。
   字面意思就是不要在RecyclerView正在layout的时候,去尝试更新适配器内容。

解决方法:

 public final void notifyDataSetChangedSafely() {
        if (mRecyclerView != null && mRecyclerView.isComputingLayout()) {
            mRecyclerView.post(new Runnable() {
                @Override
                public void run() {
                    notifyDataSetChanged();
                }
            });
        } else {
            notifyDataSetChanged();
        }
    }

即在调用notifyDataSetChanged等方法时,判断RecyclerView是否在ComputingLayout,如果是的话,在下一条消息中notifyDataSetChanged等方法。

2. Cannot call this method in a scroll callback
   该问题直接原因是RecyclerView的mDispatchScrollCounter>0导致。
   字面意思就是不要在RecyclerView执行Scroll动作的回调方法的时候去尝试直接更新适配器内容。

其值变化所在位置:

void dispatchOnScrolled(int hresult, int vresult) {
        ++this.mDispatchScrollCounter;
        int scrollX = this.getScrollX();
        int scrollY = this.getScrollY();
        this.onScrollChanged(scrollX, scrollY, scrollX, scrollY);
        this.onScrolled(hresult, vresult);
        if (this.mScrollListener != null) {
            this.mScrollListener.onScrolled(this, hresult, vresult);
        }

        if (this.mScrollListeners != null) {
            for(int i = this.mScrollListeners.size() - 1; i >= 0; --i) {
                ((RecyclerView.OnScrollListener)this.mScrollListeners.get(i)).onScrolled(this, hresult, vresult);
            }
        }

        --this.mDispatchScrollCounter;
    }

代码一目了然,即不要在RecyclerView回调onScrollChanged,onScrolled的时候去尝试直接更新适配器内容,
也不要在RecyclerView的OnScrollListener回调onScrolled的时候去尝试直接更新适配器内容。

比如我们通常会在RecyclerView滑动到底部的时候去加载更多数据来填充列表:

 recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(final RecyclerView recyclerView, int dx, int dy) {
                  if (isLastViewMoreView(recyclerView)) {
                      onLoadMore();
                  }
            }
        });

分两种情况:
a. 我们的onLoadMore的数据来源是网络,因为网络请求是异步返回,就不会出现问题。
b. 我们的onLoadMore的数据来源是本地,即请求可能是同步返回,这个时候如果直接更新适配器,那么这个更新动作就是在onScrolled方法的执行序列中进行了,这就会导致call this method in a scroll callback

解决方法:

recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(final RecyclerView recyclerView, int dx, int dy) {
                if (isLastViewMoreView(recyclerView)) {
                    new Handler().post(new Runnable() {
                        @Override
                        public void run() {
                            onLoadMore();
                        }
                    });
                }
            }
        });

即在调用onScrolled方法时,在下一条消息中去调用onLoadMore方法(更新适配器动作在onScrolled等方法的执行序列中的方法)

上一篇 下一篇

猜你喜欢

热点阅读