Java多线程下载框架02:观察者模式通知下载内容状态更新

场景描述

在Java多线程下载框架中,我们需要知道下载状态比如暂停下载,恢复下载,取消下载等状态的通知,而且不仅仅是更新当前页面,在任意页面都能接收到状态变化的更新,所以这里要用到观察者模式。


Java多线程下载框架02:观察者模式通知下载内容状态更新_第1张图片
观察者模式

关于设计模式的详细介绍,我这里有几本电子书籍推荐,公号后台回复"设计模式",即可获取下载链接。

那么什么是观察者模式(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 Observable 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 Observable object as having been changed; the
     * hasChanged method will now return true.
     */
    protected synchronized void setChanged() {
        changed = true;
    }

此处把boolen变量changed改为了true

再看notifyObservers源码

 /**
     * If this object has changed, as indicated by the
     * hasChanged method, then notify all of its observers
     * and then call the clearChanged method to indicate
     * that this object has no longer changed.
     * 

* Each observer has its update method called with two * arguments: this observable object and the arg 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);
    }

五、运行效果

Java多线程下载框架02:观察者模式通知下载内容状态更新_第2张图片
运行效果

六、观察者模式使用总结

从上面可以看出,实际上观察者和被观察者是通过接口回调来通知更新的,首先创建一个观察者(数据监听)实例并实现数据变化接口,通过注册监听将实例传入被观察者(数据变化),当被观察者数据变化的时候使用该实例的接口回传状态。了解原理之后,我们可以利用观察者模式自定义实现。

拿微信公众号来举例,假设微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了 陈守印同学 这个公众号,当这个公众号更新时就会通知这些订阅的微信用户。我们来看看用代码如何实现:

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 weixinUserlist = new ArrayList();

    @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多线程下载框架02:观察者模式通知下载内容状态更新_第3张图片
设计模式

后续文章持续更新中,微信扫码下方二维码免费关注!上一篇:Java多线程下载01:多线程的好处以及断点续传原理
点此查看全部最新文章

Java多线程下载框架02:观察者模式通知下载内容状态更新_第4张图片

我的博客
我的
我的GitHub,喜欢的话给个star吧

你可能感兴趣的:(Java多线程下载框架02:观察者模式通知下载内容状态更新)