當觀察者模式和回撥機制遇上Android原始碼

PleaseCallMeCoder發表於2016-09-29

上一篇部落格跟大家分享了Android原始碼中的裝飾者模式,有點意猶未盡,今天跟大家分享下Android中的觀察者模式,順便說一說觀察者模式和回撥機制的關係,歡迎大家拍磚。

觀察者模式

定義

觀察者模式定義了一種一對多的依賴關係,讓多個觀察者物件同時監聽某一個主題物件。這個主題物件在狀態上發生變化時,會通知所有觀察者物件,使它們能夠自動更新自己。

觀察者模式的結構

當觀察者模式和回撥機制遇上Android原始碼

觀察者模式所涉及的角色有:

  • 抽象主題(Subject)角色:抽象主題角色把所有對觀察者物件的引用儲存在一個聚集(比如ArrayList物件)裡,每個主題都可以有任何數量的觀察者。抽象主題提供一個介面,可以增加和刪除觀察者物件,抽象主題角色又叫做抽象被觀察者(Observable)角色。
  • 具體主題(ConcreteSubject)角色:將有關狀態存入具體觀察者物件;在具體主題的內部狀態改變時,給所有登記過的觀察者發出通知。具體主題角色又叫做具體被觀察者(Concrete Observable)角色。
  • 抽象觀察者(Observer)角色:為所有的具體觀察者定義一個介面,在得到主題的通知時更新自己,這個介面叫做更新介面。
  • 具體觀察者(ConcreteObserver)角色:儲存與主題的狀態自恰的狀態。具體觀察者角色實現抽象觀察者角色所要求的更新介面,以便使本身的狀態與主題的狀態 像協調。如果需要,具體觀察者角色可以保持一個指向具體主題物件的引用。

實現

抽象主題角色類

具體主題角色類

抽象觀察者角色類

具體觀察者角色類

測試類

觀察者的兩種實現方式

  • Push主題物件向觀察者推送主題的詳細資訊,不管觀察者是否需要,推送的資訊通常是主題物件的全部或部分資料。
  • Pull主題物件在通知觀察者的時候,只傳遞少量資訊。如果觀察者需要更具體的資訊,由觀察者主動到主題物件中獲取,相當於是觀察者從主題物件中拉資料。一般這種模型的實現中,會把主題物件自身通過update()方法傳遞給觀察者,這樣在觀察者需要獲取資料的時候,就可以通過這個引用來獲取了。

兩種方式的比較

  • Push模型是假定主題物件知道觀察者需要的資料;而Pull模型是主題物件不知道觀察者具體需要什麼資料,沒有辦法的情況下,乾脆把自身傳遞給觀察者,讓觀察者自己去按需要取值。
  • Push模型可能會使得觀察者物件難以複用,因為觀察者的update()方法是按需要定義的引數,可能無法兼顧沒有考慮到的使用情況。這就意味著出現新情況的時候,就可能提供新的update()方法,或者是乾脆重新實現觀察者;而Pull模型就不會造成這樣的情況,因為Pull模型下,update()方法的引數是主題物件本身,這基本上是主題物件能傳遞的最大資料集合了,基本上可以適應各種情況的需要。

回撥機制和觀察者模式

Android中有非常多的地方使用了回撥機制,例如Activity的生命週期、按鈕的點選事件、執行緒的run()方法等。

下面是回撥的基本模型:

}

這樣看來,回撥機制和觀察者模式是一致的,區別是觀察者模式裡面目標類維護了所有觀察者的引用,而回撥裡面只是維護了一個引用。

Android中的觀察者模式

Android中大量的使用了觀察者模式,Framework層裡面的事件驅動都是基於觀察者模式實現的。另外在Framework層裡面的各種服務在資料變更的時候,也是通過觀察者模式實現上層資料更新的。像View的Listener監聽、GPS位置資訊監聽、BroadcastReceiver等都是基於觀察者模式實現的。下面我們說一說ListView中的觀察者模式是如何實現的,RecyclerView大同小異,感興趣的可以自己研究下。

Listview的notifyDataSetChanged()

我們先來看下listview部分觀察者模式的結構

當觀察者模式和回撥機制遇上Android原始碼

其中為了方便研究關係,我們省略了Adapter部分的一些類的關係。接下來我們看下具體呼叫關係。

首先當我們資料改變的時候我們會呼叫adapter的notifyDataSetChanged()方法。

根據上述程式碼我們可以定位到mDataSetObservable.notifyChanged()方法。

我們看到,呼叫notifyChanged()方法,會去遍歷mObservers,呼叫所有觀察者的onchange()方法。

那麼問題來了,我們的觀察者物件是什麼時候新增進去的呢?我們去看下ListView第一次和BaseAdapter產生關聯的地方,也就是setAdapter(ListAdapter adapter)方法。

這樣我們的四個角色就全了,Observable—>Subject;DataSetObservable—>Concrete Subject;DataSetObserver—>Observer;AdapterDataSetObserver—>Concrete Observer。然後我們註冊的地方也找到了。

最後就剩下我們的資料是如何重新整理這一個問題了。AdapterDataSetObserver定義在ListView的父類AbsListView中,它又繼承自AbsListView的父類AdapterView的AdapterDataSetObserver。

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原始碼 當觀察者模式和回撥機制遇上Android原始碼

相關文章