Android 中的設計模式:觀察者模式

Rickon發表於2019-02-28

Android 中的設計模式:觀察者模式

前言

觀察者模式是我們開發工作中經常使用的開發模式。Android 原始碼中也有很多地方用到此模式。比如:ListView、ContentProvider 和 Broadcast 等等。本文將會介紹觀察者模式、實現一個觀察者模式並結合 Android 原始碼進行分析。

定義

定義物件間的一種一個對多的依賴關係,當一個物件的狀態傳送改變時,所以依賴於它的物件都得到通知並被自動更新。

介紹

觀察者模式又被稱作釋出/訂閱模式。即 1 人釋出,N 人訂閱。觀察者模式主要用來解耦,將被觀察者和觀察者解耦,讓他們之間者依賴關係很小,甚至不存在依賴。它最用的地方是 GUI 系統、訂閱——釋出系統。舉個例子,我司開發的一個 App 左上角狀態列需要實時顯示鬧鐘的狀態,若存在設定好的鬧鐘則顯示鬧鐘圖示,否則不顯示。因為儲存鬧鐘使用的 ContentProvider,所以可以使用一個監聽器監聽鬧鐘資料是否發生變化,若發生變化就判斷當前鬧鐘狀態並顯示或關閉圖示。這樣便實現了實時顯示鬧鐘狀態的功能。

UML 類圖

Android 中的設計模式:觀察者模式

手工實現

  • 建立觀察者

實現抽象觀察者 Observer 中的方法,這裡建立一個 Coder 類,並定義收到通知後的動作:

/**
 * 程式設計師是觀察者
 *
 */
public class Coder implements Observer{
	public String name;
	
	public Coder(String mName) {
		name = mName;
	}

	@Override
	public void update(Observable arg0, Object arg1) {
		// TODO Auto-generated method stub
		System.out.println("嘿," + name + ",您的快遞到了");
	}
	
	public String toString(String name) {
		return "程式設計師:"+name;
	}
}
複製程式碼
  • 建立被觀察者 實現 Observable 方法,也就是快遞員,快遞到了會通知程式設計師們來拿快遞:
/**
 * Courier 即快遞員,當他到公司時通知所有觀察者(有快遞的程式設計師)拿快遞
 * @author Rickon
 *
 */
public class Courier extends Observable {
	public void postNewExpress(String content) {
		//標識狀態或者內容發生改變(此處為快遞到了)
		setChanged();
		
		//通知所有觀察者
		notifyObservers(content);
	}
}
複製程式碼
  • 測試
public class Test {

	public static void main(String[] args) {
		//被觀察者
		Courier courier = new Courier();
		//觀察者
		Coder coder1 = new Coder("程式設計師張三");
		Coder coder2 = new Coder("程式設計師李四");
		Coder coder3 = new Coder("程式設計師王二麻子");
		
		//將觀察者註冊到被觀察者的觀察者列表中
		courier.addObserver(coder1);
		courier.addObserver(coder2);
		courier.addObserver(coder3);
		
		//快遞到了
		courier.postNewExpress("快遞到啦!");
	}

}
複製程式碼
  • 輸出結果
嘿,程式設計師王二麻子,您的快遞到了
嘿,程式設計師李四,您的快遞到了
嘿,程式設計師張三,您的快遞到了
複製程式碼

總結 上述程式碼就實現了一個簡單地觀察者模式,使用的是 JDK 內部內建的 Observable(抽象被觀察者),Observer(抽象觀察者)。JDK 內建觀察者模式也說明了觀察者模式應用的廣泛與重要性。

應用場景

  • 事件多級觸發場景;
  • 當一個物件必須通知別的物件,而它又不能假定物件是誰;
  • 跨系統的訊息交換場景,如訊息佇列、事件匯流排的處理機制。

優缺點

優點

  • 觀察者與被觀察者之間是抽象耦合,可以很好地應對業務變化;
  • 增強系統靈活性、可擴充套件性。

缺點

  • 開發過程中可能會出現一個被觀察者、多個觀察者的情況,開發和除錯會變得比較複雜。除此之外,觀察者太多的話收到通知需要消耗更長的時間。

Android 中的觀察者模式

  • ListView 深入解析
    ListView 是 Android 中很常用的控制元件,我們在使用 ListView 新增資料後會呼叫 Adapter 的 notifyDataSetChanged()方法,這是為啥呢?下面我們來一探究竟!

我們發現 notifyDataSetChanged,這個方法是在 BaseAdapter 中定義的,具體帶麼如下:

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();
    }

    //省略部分程式碼
}
複製程式碼

BaseAdapter 看起來是觀察者模式,那麼到底 BaseAdapter 是怎麼執行的呢?又是如何實現的觀察者模式,讓我們繼續分析。

首先看看 mDataSetObservable.notifyChanged() 方法:

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) {
            // 遍歷呼叫每個觀察者的 onChanged() 方法來通知被觀察者發生變化
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }
    }
    
    //省略部分程式碼
}

複製程式碼

這裡很容易看到此方法是遍歷所有觀察者,並且呼叫所有觀察者的 onChnged() 方法,從而告知觀察者發生了變化。那麼這些觀察者是怎麼設定的呢?其實這些觀察者就是 ListVIew 通過 setAdapter 方法產生的,我們看看這部分程式碼:

 @Override
    public void setAdapter(ListAdapter adapter) {
        //如果已經有了 Adapter,那麼先登出 Adapter 對應的觀察者
        if (mAdapter != null && mDataSetObserver != null) {
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
        }

        //省略部分程式碼
        super.setAdapter(adapter);

        if (mAdapter != null) {
            mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
            mOldItemCount = mItemCount;
            //獲取資料的數量
            mItemCount = mAdapter.getCount();
            checkFocus();
            //此處建立一個個資料集觀察者
            mDataSetObserver = new AdapterDataSetObserver();
            //將觀察者註冊到 Adapter 中,實際上是註冊到 DatasetObservable
            mAdapter.registerDataSetObserver(mDataSetObserver);

            //省略部分程式碼
        } else {
           //省略部分程式碼
        }

        requestLayout();
    }
複製程式碼

從上述程式碼中可以看出,在設定 Adapter 時會構建一個 AdapterDataSetObserver,也就是觀察者,之後再將這個觀察者註冊到 Adapter 中,這樣我們的被觀察者和觀察者就都構建好了。那麼 AdapterDataSetObserver 到底是怎麼執行的呢?它是定義在 ListView 的父類 AbsListView 中,程式碼如下:

class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
        @Override
        public void onChanged() {
            super.onChanged();
            if (mFastScroll != null) {
                mFastScroll.onSectionsChanged();
            }
        }

        @Override
        public void onInvalidated() {
            super.onInvalidated();
            if (mFastScroll != null) {
                mFastScroll.onSectionsChanged();
            }
        }
    }
複製程式碼

它又繼承自 AbsListView 的父類 AdapterView 的 AdapterDataSetObserver,程式碼如下

class AdapterDataSetObserver extends DataSetObserver {

        private Parcelable mInstanceState = null;
        //前面提到的呼叫 Adapter 的 notifyDataSetChanged 時會呼叫所有觀察者的 onChanged 方法,核心實現程式碼就在這裡
        @Override
        public void onChanged() {
            mDataChanged = true;
            mOldItemCount = mItemCount;
            //獲取 Adapter 中資料的數量
            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();
            //重新整理 ListView、GridView 等 AdapterView 等元件
            requestLayout();
        }
        
        //省略部分程式碼
    }
複製程式碼

原始碼分析到這裡就很明顯了,當我們呼叫 Adapter 的 notifyDataSetChanged 方法,這個方法又會呼叫 DataSetObservable 的 notifyDataSetChanged 方法,而這個方法會呼叫所有觀察者的 onChanged 方法,在 onChanged 方法中則是會呼叫 ListView 的重新佈局方法重新整理UI,這就是一個完整的觀察者模式。

ListView 觀察者模式實現總結: 我重新概述這一過程:Adapter 中包含一個資料集可觀察者 DataSetObservable,在資料數量發生變更時,開發者手動呼叫 Adapter.notifyDataSetChanged,實際上呼叫的則是 DataSetObservable 的 onChanged 方法,該方法會遍歷所有觀察者的 onChanged 方法。在 AdapterDataSetObserver 的 onChanged 函式中會獲取 Adapter 中資料集的新數量,然後呼叫 ListView 的 requestLayout() 方法重新佈局並更新 UI。

  • ContentProvider 文章的開始有提到使用 ContentProvider 的過程中也用到了觀察者模式。首先建立一個觀察者物件,程式碼如下:
ContentObserver alarmObserver = new ContentObserver(new Handler()) {
        //建立觀察者物件
        @Override
        public void onChange(boolean selfChange) {
            super.onChange(selfChange);
            //執行業務程式碼
        }

        @Override
        public void onChange(boolean selfChange, Uri uri) {
            super.onChange(selfChange, uri);
        }
    };
複製程式碼

註冊觀察者:

mContext.getContentResolver().registerContentObserver(url, true, alarmObserver);
複製程式碼

看到此註冊方法有三個引數分別代表什麼意義呢:

  • uri:針對對有變化的感興趣進行監聽的URI;
  • notifyForDescendents:true表示以uri字首開始的任何變化都進行通知;false表示完全匹配才進行通知;
  • observer:IContentObserver介面,提供了一個方法onChange,變化發生時Cursor需要更新時呼叫

這樣就通過指定Uri可以僅對資料庫中感興趣的資料有變化時,進行監聽。具體原始碼就不再進行分析,大家感興趣可以自行去分析原始碼,觀察者模式是萬變不離其宗的。

相關文章