
前言
观察者模式是我们开发工作中经常使用的开发模式。Android 源码中也有很多地方用到此模式。比如:ListView、ContentProvider 和 Broadcast 等等。本文将会介绍观察者模式、实现一个观察者模式并结合 Android 源码进行分析。
定义
定义对象间的一种一个对多的依赖关系,当一个对象的状态发送改变时,所以依赖于它的对象都得到通知并被自动更新。
介绍
观察者模式又被称作发布/订阅模式。即 1 人发布,N 人订阅。观察者模式主要用来解耦,将被观察者和观察者解耦,让他们之间者依赖关系很小,甚至不存在依赖。它最用的地方是 GUI 系统、订阅——发布系统。举个例子,我司开发的一个 App 左上角状态栏需要实时显示闹钟的状态,若存在设定好的闹钟则显示闹钟图标,否则不显示。因为存储闹钟使用的 ContentProvider,所以可以使用一个监听器监听闹钟数据是否发生变化,若发生变化就判断当前闹钟状态并显示或关闭图标。这样便实现了实时显示闹钟状态的功能。
UML 类图

手工实现
- 创建观察者
实现抽象观察者 Observer 中的方法,这里创建一个 Coder 类,并定义收到通知后的动作:
/**
* 程序员是观察者
*
*/
public class Coder implements Observer{
public String name;
public Coder(String mName) {
name = mName;
}
@Override
public void update(Observable arg0, Object arg1) {
// TODO Auto-generated method stub
System.out.println("嘿," + name + ",您的快递到了");
}
public String toString(String name) {
return "程序员:"+name;
}
}
- 创建被观察者 实现 Observable 方法,也就是快递员,快递到了会通知程序员们来拿快递:
/**
* Courier 即快递员,当他到公司时通知所有观察者(有快递的程序员)拿快递
* @author Rickon
*
*/
public class Courier extends Observable {
public void postNewExpress(String content) {
//标识状态或者内容发生改变(此处为快递到了)
setChanged();
//通知所有观察者
notifyObservers(content);
}
}
- 测试
public class Test {
public static void main(String[] args) {
//被观察者
Courier courier = new Courier();
//观察者
Coder coder1 = new Coder("程序员张三");
Coder coder2 = new Coder("程序员李四");
Coder coder3 = new Coder("程序员王二麻子");
//将观察者注册到被观察者的观察者列表中
courier.addObserver(coder1);
courier.addObserver(coder2);
courier.addObserver(coder3);
//快递到了
courier.postNewExpress("快递到啦!");
}
}
- 输出结果
嘿,程序员王二麻子,您的快递到了
嘿,程序员李四,您的快递到了
嘿,程序员张三,您的快递到了
总结 上述代码就实现了一个简单地观察者模式,使用的是 JDK 内部内置的 Observable(抽象被观察者),Observer(抽象观察者)。JDK 内置观察者模式也说明了观察者模式应用的广泛与重要性。
应用场景
- 事件多级触发场景;
- 当一个对象必须通知别的对象,而它又不能假定对象是谁;
- 跨系统的消息交换场景,如消息队列、事件总线的处理机制。
优缺点
优点
- 观察者与被观察者之间是抽象耦合,可以很好地应对业务变化;
- 增强系统灵活性、可扩展性。
缺点
- 开发过程中可能会出现一个被观察者、多个观察者的情况,开发和调试会变得比较复杂。除此之外,观察者太多的话收到通知需要消耗更长的时间。
Android 中的观察者模式
- ListView 深入解析
ListView 是 Android 中很常用的控件,我们在使用 ListView 添加数据后会调用 Adapter 的 notifyDataSetChanged()方法,这是为啥呢?下面我们来一探究竟!
我们发现 notifyDataSetChanged,这个方法是在 BaseAdapter 中定义的,具体带么如下:
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();
}
//省略部分代码
}
BaseAdapter 看起来是观察者模式,那么到底 BaseAdapter 是怎么运行的呢?又是如何实现的观察者模式,让我们继续分析。
首先看看 mDataSetObservable.notifyChanged() 方法:
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) {
// 遍历调用每个观察者的 onChanged() 方法来通知被观察者发生变化
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
//省略部分代码
}
这里很容易看到此方法是遍历所有观察者,并且调用所有观察者的 onChnged() 方法,从而告知观察者发生了变化。那么这些观察者是怎么设置的呢?其实这些观察者就是 ListVIew 通过 setAdapter 方法产生的,我们看看这部分代码:
@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 中,这样我们的被观察者和观察者就都构建好了。那么 AdapterDataSetObserver 到底是怎么运行的呢?它是定义在 ListView 的父类 AbsListView 中,代码如下:
class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
@Override
public void onChanged() {
super.onChanged();
if (mFastScroll != null) {
mFastScroll.onSectionsChanged();
}
}
@Override
public void onInvalidated() {
super.onInvalidated();
if (mFastScroll != null) {
mFastScroll.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();
}
//省略部分代码
}
源码分析到这里就很明显了,当我们调用 Adapter 的 notifyDataSetChanged 方法,这个方法又会调用 DataSetObservable 的 notifyDataSetChanged 方法,而这个方法会调用所有观察者的 onChanged 方法,在 onChanged 方法中则是会调用 ListView 的重新布局方法刷新UI,这就是一个完整的观察者模式。
ListView 观察者模式实现总结: 我重新概述这一过程:Adapter 中包含一个数据集可观察者 DataSetObservable,在数据数量发生变更时,开发者手动调用 Adapter.notifyDataSetChanged,实际上调用的则是 DataSetObservable 的 onChanged 方法,该方法会遍历所有观察者的 onChanged 方法。在 AdapterDataSetObserver 的 onChanged 函数中会获取 Adapter 中数据集的新数量,然后调用 ListView 的 requestLayout() 方法重新布局并更新 UI。
- ContentProvider 文章的开始有提到使用 ContentProvider 的过程中也用到了观察者模式。首先创建一个观察者对象,代码如下:
ContentObserver alarmObserver = new ContentObserver(new Handler()) {
//创建观察者对象
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
//执行业务代码
}
@Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
}
};
注册观察者:
mContext.getContentResolver().registerContentObserver(url, true, alarmObserver);
看到此注册方法有三个参数分别代表什么意义呢:
- uri:针对对有变化的感兴趣进行监听的URI;
- notifyForDescendents:true表示以uri前缀开始的任何变化都进行通知;false表示完全匹配才进行通知;
- observer:IContentObserver接口,提供了一个方法onChange,变化发生时Cursor需要更新时调用
这样就通过指定Uri可以仅对数据库中感兴趣的数据有变化时,进行监听。具体源码就不再进行分析,大家感兴趣可以自行去分析源码,观察者模式是万变不离其宗的。