Android設計模式原始碼解析之ListView觀察者模式
Android設計模式原始碼解析之觀察者模式
本文為 Android 設計模式原始碼解析 中 觀察者模式 分析
Android系統版本: 2.3
分析者:Mr.Simple,分析狀態:未完成,校對者:Mr.Simple,校對狀態:未開始
1. 模式介紹
模式的定義
定義物件間一種一對多的依賴關係,使得每當一個物件改變狀態,則所有依賴於它的物件都會得到通知並被自動更新。
模式的使用場景
- 關聯行為場景。需要注意的是,關聯行為是可拆分的,而不是“組合”關係;
- 事件多級觸發場景;
- 跨系統的訊息交換場景,如訊息佇列、事件匯流排的處理機制。
2. UML類圖
角色介紹
抽象主題 (Subject) 角色
抽象主題角色把所有觀察者物件的引用儲存在一個聚集(比如ArrayList物件)裡,每個主題都可以有任意數量的觀察者。抽象主題提供一個介面,可以增加和刪除觀察者物件,抽象主題角色又叫做抽象被觀察者(Observable)角色。具體主題 (ConcreteSubject) 角色
將有關狀態存入具體觀察者物件;在具體主題的內部狀態改變時,給所有登記過的觀察者發出通知。具體主題角色又叫做具體被觀察者(Concrete Observable)角色。抽象觀察者 (Observer) 角色
為所有的具體觀察者定義一個介面,在得到主題的通知時更新自己,這個介面叫做更新介面。具體觀察者 (ConcreteObserver) 角色
儲存與主題的狀態自恰的狀態。具體觀察者角色實現抽象觀察者角色所要求的更新介面,以便使本身的狀態與主題的狀態 像協調。如果需要,具體觀察者角色可以保持一個指向具體主題物件的引用。
3. 模式的簡單實現
簡單實現的介紹
AndroidWeekly是一個每週都會發布關於Android新技術、開源庫、招聘資訊等內容的網站,在這裡我們可以看到最新的技術,最牛X的工程師,經常逛逛這類網站不僅能夠開闊我們的眼界,也能讓我們接觸到最前言的科技資訊。這其實就是一個RSS系統,使用者訂閱Android Weekly的文章,每當有更新的時候將新的內容推送給訂閱使用者。這不就是觀察者模式嗎?觀察者模式的另一個名字叫做釋出-訂閱模式,下圖就是我訂閱AndroidWeekly之後他們發來的確認郵件。下面讓我們來簡單模擬一下AndroidWeekly的釋出過程吧!
實現原始碼
/**
* 程式設計師是觀察者
*
* @author mrsimple
*/
public class Coder implements Observer {
public String name ;
public Coder(String aName) {
name = aName ;
}
@Override
public void update(Observable o, Object arg) {
System.out.println( "Hi, " + name + ", AndroidWeekly更新啦, 內容 : " + arg);
}
@Override
public String toString() {
return "碼農 : " + name;
}
}
/**
* AndroidWeekly這個網站是被觀察者,它有更新所有的觀察者 (這裡是程式設計師) 都會接到相應的通知.
*
* @author mrsimple
*/
public class AndroidWeekly extends Observable {
public void postNewPublication(String content) {
// 標識狀態或者內容發生改變
setChanged();
// 通知所有觀察者
notifyObservers(content);
}
}
// 測試程式碼
public class Test {
public static void main(String[] args) {
// 被觀察的角色
AndroidWeekly androidWeekly = new AndroidWeekly();
// 觀察者
Coder mrsimple = new Coder("mr.simple");
Coder coder1 = new Coder("coder-1");
Coder coder2 = new Coder("coder-2");
Coder coder3 = new Coder("coder-3");
// 將觀察者註冊到可觀察物件的觀察者列表中
androidWeekly.addObserver(mrsimple);
androidWeekly.addObserver(coder1);
androidWeekly.addObserver(coder2);
androidWeekly.addObserver(coder3);
// 釋出訊息
androidWeekly.postNewPublication("新的一期AndroidWeekly來啦!");
}
}
輸入結果:
Hi, coder-3, AndroidWeekly更新啦, 內容 : 新的一期AndroidWeekly來啦!
Hi, coder-2, AndroidWeekly更新啦, 內容 : 新的一期AndroidWeekly來啦!
Hi, coder-1, AndroidWeekly更新啦, 內容 : 新的一期AndroidWeekly來啦!
Hi, mr.simple, AndroidWeekly更新啦, 內容 : 新的一期AndroidWeekly來啦!
可以看到所有訂閱了AndroidWeekly的使用者都受到了更新訊息,一對多的訂閱-釋出系統這麼簡單就完成了。
這裡Observer是抽象的觀察者角色,Coder扮演的是具體觀察者的角色;Observable對應的是抽象主題角色,AndroidWeekly則是具體的主題角色。Coder是具體的觀察者,他們訂閱了AndroidWeekly這個具體的可觀察物件,當AndroidWeekly有更新時,會遍歷所有觀察者 ( 這裡是Coder碼農 ),然後給這些觀察者釋出一個更新的訊息,即呼叫Coder中的update方法,這樣就達到了1對多的通知功能。Observer和Observable都已經內建在jdk中,可見觀察者模式在java中的重要性。
Android原始碼中的模式實現
ListView是Android中最重要的控制元件,沒有之一。而ListView最重要的一個點就是Adapter,在Android設計模式原始碼解析之介面卡(Adapter)模式中我們分析了Adapter模式,在我們往ListView新增資料後,我們都會呼叫一個方法: notifyDataSetChanged(), 這是為什麼呢? 今天我們就來揭開它的神祕面紗。
第一步我們就跟進這個方法notifyDataSetChanged方法,這個方法定義在BaseAdapter中,程式碼如下:
public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
// 資料集觀察者
private final DataSetObservable mDataSetObservable = new DataSetObservable();
// 程式碼省略
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是一個觀察者模式!
那麼BaseAdapter是如何運作的? 這些觀察者又是什麼呢?我們一步一步來分析。
我們先跟到mDataSetObservable.notifyChanged()函式中看看。
/**
* A specialization of Observable for DataSetObserver that provides methods for
* invoking the various callback methods of DataSetObserver.
*/
public class DataSetObservable extends Observable<DataSetObserver> {
/**
* Invokes onChanged on each observer. Called when the data set being observed has
* changed, and which when read contains the new state of the data.
*/
public void notifyChanged() {
synchronized(mObservers) {
// 呼叫所有觀察者的onChanged方式
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
// 程式碼省略
}
恩,程式碼很簡單,就是在mDataSetObservable.notifyChanged()中遍歷所有觀察者,並且呼叫它們的onChanged方法。
那麼這些觀察者是從哪裡來的呢?首先ListView通過setAdapter方法來設定Adapter,我們看看相關程式碼。
@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中,這樣我們的被觀察者、觀察者都有了。一般來說我們的資料集會放到Adapter中,例如 :
public abstract class UserAdapter extends BaseAdapter {
// 資料集
protected List<String> mDataSet = new LinkedList<String>();
protected Context mContext = null;
public CommonAdapter(Context context, List<String> dataSet) {
this.mDataSet = dataSet;
this.mContext = context;
}
}
這個時候可能你就有點暈了? AdapterDataSetObserver是什麼?它是如何運作的?那麼我就先來看看AdapterDataSetObserver吧。
AdapterDataSetObserver定義在ListView的父類AbsListView中,程式碼如下 :
class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
@Override
public void onChanged() {
super.onChanged();
if (mFastScroller != null) {
mFastScroller.onSectionsChanged();
}
}
@Override
public void onInvalidated() {
super.onInvalidated();
if (mFastScroller != null) {
mFastScroller.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();
}
// 程式碼省略
public void clearSavedState() {
mInstanceState = null;
}
}
到這裡我們就知道了,當ListView的資料發生變化時,呼叫Adapter的notifyDataSetChanged函式,這個函式又會呼叫DataSetObservable的notifyChanged函式,這個函式會呼叫所有觀察者 (AdapterDataSetObserver) 的onChanged方法。這就是一個觀察者模式!
最後我們再捋一捋,AdapterView中有一個內部類AdapterDataSetObserver,在ListView設定Adapter時會構建一個AdapterDataSetObserver,並且註冊到Adapter中,這個就是一個觀察者。而Adapter中包含一個數據集可觀察者DataSetObservable,在資料數量發生變更時開發者手動呼叫AdapternotifyDataSetChanged,而notifyDataSetChanged實際上會呼叫DataSetObservable的notifyChanged函式,該函式會遍歷所有觀察者的onChanged函式。在AdapterDataSetObserver的onChanged函式中會獲取Adapter中資料集的新數量,然後呼叫ListView的requestLayout()方法重新進行佈局,更新使用者介面。
圖1 | 圖2 |
這篇文章來自SAOS開源專案組,關於SAOS的介紹請參考Android開源庫與設計模式開源組SAOS,更多文章請移步到SAOS開源專案組; 歡迎加入Android框架設計交流群 ,群號碼:413864859。
4. 雜談
ListView主要運用了Adapter和觀察者模式使得可擴充套件性、靈活性非常強,而耦合度卻很低,這是我認為設計模式在Android原始碼中優秀運用的典範。那麼為什麼Android架構師們會這麼設計ListView,它們如何達到低耦合、高靈活性呢?這個留給大家思考吧,如果有時間我再分享我的看法。
優點
- 觀察者和被觀察者之間是抽象耦合
缺點
- 觀察者模式需要考慮一下開發效率和執行效率問題,一個被觀察者,多個觀察者,開發和除錯就會比較複雜,而且在 Java 中訊息的通知預設是順序執行,一個觀察者卡殼,會影響整體的執行效率。在這種情況下,一般考慮採用非同步的方式。