场景描述
在 Java 多线程下载框架中,我们需要知道下载状态比如暂停下载,恢复下载,取消下载等状态的通知,而且不仅仅是更新当前页面,在任意页面都能接收到状态变化的更新,所以这里要用到观察者模式。
关于设计模式的详细介绍,我这里有几本电子书籍推荐 ,点作者头像,然后私信:设计模式,即可获取下载方式。
那么什么是观察者模式(Observer)?
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,让他们能够自动更新自己。
举一个例子来说明,牛奶送奶站就是主题,订奶客户为监听者,客户从送奶站订阅牛奶后,会每天收到牛奶。如果客户不想订阅了,可以取消,以后就不会收到牛奶。
为什么要使用观察者模式?为什么不用广播, EventBus ,RxBus呢?
广播的劣势
广播是相对消耗时间、空间最多的一种方式,但是大家都知道,广播是四大组件之一,许多系统级的事件都是通过广播来通知的,比如说网络的变化、电量的变化,短信发送和接收的状态,所以,如果与android系统进行相关的通知,还是要选择本地广播;在BroadcastReceiver的 onReceive方法中,可以获得Context 、 intent 参数,这两个参数可以调用许多的sdk中的方法。
应用发送某个广播时,系统会将广播中的intent与系统中所有注册的BroadcastReceiver进行匹配,如果能匹配成功则调用相关的onReceive函数进行处理。这里存在2个问题: a、性能问题 。每个广播都会与所有BroadcastReceiver进行匹配。 b、安全问题 。广播发出去的数据可能被其他应用监听。
因此广播相对于其他的方式而言,广播是重量级的,消耗资源较多的方式。他的优势体现在与sdk连接紧密,如果需要同 android 交互的时候,广播的便捷性会抵消掉它过多的资源消耗,但是如果不同android交互,或者说,只做很少的交互,使用广播是一种浪费。
为什么不使用EventBus,RxBus?
这里不对二者的优缺点进行分析,各有各的好处,看实际需要。因为我们是封装自己的多线程下载框架,所以不能依赖第三方的一些库,因为你不知道用户会使用 RxJava 还是EventBus。比如你这里用到了RxJava的库,而别人使用你的SDK的之前就集成了EventBus,那不是又要集成RxJava?或者说你这里使用的是Rx1.0,而用户使用的是Rx2.0,所以为了避免不必要的麻烦,我们尽量不被依赖外部资源。
为什么使用观察者模式?
松耦合,观察者增加或删除无需修改主题的代码,只需调用主题对应的增加或者删除的方法即可。
主题只负责通知观察者,但无需了解观察者如何处理通知。举个例子,送奶站只负责送递牛奶,不关心客户是喝掉还是洗脸。
观察者只需等待主题通知,无需观察主题相关的细节。还是那个例子,客户只需关心送奶站送到牛奶,不关心牛奶由哪个快递人员,使用何种交通工具送达。
具体实践
一、创建一个Observable
public class DataChanger extends Observable{
/** * 对外提供一个单列引用,用于注册和取消注册监听 */private static DataChanger mDataChanger;
public static synchronized DataChanger getInstance(){
if (null == mDataChanger){
mDataChanger = new DataChanger();
}
return mDataChanger;
}
public void notifyDataChange(DownloadEnty mDownloadEnty){
//Marks this <tt>Observable</tt> object as having been changed
setChanged();
//通知观察者 改变的内容 也可不传递具体内容 notifyObservers()
notify observer s(mDownloadEnty);
}}
主要用于提供注册和删除观察者对象以及通知更新的方法 ,此处直接继承的是Java提供的Observable,其内部已经实现了
* addObserver * deleteObserver * notifyObservers() * notifyObservers(Object arg) * deleteObservers() * setChanged() * clearChanged() * hasChanged() * countObservers()
当内容变化的时候,使用setChanged()和notifyObservers(mDownloadEnty)通知观察者。
二、setChanged和notifyObservers为何物
上述代码中存在这样一处代码setChanged();, 如果在通知之前没有调用这个方法,观察者是收不到通知的 ,这是为什么呢?
这里我们看一下setChanged的源码
protected synchronized void setChanged() { changed = true; }
此处把boolen变量changed改为了true
再看notifyObservers源码
public void notifyObservers(Object arg) { /* * a temporary array buffer, used as a snapshot of the state of * current Observers. */Observer[] arrLocal; synchronized (this) { if (!hasChanged()) return; arrLocal = observers.toArray(new Observer[observers.size()]); clearChanged(); } for (int i = arrLocal.length-1; i>=0; i--) arrLocal[i].update(this, arg); }
可以看到
if (!hasChanged()) return;
所以这就是为什么通知更新前一定要调用setChanged的原因
但是为什么要加入这样一个开关呢?可能原因大致有三点
1.筛选有效通知 ,只有有效通知可以调用setChanged。比如,我的WX朋友圈一条状态,好友A点赞,后续该状态的点赞和评论并不是每条都通知A,只有A的好友触发的操作才会通知A。
2.便于撤销通知操作 ,在主题中,我们可以设置很多次setChanged,但是在最后由于某种原因需要取消通知,我们可以使用clearChanged轻松解决问题。
3.主动权控制 ,由于setChanged为protected,而notifyObservers方法为public,这就导致存在外部随意调用notifyObservers的可能,但是外部无法调用setChanged,因此真正的控制权应该在主题这里。
三、创建Observer
public abstract class DataWhatcher implements Observer {
@ Override
public void update(Observable observable, Object data) {
if (data instanceof DownloadEnty){
notifyDataChange(data);
}
}
public abstract void notifyDataChange(Object data);}
为那些在目标发生改变时需获得通知的类定义个更新的接口,这里对接口再进行了判断,对外提供了notifyDataChange抽象方法,外部可在此抽象方法在获取到更新的回调以及更新的对象。
四、添加和取消观察者
private DataWhatcher dataWhatcher = new DataWhatcher() { @Override public void notifyDataChange(Object data) { downloadEnty = (DownloadEnty) data; if (downloadEnty.downloadStatus == DownloadEnty.DownloadStatus.downloading){ LogUtil.e("download","===notifyDataChange===downloading"+downloadEnty.currentLenth); }else if (downloadEnty.downloadStatus == DownloadEnty.DownloadStatus.downloadcomplete){ LogUtil.e("download","===notifyDataChange===downloadcomplete"); }else if (downloadEnty.downloadStatus == DownloadEnty.DownloadStatus.downloadcansel){ downloadEnty = null; LogUtil.e("download","===notifyDataChange===downloadcansel"); }else if (downloadEnty.downloadStatus == DownloadEnty.DownloadStatus.downloadpause){ LogUtil.e("download","===notifyDataChange===downloadpause"); }else{ LogUtil.e("download","===notifyDataChange===下载进度"+downloadEnty.currentLenth); } } };
@Override
protected void onResume() {
super.onResume();
DownloadManager. getInstance ().addObserve(dataWhatcher);
}
@Override
protected void onStop() {
super.onStop();
DownloadManager.getInstance().removeObserve(dataWhatcher);
}
五、运行效果
六、观察者模式使用总结
从上面可以看出,实际上观察者和被观察者是通过接口回调来通知更新的,首先创建一个观察者(数据监听)实例并实现数据变化接口,通过注册监听将实例传入被观察者(数据变化),当被观察者数据变化的时候使用该实例的接口回传状态。了解原理之后,我们可以利用观察者模式自定义实现。
拿WXGZH来举例,假设WX用户就是观察者,WXGZH是被观察者,有多个的WX用户关注了 陈守印同学 这个GZH,当这个GZH更新时就会通知这些订阅的WX用户。我们来看看用代码如何实现:
1.抽象观察者(Observer)
public interface Observer {
public void update(String message );}
2.具体观察者(ConcrereObserver)
WX用户是观察者,里面实现了更新的方法:
里面定义了一个更新的方法:
public class WeixinUser implements Observer { //WX用户名字 private String name; public WeixinUser(String name) { this.name = name; } @Override public void update(String message) { System.out.println(name + ":" + message); }}
3.抽象被观察者(Subject)
抽象主题,提供了attach、detach、notify三个方法:
public interface Subject { /** * 增加订阅者 * @param observer */public void attach(Observer observer); /** * 删除订阅者 * @param observer */public void detach(Observer observer); /** * 通知订阅者更新消息 */public void notify(String message);}
4.具体被观察者(ConcreteSubject)
WXGZH是具体主题(具体被观察者),里面存储了订阅该GZH的WX用户,并实现了抽象主题中的方法:
public class SubscriptionSubject implements Subject { //储存订阅GZH的WX用户 private List<Observer> weixinUserlist = new ArrayList<Observer>(); @Override public void attach(Observer observer) { weixinUserlist.add(observer); } @Override public void detach(Observer observer) { weixinUserlist.remove(observer); } @Override public void notify(String message) { for (Observer observer : weixinUserlist) { observer.update(message); } }}
5.客户端调用
public class Client { public static void main(String[] args) { SubscriptionSubject mSubscriptionSubject=new SubscriptionSubject(); //创建WX用户 WeixinUser user1=new WeixinUser("陈守印同学GZH粉丝A"); WeixinUser user2=new WeixinUser("陈守印同学GZH粉丝B"); WeixinUser user3=new WeixinUser("陈守印同学GZH粉丝C"); WeixinUser user4=new WeixinUser("陈守印同学GZH粉丝D"); //订阅GZH mSubscriptionSubject.attach(user1); mSubscriptionSubject.attach(user2); mSubscriptionSubject.attach(user3); mSubscriptionSubject.attach(user4); //GZH更新发出消息给订阅的WX用户 mSubscriptionSubject.notify("陈守印同学GZH的文章更新啦"); }}
6.运行结果
陈守印同学GZH粉丝A:陈守印同学GZH的文章更新啦 陈守印同学GZH粉丝B:陈守印同学GZH的文章更新啦 陈守印同学GZH粉丝C:陈守印同学GZH的文章更新啦 陈守印同学GZH粉丝D:陈守印同学GZH的文章更新啦
7.观察者模式优缺点
解除耦合,让耦合的双方都依赖于抽象,从而使得各自的变换都不会影响另一边的变换。
如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
公号后台回复”设计模式”,获取设计模式书籍
上一篇: Java多线程下载01:多线程的好处以及断点续传原理