一. 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等方法的执行序列中的方法)