標籤:
上一篇部落格跟大家分享了Android源碼中的裝飾者模式,有點意猶未盡,今天跟大家分享下Android中的觀察者模式,順便說一說觀察者模式和回調機制的關係,歡迎大家拍磚。
觀察者模式定義
觀察者模式定義了一種一對多的依賴關係,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態上發生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。
觀察者模式的結構
觀察者模式所涉及的角色有:
抽象主題(Subject)角色:抽象主題角色把所有對觀察者對象的引用儲存在一個聚集(比如ArrayList對象)裡,每個主題都可以有任何數量的觀察者。抽象主題提供一個介面,可以增加和刪除觀察者對象,抽象主題角色又叫做抽象被觀察者(Observable)角色。
具體主題(ConcreteSubject)角色:將有關狀態存入具體觀察者對象;在具體主題的內部狀態改變時,給所有登記過的觀察者發出通知。具體主題角色又叫做具體被觀察者(Concrete Observable)角色。
抽象觀察者(Observer)角色:為所有的具體觀察者定義一個介面,在得到主題的通知時更新自己,這個介面叫做更新介面。
- 具體觀察者(ConcreteObserver)角色:儲存與主題的狀態自恰的狀態。具體觀察者角色實現抽象觀察者角色所要求的更新介面,以便使本身的狀態與主題的狀態 像協調。如果需要,具體觀察者角色可以保持一個指向具體主題對象的引用。
實現
抽象主題角色類
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); } }}
具體主題角色類
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); }}
抽象觀察者角色類
public interface Observer { /** * 更新介面 * @param state 更新的狀態 */ public void update(String state);}
具體觀察者角色類
public class ConcreteObserver implements Observer { //觀察者的狀態 private String observerState; @Override public void update(String state) { /** * 更新觀察者的狀態,使其與目標的狀態保持一致 */ observerState = state; System.out.println("狀態為:"+observerState); }}
測試類別
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模型是主題對象不知道觀察者具體需要什麼資料,沒有辦法的情況下,乾脆把自身傳遞給觀察者,讓觀察者自己去按需要取值。
Push模型可能會使得觀察者對象難以複用,因為觀察者的update()方法是按需要定義的參數,可能無法兼顧沒有考慮到的使用方式。這就意味著出現新情況的時候,就可能提供新的update()方法,或者是乾脆重新實現觀察者;而Pull模型就不會造成這樣的情況,因為Pull模型下,update()方法的參數是主題對象本身,這基本上是主題對象能傳遞的最大資料集合了,基本上可以適應各種情況的需要。
回調機制和觀察者模式
Android中有非常多的地方使用了回調機制,例如Activity的生命週期、按鈕的點擊事件、線程的run()方法等。
下面是回調的基本模型:
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()方法。
/** * 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()方法。
/** * 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)方法。
@Overridepublic 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。
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
當觀察者模式和回調機制遇上Android源碼