前言
觀察者模式是我們開發工作中經常使用的開發模式。Android 原始碼中也有很多地方用到此模式。比如:ListView、ContentProvider 和 Broadcast 等等。本文將會介紹觀察者模式、實現一個觀察者模式並結合 Android 原始碼進行分析。
定義
定義物件間的一種一個對多的依賴關係,當一個物件的狀態傳送改變時,所以依賴於它的物件都得到通知並被自動更新。
介紹
觀察者模式又被稱作釋出/訂閱模式。即 1 人釋出,N 人訂閱。觀察者模式主要用來解耦,將被觀察者和觀察者解耦,讓他們之間者依賴關係很小,甚至不存在依賴。它最用的地方是 GUI 系統、訂閱——釋出系統。舉個例子,我司開發的一個 App 左上角狀態列需要實時顯示鬧鐘的狀態,若存在設定好的鬧鐘則顯示鬧鐘圖示,否則不顯示。因為儲存鬧鐘使用的 ContentProvider,所以可以使用一個監聽器監聽鬧鐘資料是否發生變化,若發生變化就判斷當前鬧鐘狀態並顯示或關閉圖示。這樣便實現了實時顯示鬧鐘狀態的功能。
UML 類圖
手工實現
- 建立觀察者
實現抽象觀察者 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可以僅對資料庫中感興趣的資料有變化時,進行監聽。具體原始碼就不再進行分析,大家感興趣可以自行去分析原始碼,觀察者模式是萬變不離其宗的。