RecycleView的notifyDataSetChanged()之坑

? ? ? ?前幾天幫同事排查一個(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),問題就解決了!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容