背景
上周,之前的一个朋友发消息给我,RecyclerView的notifyDataSetChanged()方法没有用。心里一想,自己之前不就是这么用的么。但是好像脑海里有看到过也有人遇到了类似的问题,所以还是把迷雾拨开吧。
探索
自己一直有一个习惯,遇到问题不会先谷歌,会先点到源码里面看一下,再查官方文档,再谷歌。点进notifyDataSetChanged源码里面看了一下,就一行代码,但是能看到很多的东西。
public final void notifyDataSetChanged() {
mObservable.notifyChanged();
}
一行代码,看出了一个设计模式,观察者模式,从这里就可以猜到通过数据的改变,让观察者来改变数据。但是这里并不能看出为什么这个方法有的人会调用失效。查阅官方文档。
文档写的很清楚,这个方法就是刷新数据的。图中用红框框标记出来的。当前有两种方式可以使数据刷新,第一种是item的数据改变了,但是结构没改变(也就是item的位置没改变),第二种是发生了item的插入,删除等操作的时候。看完这里,这个方法不就是刷新数据的吗?但是怎么会有不起作用的现象呢?还是返回源码里面找吧。
既然notifyDataSetChanged()这个方法中就一行代码,并没有很多的信息,就往上看一层。setAdapter()
public void setAdapter(Adapter adapter) {
// bail out if layout is frozen
setLayoutFrozen(false);
setAdapterInternal(adapter, false, true);
requestLayout();
}
这个方法主要起作用的其实是中间的那个方法。setAdapterInternal(adapter, false, true);继续往下看
private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
boolean removeAndRecycleViews) {
//如果这个adapter存在,那么就把他上面的其他观察者都取消掉
if (mAdapter != null) {
mAdapter.unregisterAdapterDataObserver(mObserver);
mAdapter.onDetachedFromRecyclerView(this);
}
if (!compatibleWithPrevious || removeAndRecycleViews) {
// end all running animations
if (mItemAnimator != null) {
mItemAnimator.endAnimations();
}
// Since animations are ended, mLayout.children should be equal to
// recyclerView.children. This may not be true if item animator's end does not work as
// expected. (e.g. not release children instantly). It is safer to use mLayout's child
// count.
if (mLayout != null) {
mLayout.removeAndRecycleAllViews(mRecycler);
mLayout.removeAndRecycleScrapInt(mRecycler);
}
// we should clear it here before adapters are swapped to ensure correct callbacks.
mRecycler.clear();
}
mAdapterHelper.reset();
final Adapter oldAdapter = mAdapter;
mAdapter = adapter;
//然后再给这个dapter加上观察者
if (adapter != null) {
adapter.registerAdapterDataObserver(mObserver);
adapter.onAttachedToRecyclerView(this);
}
if (mLayout != null) {
mLayout.onAdapterChanged(oldAdapter, mAdapter);
}
mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
mState.mStructureChanged = true;
markKnownViewsInvalid();
}
代码中主要就是个adapter添加一个观察者,那么这个观察者也就是重点。看一下这个mObserver
//初始化了一个数据集的观察者对象。在数据集改变你的时候进行刷新
private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
private class RecyclerViewDataObserver extends AdapterDataObserver {
RecyclerViewDataObserver() {
}
//notifyDataSetChanged后调用
@Override
public void onChanged() {
assertNotInLayoutOrScroll(null);
if (mAdapter.hasStableIds()) {
// TODO Determine what actually changed.
// This is more important to implement now since this callback will disable all
// animations because we cannot rely on positions.
mState.mStructureChanged = true;
setDataSetChangedAfterLayout();
} else {
mState.mStructureChanged = true;
setDataSetChangedAfterLayout();
}
if (!mAdapterHelper.hasPendingUpdates()) {
requestLayout();
}
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
triggerUpdateProcessor();
}
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
triggerUpdateProcessor();
}
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
triggerUpdateProcessor();
}
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) {
triggerUpdateProcessor();
}
}
void triggerUpdateProcessor() {
if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}
}
在调用notifyDataSetChanged()完成后就会调用观察者对象的onChange()方法来刷新数据。流程就是这样,感觉设计的很巧妙,但是为啥就没效果了呢?然后让朋友把代码发给了我。
分析错误代码
大体一看,并没有看到什么错误。但是代码中真的存在错误,观察到这两行代码了吗?
list = data;
adapter.notifyDataSetChanged();
其实就是这里出现了问题。刚开始list是个空的对象,然后把空的集合数据塞给了adapter,于是有了他的一个观察者RecyclerViewDataObserver,他观察的是这个list对象所指的地址上的堆内存里的数据。但是现在通过网络访问拿到数据以后,直接用list指向了data,现在list指向的地址变了,数据也有了。但是RecyclerViewDataObserver观察的永远是list刚开始所指的那个地址。所以不会起作用。为了好理解,画个图:
oberserber永远观察的都是地址0x0001上的数据。现在list是有值了,但是地址变了。调用notifyDataSetChanged当前没有用了。
解决
list.addAll(data);
adapter.notifyDataSetChanged();
使用这种方式可以解决上面的问题,现在就把数据加到0x0001这个地址里了,调用刷新方法,搞定。
总结
虽然是一个小问题,但是能反应出很多问题, 一个小问题很可能会花大量的时间找答案。总结问题,成就将来。