? ? ? ?前幾天幫同事排查一個(gè)調(diào)用notifyDataSetChanged()未刷新的bug。剛開始在網(wǎng)上查,幾乎都是說,如果數(shù)據(jù)源變了,adapter訪問的還是老的數(shù)據(jù)源,所以刷新無效。我看了下同事代碼確實(shí)是數(shù)據(jù)源地址變了。解決辦法是adapter里new一個(gè)List,然后每次addAll新的數(shù)據(jù),保證地址不變。
? ? ? ?然后我就去看源碼,首先我們從setAdapter()開始看:
/**
* Set a new adapter to provide child views on demand.
* <p>
* When adapter is changed, all existing views are recycled back to the pool. If the pool has
* only one adapter, it will be cleared.
*
* @param adapter The new adapter to set, or null to set no adapter.
* @see #swapAdapter(Adapter, boolean)
*/
public void setAdapter(Adapter adapter) {
// bail out if layout is frozen
setLayoutFrozen(false);
setAdapterInternal(adapter, false, true);
processDataSetCompletelyChanged(false);
requestLayout();
}
/**
* Replaces the current adapter with the new one and triggers listeners.
* @param adapter The new adapter
* @param compatibleWithPrevious If true, the new adapter is using the same View Holders and
* item types with the current adapter (helps us avoid cache
* invalidation).
* @param removeAndRecycleViews If true, we'll remove and recycle all existing views. If
* compatibleWithPrevious is false, this parameter is ignored.
*/
private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
boolean removeAndRecycleViews) {
//注意這個(gè)mObserver,類名是RecyclerViewDataObserver,即數(shù)據(jù)源變化的觀察者/訂閱者,在這里先給原來的adapter注銷了這個(gè)觀察者,再給新的adapter注冊了這個(gè)觀察者
if (mAdapter != null) {
mAdapter.unregisterAdapterDataObserver(mObserver);
mAdapter.onDetachedFromRecyclerView(this);
}
if (!compatibleWithPrevious || removeAndRecycleViews) {
removeAndRecycleViews();
}
mAdapterHelper.reset();
final Adapter oldAdapter = mAdapter;
mAdapter = adapter;
if (adapter != null) {
//追蹤這行代碼內(nèi)部實(shí)現(xiàn)
adapter.registerAdapterDataObserver(mObserver);
adapter.onAttachedToRecyclerView(this);
}
if (mLayout != null) {
mLayout.onAdapterChanged(oldAdapter, mAdapter);
}
mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
mState.mStructureChanged = true;
}
public void registerAdapterDataObserver(@NonNull AdapterDataObserver observer) {
//mObservable出現(xiàn)了,這個(gè)就是被觀察/被訂閱的主題,類名是AdapterDataObservable,被定義在RecycleView.Adapter類里,
//這個(gè)對象在**運(yùn)行時(shí)**就已經(jīng)分配好了內(nèi)存空間,使用final 修飾。
mObservable.registerObserver(observer);
}
//當(dāng)我們調(diào)用notifyDataSetChanged()方法時(shí),mObservable將會去通知所有的觀察者數(shù)據(jù)變化了
public final void notifyDataSetChanged() {
//追蹤這行代碼內(nèi)部實(shí)現(xiàn)
mObservable.notifyChanged();
}
public void notifyChanged() {
// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
//繼續(xù)追蹤
mObservers.get(i).onChanged();
}
}
//可以看到這里就是對數(shù)據(jù)源變化做出的邏輯處理
@Override
public void onChanged() {
assertNotInLayoutOrScroll(null);
mState.mStructureChanged = true;
processDataSetCompletelyChanged(true);
if (!mAdapterHelper.hasPendingUpdates()) {
requestLayout();
}
}
? ? ? ?我把這段源碼反復(fù)看了好幾遍,實(shí)在是沒想通adapter內(nèi)部沒有任何地方獲取了List對象的地址啊?我們在繼承Adapter的時(shí)候重寫的那幾個(gè)方法,也根本沒把List地址拋給Adapter,數(shù)據(jù)渲染都是Adapter通過onBindViewHolder()回調(diào)讓我們自己獲取數(shù)據(jù)渲染的。當(dāng)List數(shù)據(jù)有變動(dòng)的時(shí)候,我們調(diào)notifyDataSetChanged(),就是走我們剛剛看過的這幾段代碼的邏輯。但是大多數(shù)人都說保證List地址不變動(dòng)就可解決應(yīng)該是已經(jīng)實(shí)踐可行的方式了,只是我不懂這其中的緣由,如果有了解的朋友,麻煩在下方留言告知我,十分感謝!
? ? ? ?說一下我的解決辦法吧。我們看onChanged()的實(shí)現(xiàn)里,有一段這樣的代碼:
if (!mAdapterHelper.hasPendingUpdates()) {
requestLayout();
}
我懷疑是因?yàn)闆]有走進(jìn)這個(gè)if邏輯里,所以沒有執(zhí)行 requestLayout();
來刷新布局。于是,我追蹤查看mAdapterHelper.hasPendingUpdates()的實(shí)現(xiàn):
boolean hasPendingUpdates() {
return mPendingUpdates.size() > 0;
}
繼續(xù)查看mPendingUpdates這個(gè)變量在何時(shí)才會改變size,在這里,我們就Ctrl+F直接搜索mPendingUpdates.add,查看:
/**
* @return True if updates should be processed.
*/
boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) {
if (itemCount < 1) {
return false;
}
mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload));
mExistingUpdateTypes |= UpdateOp.UPDATE;
return mPendingUpdates.size() == 1;
}
/**
* @return True if updates should be processed.
*/
boolean onItemRangeInserted(int positionStart, int itemCount) {
if (itemCount < 1) {
return false;
}
mPendingUpdates.add(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount, null));
mExistingUpdateTypes |= UpdateOp.ADD;
return mPendingUpdates.size() == 1;
}
/**
* @return True if updates should be processed.
*/
boolean onItemRangeRemoved(int positionStart, int itemCount) {
if (itemCount < 1) {
return false;
}
mPendingUpdates.add(obtainUpdateOp(UpdateOp.REMOVE, positionStart, itemCount, null));
mExistingUpdateTypes |= UpdateOp.REMOVE;
return mPendingUpdates.size() == 1;
}
/**
* @return True if updates should be processed.
*/
boolean onItemRangeMoved(int from, int to, int itemCount) {
if (from == to) {
return false; // no-op
}
if (itemCount != 1) {
throw new IllegalArgumentException("Moving more than 1 item is not supported yet");
}
mPendingUpdates.add(obtainUpdateOp(UpdateOp.MOVE, from, to, null));
mExistingUpdateTypes |= UpdateOp.MOVE;
return mPendingUpdates.size() == 1;
}
只有在這4個(gè)方法里才會進(jìn)行add操作。隨便選中一個(gè),查看這些方法的調(diào)用之處,我就不貼代碼了,在RecyclerViewDataObserver類里調(diào)用了,繼續(xù)查看誰調(diào)用了RecyclerViewDataObserver的這幾個(gè)方法。反正照這個(gè)思路一直找下去,最后你就會發(fā)現(xiàn)當(dāng)我們調(diào)用adapter的notifyItemChanged()/notifyItemRangeChanged()/notifyItemInserted()/notifyItemMoved()等等這些方法的時(shí)候,才會改變mPendingUpdates的size,從而在走onChange()的時(shí)候才會刷新布局。
? ? ? ?DuangDuangDuang!!!解決辦法來了,在這種情況下,調(diào)用adapter.notifyItemChanged(int position, Object payload) 第二個(gè)參數(shù)為null或者"full"時(shí)是刷新全部,否則就是局部刷新,所以參數(shù)可以傳(-1,null),問題就解決了!