Android源碼中的觀察者模式

        工作這麼久,看過這麼多別人的技術博客,還沒有在自己的賬號上寫點什麼,實在慚愧。與其說要把自己的經驗寫出來給大家分享,不如說我想記錄一下此刻自己的所思所得,以便日後查看。

        早在本科時期就接觸過一本書叫《大話設計模式》。因爲當時代碼量幾乎爲零,因此對設計模式並沒有多少認識。現在出去面試經常被問到設計模式,自己寫Android代碼時也遇到一些常見模式,今天想要記錄一下這幾天研究的觀察者模式。

1 JDK源碼中的觀察者模式       

        不管是在《大話設計模式》,還是GoF的《設計模式》,還是別的關於設計模式的經典書籍,抑或是在各類技術博客,大家可以輕易地找到關於觀察者模式的定義和示例。因此觀察者模式的概念和基本使用不是本文的討論重點。重點是,觀察者模式的使用是如此的廣泛,以至於JDK源碼中已爲該模式提供java.util.Observable基類和java.util.Observers接口。

JDK中的觀察者模式UML類圖


        JDK中的觀察者模式的使用如圖所示:Student類實現Observer接口,通過addObserver方法註冊到Teacher的觀察者列表中去;而Teacher類繼承自Observable基類,當Teacher發佈消息(publishMessage)時調用notifyObservers()方法,遍歷觀察者列表中的所有Student,調用他們的update方法;於是Teacher中的message傳遞到了每個Student中。

        JDK中的Observable基類和Observer接口爲我們的簡單使用觀察者模式提供了方便,但它有一個非常明顯的缺點:爲了使用觀察者模式,需要讓Teacher繼承Observable這個基類,但是Teacher可能已經繼承了Person類,而Java又是單繼承,不能同時再繼承Observable類。解決這個矛盾的思路有兩種:一是自定義觀察者模式,將add、delete、notify等方法寫進Teacher類;二是使用代理模式,在Teacher中維護一個Observable類的對象,並且實現同名的方法,類似如下代碼。

    public void addObserver(Observer observer){
        mObservable.addObserver(observer);
    }
    此處不展開討論了,Android源碼中的觀察者就是採用類似第二種思路的方法。
2 Android源碼中的觀察者模式
      其實翻看Android源碼,發現Android並沒有使用JDK的Observable基類和Observer接口。Instead,Android定義了一個android.database.Observable<T>抽象類,其中T是Oberver type。Observable抽象類中只有registerObserver、unregisterObserver和unregisterAll三個抽象方法,notify的操作需要子類自己定義。與JDK中Observer接口相對應的是DataSetObserver抽象類,裏面有onChanged和onValidated兩個抽象方法。notify方法、DataSetObserver類、onChanged方法,是不是想到了什麼東西?對,setDataSetChanged()!是不是很熟悉?

2.1 ListView源碼中的觀察者模式

      在使用ListView時,數據改變後,我們會手動去調用ListView對應的adapter的setDataSetChanged()方法來通知ListView更新UI。換一種說法,ListView的UI是觀察者,ListView對應的adapter中的數據是被觀察者,ListView通過註冊一個觀察者到adapter中,以實現監聽adapter的數據變化的目的。經過刨祖墳一般的Ctrl+鼠標左鍵之後,畫出如下UML類圖。圖中可以看到,ListView註冊到adapter中的觀察者叫AdapterDataSetObserver,定義在ListView的父類AbsListview中。它又繼承自AbsListView的父類AdapterView類中的同名AdapterDataSetObserver。AdapterView.AdapterDataSetObserver最終繼承自DataSetObserver抽象類。

ListView中的觀察者模式UML類圖

        細看左下角的BaseAdapter並不是繼承Observable,而是擁有DataSetObservable的一個對象,並且包裝一下DataSetObservable的方法,對外提供擁有類似方法名的方法,比如

    /**
     * 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();
    }
        上文提到,ListView通過註冊一個觀察者到adapter中以實現監聽adapter中數據集的變化,那麼註冊的時機是什麼呢?繼續挖祖墳,找到ListView的setAdapter方法,此方法定義在ListView的爺爺類AdapterView中,這裏是Override父類方法。當ListView綁定adapter時,先解註冊以前的觀察者(如果有的話),然後再new一個新的AdapterDataSetObserver並且註冊到adapter中去。
    @Override
    public void setAdapter(ListAdapter adapter) {
        if (mAdapter != null && mDataSetObserver != null) {
            <strong>mAdapter.unregisterDataSetObserver(mDataSetObserver);</strong>
        }

       //...此處省略部分代碼

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

           <strong> mDataSetObserver = new AdapterDataSetObserver();
           mAdapter.registerDataSetObserver(mDataSetObserver);</strong>

            mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());

           //...此處省略部分代碼} 
       else { 
            mAreAllItemsSelectable = true; checkFocus(); 
            // Nothing selected checkSelectionChanged(); 
       } 
       requestLayout(); 
    }


        於是我們手動調用adapter的notifyDataSetChanged方法的時候,最終會調到AdapterView.AdapterDataSetObserver的onChanged方法,通過requestLayout()去更新ListView的UI。
        @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();
        }
2.2 RecyclerView中的觀察者模式
      由於最近使用RecyclerView代替ListView,所以把RecyclerView中的觀察者模式的實現源碼也挖出來了,詳見下圖,與ListView類似。

RecyclerView中的觀察者模式UML類圖

參考書目:

  《大話設計模式》程傑 著

 《Android源碼設計模式解析與實戰》何紅輝/關愛民 著

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章