上一篇部落格跟大家分享了Android原始碼中的裝飾者模式,有點意猶未盡,今天跟大家分享下Android中的觀察者模式,順便說一說觀察者模式和回撥機制的關係,歡迎大家拍磚。
觀察者模式
定義
觀察者模式定義了一種一對多的依賴關係,讓多個觀察者物件同時監聽某一個主題物件。這個主題物件在狀態上發生變化時,會通知所有觀察者物件,使它們能夠自動更新自己。
觀察者模式的結構
觀察者模式所涉及的角色有:
- 抽象主題(Subject)角色:抽象主題角色把所有對觀察者物件的引用儲存在一個聚集(比如ArrayList物件)裡,每個主題都可以有任何數量的觀察者。抽象主題提供一個介面,可以增加和刪除觀察者物件,抽象主題角色又叫做抽象被觀察者(Observable)角色。
- 具體主題(ConcreteSubject)角色:將有關狀態存入具體觀察者物件;在具體主題的內部狀態改變時,給所有登記過的觀察者發出通知。具體主題角色又叫做具體被觀察者(Concrete Observable)角色。
- 抽象觀察者(Observer)角色:為所有的具體觀察者定義一個介面,在得到主題的通知時更新自己,這個介面叫做更新介面。
- 具體觀察者(ConcreteObserver)角色:儲存與主題的狀態自恰的狀態。具體觀察者角色實現抽象觀察者角色所要求的更新介面,以便使本身的狀態與主題的狀態 像協調。如果需要,具體觀察者角色可以保持一個指向具體主題物件的引用。
實現
抽象主題角色類
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
public abstract class Subject { /** * 用來儲存註冊的觀察者物件 */ private List<Observer> list = new ArrayList<Observer>(); /** * 註冊觀察者物件 * @param observer 觀察者物件 */ public void attach(Observer observer){ list.add(observer); System.out.println("Attached an observer"); } /** * 刪除觀察者物件 * @param observer 觀察者物件 */ public void detach(Observer observer){ list.remove(observer); } /** * 通知所有註冊的觀察者物件 */ public void nodifyObservers(String newState){ for(Observer observer : list){ observer.update(newState); } } } |
具體主題角色類
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class ConcreteSubject extends Subject{ private String state; public String getState() { return state; } public void change(String newState){ state = newState; System.out.println("主題狀態為:" + state); //狀態發生改變,通知各個觀察者 this.nodifyObservers(state); } } |
抽象觀察者角色類
1 2 3 4 5 6 7 |
public interface Observer { /** * 更新介面 * @param state 更新的狀態 */ public void update(String state); } |
具體觀察者角色類
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class ConcreteObserver implements Observer { //觀察者的狀態 private String observerState; @Override public void update(String state) { /** * 更新觀察者的狀態,使其與目標的狀態保持一致 */ observerState = state; System.out.println("狀態為:"+observerState); } } |
測試類
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class Test { public static void main(String[] args) { //建立主題物件 ConcreteSubject subject = new ConcreteSubject(); //建立觀察者物件 Observer observer = new ConcreteObserver(); //將觀察者物件登記到主題物件上 subject.attach(observer); //改變主題物件的狀態 subject.change("new state"); } } |
觀察者的兩種實現方式
- Push主題物件向觀察者推送主題的詳細資訊,不管觀察者是否需要,推送的資訊通常是主題物件的全部或部分資料。
- Pull主題物件在通知觀察者的時候,只傳遞少量資訊。如果觀察者需要更具體的資訊,由觀察者主動到主題物件中獲取,相當於是觀察者從主題物件中拉資料。一般這種模型的實現中,會把主題物件自身通過update()方法傳遞給觀察者,這樣在觀察者需要獲取資料的時候,就可以通過這個引用來獲取了。
兩種方式的比較
- Push模型是假定主題物件知道觀察者需要的資料;而Pull模型是主題物件不知道觀察者具體需要什麼資料,沒有辦法的情況下,乾脆把自身傳遞給觀察者,讓觀察者自己去按需要取值。
- Push模型可能會使得觀察者物件難以複用,因為觀察者的update()方法是按需要定義的引數,可能無法兼顧沒有考慮到的使用情況。這就意味著出現新情況的時候,就可能提供新的update()方法,或者是乾脆重新實現觀察者;而Pull模型就不會造成這樣的情況,因為Pull模型下,update()方法的引數是主題物件本身,這基本上是主題物件能傳遞的最大資料集合了,基本上可以適應各種情況的需要。
回撥機制和觀察者模式
Android中有非常多的地方使用了回撥機制,例如Activity的生命週期、按鈕的點選事件、執行緒的run()方法等。
下面是回撥的基本模型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public interface CallBack { public void oncall(); } public class A { private CallBack callback; //註冊一個事件 public void register(CallBack callback){ this.callback = callback; } // 需要呼叫的時候回撥 public void call(){ callback.oncall(); } } public static void main(String[] args) { A a = new A(); a.register(new CallBack() { @Override public void oncall() { System.out.println("回撥函式被呼叫"); } }); a.call(); |
}
這樣看來,回撥機制和觀察者模式是一致的,區別是觀察者模式裡面目標類維護了所有觀察者的引用,而回撥裡面只是維護了一個引用。
Android中的觀察者模式
Android中大量的使用了觀察者模式,Framework層裡面的事件驅動都是基於觀察者模式實現的。另外在Framework層裡面的各種服務在資料變更的時候,也是通過觀察者模式實現上層資料更新的。像View的Listener監聽、GPS位置資訊監聽、BroadcastReceiver等都是基於觀察者模式實現的。下面我們說一說ListView中的觀察者模式是如何實現的,RecyclerView大同小異,感興趣的可以自己研究下。
Listview的notifyDataSetChanged()
我們先來看下listview部分觀察者模式的結構
其中為了方便研究關係,我們省略了Adapter部分的一些類的關係。接下來我們看下具體呼叫關係。
首先當我們資料改變的時候我們會呼叫adapter的notifyDataSetChanged()方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
/** * Common base class of common implementation for an {@link Adapter} that can be * used in both {@link ListView} (by implementing the specialized * {@link ListAdapter} interface) and {@link Spinner} (by implementing the * specialized {@link SpinnerAdapter} interface). */ 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(); } } |
根據上述程式碼我們可以定位到mDataSetObservable.notifyChanged()方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
/** * A specialization of {@link Observable} for {@link DataSetObserver} * that provides methods for sending notifications to a list of * {@link DataSetObserver} objects. */ 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(); } } } } |
我們看到,呼叫notifyChanged()方法,會去遍歷mObservers,呼叫所有觀察者的onchange()方法。
那麼問題來了,我們的觀察者物件是什麼時候新增進去的呢?我們去看下ListView第一次和BaseAdapter產生關聯的地方,也就是setAdapter(ListAdapter adapter)方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
@Override public void setAdapter(ListAdapter adapter) { //如果已經設定過了Adapter,那麼取消註冊對應的觀察者。 if (mAdapter != null && mDataSetObserver != null) { mAdapter.unregisterDataSetObserver(mDataSetObserver); } //省略部分程式碼 if (mAdapter != null) { mAreAllItemsSelectable = mAdapter.areAllItemsEnabled(); mOldItemCount = mItemCount; mItemCount = mAdapter.getCount(); checkFocus(); //建立一個對應資料的觀察者 mDataSetObserver = new AdapterDataSetObserver(); //間接呼叫DataSetObservable的註冊方法 mAdapter.registerDataSetObserver(mDataSetObserver); //省略部分程式碼 } else { mAreAllItemsSelectable = true; checkFocus(); // Nothing selected checkSelectionChanged(); } requestLayout(); } |
這樣我們的四個角色就全了,Observable—>Subject;DataSetObservable—>Concrete Subject;DataSetObserver—>Observer;AdapterDataSetObserver—>Concrete Observer。然後我們註冊的地方也找到了。
最後就剩下我們的資料是如何重新整理這一個問題了。AdapterDataSetObserver定義在ListView的父類AbsListView中,它又繼承自AbsListView的父類AdapterView的AdapterDataSetObserver。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
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; } } |
requestLayout()方法在View裡有實現,子View按需求重寫。我們看下注釋好了。
/*Call this when something has changed which has invalidated the layout of this view. This will schedule a layout pass of the view tree./
好了,到這裡所有的呼叫關係我們基本就搞清楚了。當ListView的資料發生變化時,呼叫Adapter的notifyDataSetChanged函式,這個函式又會呼叫DataSetObservable的notifyChanged函式,這個函式會呼叫所有觀察者 (AdapterDataSetObserver) 的onChanged方法。在onChanged函式中會獲取Adapter中資料集的新數量,然後呼叫ListView的requestLayout()方法重新進行佈局,更新使用者介面。
瞎總結
ListView主要運用了Adapter和觀察者模式使得可擴充套件性、靈活性非常強,而耦合度卻很低,這是我認為設計模式在Android原始碼中優秀運用的典範。那我們就要開始思考了,我們有沒有其他更漂亮的套路來實現ListView元件,我們可以把這件實現思路應用到哪裡?
人是會思考的蘆葦,思考著思考著我們就成為了別人眼中的大神。
參考連結:
http://www.itdadao.com/articles/c15a265623p0.html
http://blog.csdn.net/bboyfeiyu/article/details/44040533
http://www.cnblogs.com/mythou/p/3370340.html
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式