场景描述
在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()
notifyObservers(mDownloadEnty);
}
}
主要用于提供注册和删除观察者对象以及通知更新的方法,此处直接继承的是Java提供的Observable,其内部已经实现了
* addObserver
* deleteObserver
* notifyObservers()
* notifyObservers(Object arg)
* deleteObservers()
* setChanged()
* clearChanged()
* hasChanged()
* countObservers()
当内容变化的时候,使用setChanged()和notifyObservers(mDownloadEnty)通知观察者。
二、setChanged和notifyObservers为何物
上述代码中存在这样一处代码setChanged();,如果在通知之前没有调用这个方法,观察者是收不到通知的,这是为什么呢?
这里我们看一下setChanged的源码
/**
* Marks this <tt>Observable</tt> object as having been changed; the
* <tt>hasChanged</tt> method will now return <tt>true</tt>.
*/
protected synchronized void setChanged() {
changed = true;
}
此处把boolen变量changed改为了true
再看notifyObservers源码
/**
* If this object has changed, as indicated by the
* <code>hasChanged</code> method, then notify all of its observers
* and then call the <code>clearChanged</code> method to indicate
* that this object has no longer changed.
* <p>
* Each observer has its <code>update</code> method called with two
* arguments: this observable object and the <code>arg</code> argument.
*
* @param arg any object.
* @see java.util.Observable#clearChanged()
* @see java.util.Observable#hasChanged()
* @see java.util.Observer#update(java.util.Observable, java.lang.Object)
*/
public void notifyObservers(Object arg) {
/*
* a temporary array buffer, used as a snapshot of the state of
* current Observers.
*/
Observer[] arrLocal;
synchronized (this) {
/* We don't want the Observer doing callbacks into
* arbitrary Observables while holding its own Monitor.
* The code where we extract each Observable from
* the ArrayList and store the state of the Observer
* needs synchronization, but notifying observers
* does not (should not). The worst result of any
* potential race-condition here is that:
*
* 1) a newly-added Observer will miss a
* notification in progress
* 2) a recently unregistered Observer will be
* wrongly notified when it doesn't care
*/
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。比如,我的微信朋友圈一条状态,好友A点赞,后续该状态的点赞和评论并不是每条都通知A,只有A的好友触发的操作才会通知A。
2.便于撤销通知操作,在主题中,我们可以设置很多次setChanged,但是在最后由于某种原因需要取消通知,我们可以使用clearChanged轻松解决问题。
3.主动权控制,由于setChanged为protected,而notifyObservers方法为public,这就导致存在外部随意调用notifyObservers的可能,但是外部无法调用setChanged,因此真正的控制权应该在主题这里。
三、创建Observer
/**
* Created by chenshouyin on 2017/10/25.
* 我的博客:http://blog.csdn.net/e_inch_photo
* 我的Github:https://github.com/chenshouyin
*/
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);
}
五、运行效果
六、观察者模式使用总结
从上面可以看出,实际上观察者和被观察者是通过接口回调来通知更新的,首先创建一个观察者(数据监听)实例并实现数据变化接口,通过注册监听将实例传入被观察者(数据变化),当被观察者数据变化的时候使用该实例的接口回传状态。了解原理之后,我们可以利用观察者模式自定义实现。
拿微信公众号来举例,假设微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了 陈守印同学 这个公众号,当这个公众号更新时就会通知这些订阅的微信用户。我们来看看用代码如何实现:
1.抽象观察者(Observer)
public interface Observer {
public void update(String message);
}
2.具体观察者(ConcrereObserver)
微信用户是观察者,里面实现了更新的方法:
里面定义了一个更新的方法:
public class WeixinUser implements Observer {
// 微信用户名字
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)
微信公众号是具体主题(具体被观察者),里面存储了订阅该公众号的微信用户,并实现了抽象主题中的方法:
public class SubscriptionSubject implements Subject {
//储存订阅公众号的微信用户
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();
//创建微信用户
WeixinUser user1=new WeixinUser("陈守印同学公众号粉丝A");
WeixinUser user2=new WeixinUser("陈守印同学公众号粉丝B");
WeixinUser user3=new WeixinUser("陈守印同学公众号粉丝C");
WeixinUser user4=new WeixinUser("陈守印同学公众号粉丝D");
//订阅公众号
mSubscriptionSubject.attach(user1);
mSubscriptionSubject.attach(user2);
mSubscriptionSubject.attach(user3);
mSubscriptionSubject.attach(user4);
//公众号更新发出消息给订阅的微信用户
mSubscriptionSubject.notify("陈守印同学公众号的文章更新啦");
}
}
6.运行结果
陈守印同学公众号粉丝A:陈守印同学公众号的文章更新啦
陈守印同学公众号粉丝B:陈守印同学公众号的文章更新啦
陈守印同学公众号粉丝C:陈守印同学公众号的文章更新啦
陈守印同学公众号粉丝D:陈守印同学公众号的文章更新啦
6.观察者模式优缺点
- 解除耦合,让耦合的双方都依赖于抽象,从而使得各自的变换都不会影响另一边的变换。
- 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
公号后台回复"设计模式",获取设计模式书籍
后续文章持续更新中,微信扫码下方二维码免费关注!上一篇:Java多线程下载01:多线程的好处以及断点续传原理
点此查看全部最新文章
我的博客
我的简书
我的GitHub,喜欢的话给个star吧