美文网首页安卓开发博客
Android源码设计模式(七)— 解决、解耦的钥匙 — 观察者

Android源码设计模式(七)— 解决、解耦的钥匙 — 观察者

作者: 随时学丫 | 来源:发表于2018-07-13 10:12 被阅读8次

    Android源码设计模式(一) -- 面向对象的六大原则
    Android源码设计模式(二)-- 应用最广的模式:单例模式
    Android源码设计模式(三)-- 自由扩展你的项目:Builder 模式
    Android源码设计模式(四)-- 时势造英雄:策略模式
    Android源码设计模式(五)-- 使编程更有灵活性:责任链模式
    Android源码设计模式(六)— 编程好帮手:代理模式
    Android源码设计模式(七)— 解决、解耦的钥匙 — 观察者模式

    简书 MD 语法不识别 [TOC] ,也不会根据标题行(#) 来插入目录,作为每次看资料喜欢先看目录把握总体的我来说,很不习惯,查找跳转也不方便,所以,如果看到文章没有目录预览的,请安装脚本:简书目录脚本地址

    一、观察者模式的定义

    定义对象间的一对多的依赖关系,使得每当一个对象改变状态,则所有依赖它的对象都会得到通知并被自动更新。

    二、观察者模式的使用场景

    • 关联行为场景,需要注意的是管理行为是可拆分的,而不是组合关系。
    • 事件多级触发场景。
    • 跨系统的消息交换场景,如消息队列 ,事件总线的处理机制。

    三、观察者模式的 UML 类图】

    观察者UML.png

    角色介绍

    • Subject:抽象主题,也就是被观察者(Observable)的角色,抽象主题角色把所有观察者对象的引用保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者。
    • ConcreteSubject:具体主题,该角色将有关状态存入具体观察者对象,在具体主题内部状态发生改变时,给所有注册过的观察者发出通知,具体主题角色又叫做具体观察者角色。
    • Observer:抽象观察者,该角色是观察者的抽象类,定义了一个更新接口,使得在得到主题的更改通知时更新自己。
    • ConcreteObserver:具体的观察者,该角色实现抽象观察者所定义的更新接口,以便在主题状态发生改变时更新自身的状态。

    四、观察者模式的简单实现

    案例分析:开发技术前线网站 (www.devtf.cn) 是一个聚合了关于 Android,IOS 新技术文章的开源库等内容网站,在这里可以看到新的技术推送。开发技术前线网站不仅仅是一个内容发布者,也支持用户邮箱订阅,每周发布周报后会将优质内容推送给订阅用户,这种模式叫做发布 -- 订阅模式。它的另一个名称叫做观察者模式,就是订阅开发技术前线之后收到的周报。

    下面我们简单实现一下订阅过程。

    //程序员是观察者
    public class Coder implements Observer {
        private String name;
        public Coder(String name) {
            this.name = name;
        }
        @Override
        public void update(Observable o, Object arg) {
            System.out.println("Hi, " + name + ", DevTechFrontier 更新啦,内容:" + arg);
        }
        @Override
        public String toString() {
            return "码农:" + name;
        }
    }
    
    //java.util 包中的接口
    package java.util;
    public interface Observer {
        void update(Observable o, Object arg);
    }
    

    DevTechFrontier 被观察者,当有更新时所有观察者都会收到通知

    //DevTechFrontier 被观察者
    public class DevTechFrontier extends Observable {
        public void postNewNotification(String content) {
            setChanged();
            notifyObservers(content);
        }
    }
    
    //java.util 包中的类
    public class Observable {
        private boolean changed = false;
        private Vector<Observer> obs;
        public Observable() {
            obs = new Vector<>();
        }
    
        public synchronized void addObserver(Observer o) {
            if (o == null)
                throw new NullPointerException();
            if (!obs.contains(o)) {
                obs.addElement(o);
            }
        }
    
        public synchronized void deleteObserver(Observer o) {
            obs.removeElement(o);
        }
    
        public void notifyObservers() {
            notifyObservers(null);
        }
    
        public void notifyObservers(Object arg) {
            Object[] arrLocal;
            synchronized (this) {
                if (!changed)
                    return;
                arrLocal = obs.toArray();
                clearChanged();
            }
            for (int i = arrLocal.length-1; i>=0; i--)
                ((Observer)arrLocal[i]).update(this, arg);
        }
    
        public synchronized void deleteObservers() {
            obs.removeAllElements();
        }
    
        protected synchronized void setChanged() {
            changed = true;
        }
    
        protected synchronized void clearChanged() {
            changed = false;
        }
    
        public synchronized boolean hasChanged() {
            return changed;
        }
    
        public synchronized int countObservers() {
            return obs.size();
        }
    }
    

    Client 类,测试代码

    public class Client {
        public static void main(String[] args) {
            DevTechFrontier devTechFrontier = new DevTechFrontier();
            Coder coder1 = new Coder("coder-1");
            Coder coder2 = new Coder("coder-2");
            Coder coder3 = new Coder("coder-3");
            devTechFrontier.addObserver(coder1);
            devTechFrontier.addObserver(coder2);
            devTechFrontier.addObserver(coder3);
            devTechFrontier.postNewNotification("新的一期技术前线周报发布啦!");
        }
    }
    //---------------------------------运行结果--------------------------------------------------
    Hi, coder-3, DevTechFrontier 更新啦,内容:新的一期技术前线周报发布啦!
    Hi, coder-2, DevTechFrontier 更新啦,内容:新的一期技术前线周报发布啦!
    Hi, coder-1, DevTechFrontier 更新啦,内容:新的一期技术前线周报发布啦!
    

    可以看到所有订阅了开发技术前线的用户都能收到更新通知,一对多订阅 -- 发布系统就完成了。

    Observer 和 Observable 是系统内置类型,这里 Observer 是抽象的观察者角色,Coder 是具体观察者角色;Observable 是对应的抽象主题角色,DevTechFrontier 是具体主题角色。

    Coder 是具体观察者角色,他们都订阅了 DevTechFrontier 这个可观察者对象,当 DevTechFrontier 更新时,会遍历所有观察者,然后给这些观察者发布一个更新消息,就是调用 Coder 中的 update 方法,这样就达到了一对多的通知功能。

    五、Android 中观察者模式实现

    ListView 是 Android 中重要的控件之一,而 ListView 最重要的一个功能就是 Adapter,后面我们会分析 Adapter 模式。通常,在我们往 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();
        }
    
        /**
         * Notifies the attached observers that the underlying data is no longer valid
         * or available. Once invoked this adapter is no longer valid and should
         * not report further data set changes.
         */
        public void notifyDataSetInvalidated() {
            mDataSetObservable.notifyInvalidated();
        }
    
        public boolean areAllItemsEnabled() {
            return true;
        }
    
        public boolean isEnabled(int position) {
            return true;
        }
    
        public View getDropDownView(int position, View convertView, ViewGroup parent) {
            return getView(position, convertView, parent);
        }
    
        public int getItemViewType(int position) {
            return 0;
        }
    
        public int getViewTypeCount() {
            return 1;
        }
        
        public boolean isEmpty() {
            return getCount() == 0;
        }
    }
    

    可以看到 BaseAdapter 就是一个观察者模式,那么 BaseAdapter 是如何运作的呢?这些观察者又是什么?我们下面一步一步分析:

    我们先到 mDataSetObservable.notifyChanged() 函数中看看:

    public class DataSetObservable extends Observable<DataSetObserver> {
         //调用每个函数的 onChanged() 方法通知他们的被观察者发生变化
        public void notifyChanged() {
            synchronized(mObservers) {
                for (int i = mObservers.size() - 1; i >= 0; i--) {
                    mObservers.get(i).onChanged();
                }
            }
        }
    }
    

    这里就是遍历所有观察者,并调用它们的 onChanged() 方法,从而告知观察者发生了变化。

    那么观察者是从哪里来的呢?其实这些观察者就是 ListView 通过 setAdapter 时产生的,我们看看相关代码:

    @Override
    public void setAdapter(ListAdapter adapter) {
        //如果已经有了一个Adapter,先注销该Adapter对应的观察者
        if (null != mAdapter) {
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
        }
    
        resetList();
        mRecycler.clear();
    
        if (mHeaderViewInfos.size() > 0 || mFooterViewInfos.size() > 0) {
            mAdapter = new PLA_HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
        } else {
            mAdapter = adapter;
        }
    
        mOldSelectedPosition = INVALID_POSITION;
        mOldSelectedRowId = INVALID_ROW_ID;
        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 {
            mAreAllItemsSelectable = true;
            checkFocus();
            // Nothing selected
        }
    
        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<ListAdapter>.AdapterDataSetObserver,具体代码如下:

    class AdapterDataSetObserver extends DataSetObserver {
        private Parcelable mInstanceState = null;
    
        //上文讲过,调用Adapter的notifyDataSetChanged会调用所有观察者的onChanged方法,核心实现就在这里
        @Override
        public void onChanged() {
            mDataChanged = true;
            mOldItemCount = mItemCount;
            mItemCount = getAdapter().getCount();
            if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
                    && mOldItemCount == 0 && mItemCount > 0) {
                AdapterView.this.onRestoreInstanceState(mInstanceState);
                mInstanceState = null;
            } else {
                rememberSyncState();
            }
            checkFocus();
            //重新布局ListView,GridView等AdapterView组件
            requestLayout();
        }
    
        @Override
        public void onInvalidated() {
            mDataChanged = true;
    
            if (AdapterView.this.getAdapter().hasStableIds()) {
                mInstanceState = AdapterView.this.onSaveInstanceState();
            }
    
            mOldItemCount = mItemCount;
            mItemCount = 0;
            mSelectedPosition = INVALID_POSITION;
            mSelectedRowId = INVALID_ROW_ID;
            mNextSelectedPosition = INVALID_POSITION;
            mNextSelectedRowId = INVALID_ROW_ID;
            mNeedSync = false;
    
            checkFocus();
            requestLayout();
        }
    
        public void clearSavedState() {
            mInstanceState = null;
        }
    }
    

    到这里我们知道,当 ListView 的数据发生变化时,调用 Adapter 的 notifyDataSetChanged 会调用所有观察者的onChanged 方法,在 onChanged 方法中又会调用 ListView 的 requestLayout 进行重新布局使得 ListView 刷新界面。

    子 View 调用 requestLayout 方法,会标记当前 View 及父容器,同时逐层向上提交,直到 ViewRootImpl 处理该事件,ViewRootImpl 会调用三大流程,从 measure 开始,对于每一个含有标记位的 View 及其子 View都会进行测量、布局、绘制。

    Android View 深度分析requestLayout、invalidate与postInvalidate

    最后,我们再整理一下这个过程,AdapterListView 中有一个内部类 AdapterDataSetObserver,在 ListView 中设置 Adapter 时会创建 AdapterDataSetObserver,并且注册到 Adapter 中,这就是一个观察者,而 Adapter 中包含一个数据集可观察者 DataSetObservable,在数据数量发送变更时,开发者手动调用 Adapter.notifyDataSetChanged,而 notifyDataSetChanged 实际上会调用 DataSetObservable 的 notifyChanged 函数,该函数会遍历所有观察者的 onChanged 方法,在 AdapterDataSetObserver 的 onChanged 中会获取 Adapter 中数据集的新数量,然后调用 requestLayout 进行 View 绘制三大流程。

    六、总结

    优点

    • 观察者和被观察者之间是抽象解耦,应对业务变化
    • 增强系统灵活性,可扩展性

    缺点

    • 程序中被一个被观察者,多个观察者,开发和调试变得复杂
    • 在 Java 中消息的通知默认都是顺序执行,一个观察者卡顿,会影响整体执行效率,在这种情况下,一般考虑采用异步的方式。

    相关文章

      网友评论

        本文标题:Android源码设计模式(七)— 解决、解耦的钥匙 — 观察者

        本文链接:https://www.haomeiwen.com/subject/xafjpftx.html