今天遇到一个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(),
那么就是新建立消息放入消息队列末尾,这样两个刷新不在同一个消息,就完美避开了这个问题。
又可以愉快的玩耍了