一、观察者模式介绍
观察者模式是一个使用频率非常高的模式,它最常用的地方是 GUI 系统、订阅–发布系统。因为这个模式的一个重要作用就是解耦,将被观察者和观察者解耦,使得他们之间的依赖性更小,甚至做到毫无依赖。以 GUI 系统来说,应用的 UI 具有易变性,尤其是前期随着业务的改变或者产品的需求修改,应用界面也会经常性变化,但是业务逻辑基本变化不大,此时, GUI 系统需要一套机制来应对这种情况,使得 UI 层与具体的业务逻辑解耦,观察者模式此时就派上用场了。
二、观察者模式的定义
定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。
三、观察者模式的使用场景
- 关联行为场景,需要注意的是,关联行为是可拆分的,而不是“组合”关系;
- 事件多级触发场景;
- 跨系统的消息交换场景,如消息队列、事件总线的处理机制。
四、观察者模式的 UML 类图
UML 类图如上图所示:
角色介绍:
- Subject: 抽象主题,也就是被观察(Observable)的角色,抽象主题角色把所有观察者对象的引用保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
- ConcreteSubject: 具体主题,该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发出通知,具体主题角色又叫做具体被观察者(ConcreteObservable)角色。
- Observer:抽象观察者,该角色是观察者的抽象类,它定义了一个更新接口,使得在得到主题的更改通知时更新自己。
- ConcreteObserver: 具体的观察者,该角色实现抽象观察者角色所定义的更新接口,以便在主题的状态发生变化时更新自身的状态。
五、观察者模式的简单实现
下面让我们来简单模拟一下开发技术前线的发布–订阅过程:
//程序员是观察者
public class Coder implements Observer {
public String name;
public Coder(String aName) {
name = aName;
}
@Override
public void update(Observable o, Object arg) {
System.out.println("Hi, " + name + ", DevTechFrontier 更新啦,内容 :" + arg);
}
@Override
public String toString() {
return "码农 :" + name;
}
}
//DevTechFrontier 即开发技术前线,这个网站是被观察者角色,
//当他有更新时所有的观察者(这里是程序要)都会接收到相应的通知
public class DevTechFrontier extends Observable {
public void postNewPublication(String content) {
//标识状态或者内容发生改变
setChanged();
//通知所有观察者
notifyObservers(content);
}
}
//测试代码
public class Test {
public static void main(String[] args) {
//被观察的角色
DevTechFrontier devTechFrontier = new DevTechFrontier();
//观察者
Coder mrsimple = new Coder("mr.simple");
Coder coder1 = new Coder("coder-1");
Coder coder2 = new Coder("coder-2");
Coder coder3 = new Coder("coder-3");
//将观察者注册到可观察对象的观察者列表中
devTechFrontier.addObserver(mrsimple);
devTechFrontier.addObserver(coder1);
devTechFrontier.addObserver(coder2);
devTechFrontier.addObserver(coder3);
//发布消息
devTechFrontier.postNewPublication("新的一期开发技术前线周报发布啦!");
}
}
输出结果:
可以看到所有订阅了开发技术前线的用户都收到了更新消息,一对多的订阅–发布系统就完成了。
Observer 和 Observable 是 JDK 中的内置类型,可见观察者模式是非常重要的,这里 Observer 是抽象的观察者角色,Coder 扮演的是具体观察者的角色:Observable 对应的是抽象主题角色,DevTechFrontier 则是具体的主题角色。Coder 是具体的观察者,它们订阅了 DevTechFrontier 这个具体的可观察对象,当 DevTechFrontier 有更新时,会遍历所有观察者(这里是Coder), 然后给这些观察者发布一个更新的消息,即调用 Coder 中的 update 方法,这样就达到了一对多的通知功能。在这个过程中,通知系统都是依赖 Observer 和 Observable 这些抽象类,因此,对于 Coder 和 DevTechFrontier 完全没有耦合,保证了订阅系统的灵活性、可扩展性。
六、Android源码分析
ListView 是 Android 中最重要的空间之一,而 ListView 最重要的一个功能就是 Adapter , 后面我们会分析 Adapter 模式。通常,在我们往 ListView 添加数据后,都会调用 Adapter 的 notifyDataSetChanged() 方法,这是为什么呢?今天就来揭开它的神秘面纱。
第一步我们就跟进这个方法 notifyDataSetChanged, 这个方法定义在 BaseAdapter 中,具体代码如下:
/**
* 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();
}
}
一看 BaseAdapter 代码就大体有了这么一个认识:BaseAdapter 是一个观察者模式!那么 BaseAdapter 是如何运作的?这些观察者又是什么呢?我们一步一步来分析。
我们先到 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.
* 调用每个观察者的onChanged函数来通知他们被观察者发生了变化
*/
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.
// 调用所有观察者的onChanged方法
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
//代码省略
}
这个代码很简单,就是在 mDataSetObservable.notifyChanged() 中遍历所有观察者,并且调用它们的 onChanged 方法,从而告知观察者发生了变化。
那么这些观察者是从哪里来的呢?其实这些观察者就是 ListView 通过 setAdapter 方法设置 Adapter 产生的,我们看看相关代码:
public void setAdapter(ListAdapter adapter) {
//如果已经有了一个 Adapter,那么先注销该 Adapter 对应的观察者
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
//代码省略
// AbsListView#setAdapter will update choice mode states.
super.setAdapter(adapter);
if (mAdapter != null) {
mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
mOldItemCount = mItemCount;
//获取数据的数量
mItemCount = mAdapter.getCount();
checkFocus();
//注意这里:创建一个数据集观察者
mDataSetObserver = new AdapterDataSetObserver();
//将这个观察者注册到 Adapter 中,实际上是注册到 DataSetObservable中
mAdapter.registerDataSetObserver(mDataSetObserver);
mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
//代码省略
} else {
//代码省略
}
requestLayout();
}
从程序中可以看到,在设置 Adapter 时会构建一个 AdapterDataSetObserver, 这就是上面所说的观察者,最后,将这个观察者注册到 Adapter 中,这样我们的被观察者,观察者都有了。这个时候可能你有点不明白,AdapterDataSetObserver 是什么?他是如何运作的?那么就先来看看 AdapterDataSetObserver,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();
}
//代码省略
public void clearSavedState() {
mInstanceState = null;
}
}
到这里我们就知道了,当 ListView 的数据发生变化时,调用 Adapter 的 notifyDataSetChanged 函数,这个函数又会调用 DataSetObservable 的 nofifyChanged 函数,这个函数会调用所有观察者(AdapterDataSetObserver)的 onChanged 方法,在 onChanged 函数中又会调用 ListView 重新布局的函数使得 ListView 刷新界面,这就是一个观察者模式!
最后,我们再整理一下这个过程,AdapterView 中有一个内部类 AdapterDataSetObserver, 在 ListView 中设置 Adapter 时会构建一个 AdapterDataSetObserver,并且注册到 Adapter 中,这就是一个观察者。而 Adapter 中包含一个数据集可观察者 DataSetObservable , 在数据数量发生变更时,开发者动手调用 Adapter.notifyDataSetChanged, 而 notifyDataSetChanged 实际上会调用 DataSetObservable 的 notifyChanged 函数,该函数会遍历所有观察者的 onChanged 函数。在 AdapterDataSetObserver 的 onChanged 函数中会获取 Adapter 中数据集的新数量,然后调用 ListView 的 requestLayout() 方法重新进行布局,更新用户界面。
七、观察者模式的深入拓展
BroadcastReceiver 是 Android 的四大组件之一,它作为应用内、进程间的一种重要通信手段,能够将某个消息通过广播的形式传递给它注册的对应广播接收器的对象,接收对象需要通过 Context 的 registerReceiver 函数注册到 AMS(ActivityManagerService) 中,当通过 sendBroadcast 发送广播时,所有注册了对应的 IntentFilter 的 BroadcastReceiver 对象就会接收到这个消息,BroadcastReceiver 的 onReceiver 方法就会被调用,这就是一个典型的发布–订阅模式,也就是我们的观察者模式。广播接收器的注册调用时序图如下:
例如,我们在 MainActivity 中注册一个广播接收器,示例代码如下:
public class MainActivity extends Activity {
@Override
protected void onResume() {
super.onResume();
IntentFilter updateFilter = new IntentFilter("info.update");
registerReceiver(updateReceiver, updateFilter);
}
BroadcastReceiver updateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(getApplicationContext(), "update", Toast.LENGTH_SHORT).show();
}
};
//代码省略
}
我们在 MainActivity 的 onResume 里注册了一个只接收 Action 为 “info.update” 的广播接收器,应用内的其他地方发布一个 Action 为 “info.update” 的广播时,就会触发 updateReceiver 的 onReceiver 函数。下面我们就来一步一步分析广播接收器的基本原理。
我们发现 registerReceiver 函数并不是在 Activity 中实现,因此,我们把目标移向 Activity 的父类 ContextWrapper, registerReceiver函数如下:
public class ContextWrapper extends Context {
Context mBase;
//代码省略
@Override
public Intent registerReceiver(
BroadcastReceiver receiver, IntentFilter filter,
String broadcastPermission, Handler scheduler) {
return mBase.registerReceiver(receiver, filter, broadcastPermission,
scheduler);
}
//代码省略
}
这里的成员变量 mBase, mBase 对象是一个 ContextImpl 类的实例。继续转移到 ContextImpl 的 registerReceiver 函数,具体代码如下:
注意:想查看ContextImpl的源码时,无法找到ContextImpl这个类。由于ContextImpl是抽象类Context的实现类。然而查看Context类的继承结构,如下图:没有发现ContextImpl。
后来查到原因是:这个文件是保护文件,就是注解了是内部保护文件,所以在eclipse,Androidstudio中都是不显示的。所以可以去SDk的安装目录中的sources文件夹中直接找那个Java文件,/android-sdk/sources/android-19/android/app/ContextImpl.java。
class ContextImpl extends Context {
// 代码省略
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
return registerReceiver(receiver, filter, null, null);
}
private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
IntentFilter filter, String broadcastPermission,
Handler scheduler, Context context) {
IIntentReceiver rd = null;
if (receiver != null) {
if (mPackageInfo != null && context != null) {
if (scheduler == null) {
//获取 Handler 来投递消息
scheduler = mMainThread.getHandler();
}
// 获取 IntentReceiver 对象,通过它与 AMS 交互,并且通过 Handler 传递消息
rd = mPackageInfo.getReceiverDispatcher(
receiver, context, scheduler,
mMainThread.getInstrumentation(), true);
} else {
//代码省略
//...
}
}
try {
// 调用 ActivityManagerNative 的 registerReceiver
return ActivityManagerNative.getDefault().registerReceiver(
mMainThread.getApplicationThread(), mBasePackageName,
rd, filter, broadcastPermission, userId);
} catch (RemoteException e) {
return null;
}
}
// 代码省略
}
注册广播接收器的函数调用最终进入到了 ContextImpl 的 registerReceiverInternal 这个函数,这里的成员变量 mPackageInfo 是一个 LoadedApk 实例,他是用来负责处理广播的接收。参数 broadcastPermission 和 scheduler 都为 null , 而参数 context 是上面的函数通过调用函数 getOuterContext 得到的,这里它指向的是 MainActivity.
由于条件 mPackageInfo != null 和 context != null 都成立,而且条件 scheduler == null 也成立,于是就调用 mMainThread.getHandler() 来获得一个 Handler , 这个 Handler 是用来分发 ActivityManagerService 发送过的广播,这里的成员变量 mMainThread 是一个 ActivityThread 实例,我们先来看看 ActivityThread.getHandler 函数的实现,然后再回过头来继续分析 ContextImpl.registerReceiverInternal 函数:
public final class ActivityThread{
final H mH = new H();
private final class H extends Handler{
public void handleMessage(Message msg){
switch(msg.what){
//代码省略
}
}
//代码省略
}
final Handler getHandler(){
return mH;
}
//代码省略
}
有了这个 Handler 之后,就可以分发消息给应用程序处理了。
再回到上一步的 ContextImpl.registerReceiverInternal 函数中,它通过 mPackageInfo.getReceiverDispatcher 函数获得一个 IIntentReceiver 接口对象 rd, 这是一个 Binder 对象,接下来会把它传给 ActivityManagerService, ActivityManagerService 在收到相应的广播时,就是通过这个 Binder 对象来通知 MainActivity 来接收的。
八、总结
观察者模式主要的作用就是对象解耦,将观察者与被观察者完全隔离,只依赖于 Observer 和 Observer抽象,例如,ListView 就是运用了 Adapter 和观察者模式使得它的可扩展性、灵活性非常强,而耦合度却很低,这是设计模式在 Android 源码中优秀运用的典范。那么为什么 Android 架构师们会这样设计 ListView, 他们如何达到底耦合、高灵活性呢?广播接收器和事件总线在观察者模式上有什么相似之处,又有什么不同?
优点:
1、观察者和被观察者之间是抽象耦合,应对业务变化;
2、增强系统灵活性,可扩展性。
缺点:
在应用观察者模式时需要考虑一下开发效率和运行效率问题,程序中包括一个被观察者、多个观察者、开发和调试等内容会比较复杂,而且在 Java 中消息的通知默认是顺序执行,一个观察者卡顿,会影响整体的执行效率,在这种情况下,一般考虑采用异步的方式。