ListView中的觀察者模式

*本篇文章已授權微信公衆號 guolin_blog (郭霖)獨家發佈

雖然現在RecyclerView很好用,也在逐漸替代ListView。Github 上也很多實用的封裝。但是這些都不阻礙我們學習ListView優秀的源碼設計。

進入正題,我用的是Api-23的源碼。接下來就從源碼的角度帶你學習ListView中的觀察者模式

當我們開啓異步線程,向服務端拉取數據後,數據源已經更新了,此時想要更新ListView的視圖以顯示新的數據。
ListView使用了Adapter模式,很簡單隻需一行代碼就能完成ListView的更新。

mAdapter.notifyDataSetChanged();

那麼這裏引出一個問題,
更新ListView的工作,是Adapter完成的還是ListView自身內部完成的?可以先猜想一下再往下看。
因爲我之前已經學習過自定義控件,所以我看源碼之前猜想是ListView完成的。慣性使然,我想到他可能是調用了onLayout(),onDraw()等方法呀,去重新佈局,繪製

那接下來就解開疑惑吧。

先找到源頭,從ListView綁定Adapter那裏開始。

mListView.setAdapter(mAdapter);

ListView和Adapter就是用這行代碼建立起關聯的。

那麼跟蹤setAdapter方法進去:

public void setAdapter(ListAdapter adapter) {
    if (mAdapter != null && mDataSetObserver != null) {
        mAdapter.unregisterDataSetObserver(mDataSetObserver);
    }

    resetList();
    mRecycler.clear();

    if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
        mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
    } else {
        mAdapter = adapter;
    }

    mOldSelectedPosition = INVALID_POSITION;
    mOldSelectedRowId = INVALID_ROW_ID;

    // AbsListView#setAdapter will update choice mode states.
    super.setAdapter(adapter);

    if (mAdapter != null) {
        mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
        mOldItemCount = mItemCount;
        mItemCount = mAdapter.getCount();
        checkFocus();

        mDataSetObserver = new AdapterDataSetObserver();
        mAdapter.registerDataSetObserver(mDataSetObserver);

        mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());

        int position;
        if (mStackFromBottom) {
            position = lookForSelectablePosition(mItemCount - 1, false);
        } else {
            position = lookForSelectablePosition(0, true);
        }
        setSelectedPositionInt(position);
        setNextSelectedPositionInt(position);

        if (mItemCount == 0) {
            // Nothing selected
            checkSelectionChanged();
        }
    } else {
        mAreAllItemsSelectable = true;
        checkFocus();
        // Nothing selected
        checkSelectionChanged();
    }

    requestLayout();
}

方法是這樣開始的

if (mAdapter != null && mDataSetObserver != null) {
    mAdapter.unregisterDataSetObserver(mDataSetObserver);
}

先判斷mAdapter != null && mDataSetObserver != null
mAdapter肯定是不爲null的,那麼mDataSetObserver呢?這個引用是哪裏被賦值的,先不管,繼續往下看setAdapter方法。
這裏先分享我看源碼的方法吧:
剛開始的時候我是很喜歡往深處闖,導致看了一天都無法自拔,思路又散了。現在我看源碼都是挑重點看,比如這個setAdapter方法,一路看下來都沒有return 語句跳出,那麼就一定會來到if(mAdapter !=null )這個判斷,如下:

if (mAdapter != null) {
    mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
    mOldItemCount = mItemCount;
    mItemCount = mAdapter.getCount();
    checkFocus();

    mDataSetObserver = new AdapterDataSetObserver();
    mAdapter.registerDataSetObserver(mDataSetObserver);

   //代碼省略
}

到了這裏,我們也就找到了mDataSetObserver,原來是在這裏被賦值的。
現在得出小結論:
1.在ListView的setAdapter方法中,生成了一個AdapterDataSetObserver對象並賦值給mDataSetObserver
2.調用Adapter的registerDataSetObserver方法將mDataSetObserver註冊進去。

現在我們好奇的是Adapter的registerDataSetObserver方法。繼續前進。
在BaseAdapter類中找到了registerDataSetObserver方法,並且也找到了經常調用的,很熟悉的notifyDataSetChanged方法。如下:

public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
    private final DataSetObservable mDataSetObservable = new DataSetObservable();

    public boolean hasStableIds() {
        return false;
    }

    public void registerDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.registerObserver(observer);
    }

    public void unregisterDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.unregisterObserver(observer);
    }

    /**
     * Notifies the attached observers that the underlying data has been changed
     * and any View reflecting the data set should refresh itself.
     */
    public void notifyDataSetChanged() {
        mDataSetObservable.notifyChanged();
    }

    /**
     * Notifies the attached observers that the underlying data is no longer valid
     * or available. Once invoked this adapter is no longer valid and should
     * not report further data set changes.
     */
    public void notifyDataSetInvalidated() {
        mDataSetObservable.notifyInvalidated();
    }

    //代碼省略

}

可以看到,在registerDataSetObserver方法中,又調用了DataSetObservable的registerObserver方法將傳進來的AdapterDataSetObserver對象註冊進去,那麼這個DataSetObservable又是什麼呢?繼續跟進

這個DataSetObservable源碼比較少,那就全部貼出

public class DataSetObservable extends Observable<DataSetObserver> {
    /**
     * Invokes {@link DataSetObserver#onChanged} on each observer.
     * Called when the contents of the data set have changed.  The recipient
     * will obtain the new contents the next time it queries the data set.
     */
    public void notifyChanged() {
        synchronized(mObservers) {
            // 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--) {
                mObservers.get(i).onChanged();
            }
        }
    }

    /**
     * Invokes {@link DataSetObserver#onInvalidated} on each observer.
     * Called when the data set is no longer valid and cannot be queried again,
     * such as when the data set has been closed.
     */
    public void notifyInvalidated() {
        synchronized (mObservers) {
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onInvalidated();
            }
        }
    }
}

好像看不太懂。mObservers是什麼?竟然沒有registerObserver方法。哈哈,那肯定是父類繼承下來的啊。在DataSetObservable類中暫時沒我們想要知道的信息,那麼就看看他的父類Observable吧。Observable還是個泛型。不管,看內部實現原理就好

public abstract class Observable<T> {
    /**
     * The list of observers.  An observer can be in the list at most
     * once and will never be null.
     */
    protected final ArrayList<T> mObservers = new ArrayList<T>();

    /**
     * Adds an observer to the list. The observer cannot be null and it must not already
     * be registered.
     * @param observer the observer to register
     * @throws IllegalArgumentException the observer is null
     * @throws IllegalStateException the observer is already registered
     */
    public void registerObserver(T observer) {
        if (observer == null) {
            throw new IllegalArgumentException("The observer is null.");
        }
        synchronized(mObservers) {
            if (mObservers.contains(observer)) {
                throw new IllegalStateException("Observer " + observer + " is already registered.");
            }
            mObservers.add(observer);
        }
    }

    //代碼省略

}

找到了registerObserver方法。代碼邏輯還挺簡單的。
我們又可以得出小結論:

DataSetObservable的內部維護着一個觀察者集合,即源碼中的mObservers。當我們的ListView綁定了Adapter,調用BaseAdapter的registerDataSetObserver方法時,實際上是在這個觀察者集合mObservers裏將該觀察者添加進來。對ListView來說,這個觀察者就是AdapterDataSetObserver
完成註冊。以上就是setAdapter方法的源碼分析

再看到BaseAdapter的notifyDataSetChanged()方法

public void notifyDataSetChanged() {
    mDataSetObservable.notifyChanged();
}

內部調用了DataSetObservable的notifyChanged方法

再回到DataSetObservable的源碼,看到notifyChanged()方法

public void notifyChanged() {
    synchronized(mObservers) {
        // 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--) {
            mObservers.get(i).onChanged();
        }
    }
}

從觀察者集合裏遍歷出觀察者,並調用該觀察者的onChange()方法

很清楚了吧。
當我們調用Adapter的notifyDataSetChanged方法更新ListView。
在notifyDataSetChanged方法中又會調用DataSetObservable的notifyChanged方法。
而從DataSetObservable的源碼中,我們知道了在notifyChanged方法中又會遍歷出
AdapterDataSetObserver(觀察者),並調用這個觀察者的onChanged()方法。
完畢,底層實現就是這樣。
接下來只需要知道AdapterDataSetObserver(觀察者)的onChanged()方法裏做了什麼就好了。
而AdapterDataSetObserver,是ListView的父類AdapterView的一個內部類。他是真的有onChanged方法的。不信你看

class AdapterDataSetObserver extends DataSetObserver {

    private Parcelable mInstanceState = null;

    @Override
    public void onChanged() {
        mDataChanged = true;
        mOldItemCount = mItemCount;
        mItemCount = getAdapter().getCount();

        // Detect the case where a cursor that was previously invalidated has
        // been repopulated with new data.
        if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
                && mOldItemCount == 0 && mItemCount > 0) {
            AdapterView.this.onRestoreInstanceState(mInstanceState);
            mInstanceState = null;
        } else {
            rememberSyncState();
        }
        checkFocus();
        requestLayout();
    }

    //代碼省略

}

終於揭開謎底,在AdapterDataSetObserver的onChanged()方法裏,實際上是調用了View的requestLayout()方法進行重新策略,佈局,繪製整個ListView的子項item view
requestLayout()的源碼如下:

public void requestLayout() {
    if (mMeasureCache != null) mMeasureCache.clear();

    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
        // Only trigger request-during-layout logic if this is the view requesting it,
        // not the views in its parent hierarchy
        ViewRootImpl viewRoot = getViewRootImpl();
        if (viewRoot != null && viewRoot.isInLayout()) {
            if (!viewRoot.requestLayoutDuringLayout(this)) {
                return;
            }
        }
        mAttachInfo.mViewRequestingLayout = this;
    }

    mPrivateFlags |= PFLAG_FORCE_LAYOUT;
    mPrivateFlags |= PFLAG_INVALIDATED;

    if (mParent != null && !mParent.isLayoutRequested()) {
        mParent.requestLayout();
    }
    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
        mAttachInfo.mViewRequestingLayout = null;
    }
}

AdapterView是繼承ViewGroup的,但是ViewGroup並沒有重寫requestLayout()方法。有能力的同學可以繼續深入研究AdapterView到底是怎麼重新佈局的

至此,我們已經解開了開篇的疑惑
綜上所述,AdapterDataSetObserver這個是觀察者,在AdapterDataSetObserver的onChanged函數中,實際上調用的是View中的方法完成了整個更新ListView的工作,AdapterDataSetObserver只是在外層進行了包裝,真正的核心功能是ListView,更加準確的說話是ListView的父類AdapterView。

可以換個角度理解,ListView 調用 setAdapter 方法,將一個觀察者注入 Adapter 中維護的觀察者序列,這樣 ListView 和 Adapter 就建立起了關聯,當有了數據變化,我們調用了 Adapter 的 notifyDataSetChanged 方法,內部會遍歷出觀察者,ListView是觀察者,Adapter 是被觀察者。ListView對Adapter訂閱。有了數據更新後,Adapter通知ListView,告訴 ListView ,你可以更新視圖了,之後 ListView調用 requestLayout 方法重新繪製界面,完成視圖的更新。

ListView就是通過Adapter模式,觀察者模式,子項複用機制實現了視圖良好的擴展性,節約了內存開銷,提高了運行效率

發佈了27 篇原創文章 · 獲贊 138 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章