編輯:關於Android編程
上一篇博客跟大家分享了Android源碼中的裝飾者模式,有點意猶未盡,今天跟大家分享下Android中的觀察者模式,順便說一說觀察者模式和回調機制的關系,歡迎大家拍磚。
觀察者模式定義了一種一對多的依賴關系,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態上發生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。
觀察者模式所涉及的角色有:
抽象主題(Subject)角色:抽象主題角色把所有對觀察者對象的引用保存在一個聚集(比如ArrayList對象)裡,每個主題都可以有任何數量的觀察者。抽象主題提供一個接口,可以增加和刪除觀察者對象,抽象主題角色又叫做抽象被觀察者(Observable)角色。
具體主題(ConcreteSubject)角色:將有關狀態存入具體觀察者對象;在具體主題的內部狀態改變時,給所有登記過的觀察者發出通知。具體主題角色又叫做具體被觀察者(Concrete Observable)角色。
抽象觀察者(Observer)角色:為所有的具體觀察者定義一個接口,在得到主題的通知時更新自己,這個接口叫做更新接口。
抽象主題角色類
public abstract class Subject {
/**
* 用來保存注冊的觀察者對象
*/
private List list = new ArrayList();
/**
* 注冊觀察者對象
* @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
主題對象在通知觀察者的時候,只傳遞少量信息。如果觀察者需要更具體的信息,由觀察者主動到主題對象中獲取,相當於是觀察者從主題對象中拉數據。一般這種模型的實現中,會把主題對象自身通過update()方法傳遞給觀察者,這樣在觀察者需要獲取數據的時候,就可以通過這個引用來獲取了。
兩種方式的比較
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 {
/**
* 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)方法。
@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。
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組件,我們可以把這件實現思路應用到哪裡?
幀動畫:是指多張圖片快速切換先看一下實現的效果 實現方式第一步:使用Android Studio創建一個Android工程,並且在drawable
對於android開發來說自定義View還是一個比較重要的技能,所以在這裡寫一篇自定義View入門的文章,也是實現一個相對簡單的隨機產生驗證碼的功能: 自定義View主要
Android系統的動態鏈接工具是/system/bin/linker(一般的Linux系統是ld.so),雖然名字不同,但是基本的動態鏈接過程是類似的。需
前言:View框架寫到第六篇,發現前面第二篇竟然沒有,然後事情是在微信公眾號發了,忘記在博客上更新,所以關注微信公眾號的應該都看過了,趁今天有時間遂補上。(PS:本篇文章