观察者模式

1、定义

观察者模式就是定义对象间的一种一对多的依赖关系。当被观察者(Observable)发生改变,所有依赖与它的对象(观察者,Observer)都会得到通知并自动更新。

2、分析BaseAdapter

观察者模式是我们常接触的一种设计模式,比如经常使用的ListVIew的BaseAdapter使用的就是观察者模式。


image.png
public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {

    private final DataSetObservable mDataSetObservable = new DataSetObservable();
    
    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();
    }

    。。。
}

1、通知数据的改变

当数据发生改变的时候,我们通常会调用BaseAdapter的notifyDataSetChanged(),通知所有的观察者:通过遍历所有的观察者(mObservers),并调用其onChanged()来通知观察者。

public class DataSetObservable extends Observable<DataSetObserver> {

    public void notifyChanged() {
        synchronized(mObservers) {
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }
    }

    。。。
}

但是观察者是从哪里来的呢?

2、注册观察者

这些观察者(Observer)是在setAdapter()的时候创建并注册到被观察者(Observable)的。

public class ListView extends AbsListView {
    /**
     * Should be used by subclasses to listen to changes in the dataset
     */
    AdapterDataSetObserver mDataSetObserver;

    /**
     * The adapter containing the data to be displayed by this view
     */
    ListAdapter mAdapter;
    。。。
    @Override
    public void setAdapter(ListAdapter adapter) {
        if (mAdapter != null && mDataSetObserver != null) {
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
        }

        。。。

        mAdapter = adapter;

        if (mAdapter != null) {

            mDataSetObserver = new AdapterDataSetObserver();
            mAdapter.registerDataSetObserver(mDataSetObserver);

            。。。
        } 

        requestLayout();
    }
}

其中AdapterDataSetObserver在ListView的父类中,并在onChanged()被调用的时候,调用requestLayout()重新布局listView。

class AdapterDataSetObserver extends DataSetObserver {

        @Override
        public void onChanged() {
            。。。
            requestLayout();
        }

        @Override
        public void onInvalidated() {
            。。。
            requestLayout();
        }
}

所以,当ListVIew数据发生改变的时候,调用Adapter的notifyDataSetChanged()--->DataSetObservable (notifyChanged())--->DataSetObserver(onChanged()),总之,最终调用了所有观察者的onChanged()方法,在onChanged()方法中又会调用ListVIew的 requestLayout()函数,重新布局ListView。

其中Observable

public abstract class Observable<T> {
    /**
     * The list of observers.  An observer can be in the list at most
     * once and will never be null.
     */
    protected final ArrayList<T> mObservers = new ArrayList<T>();

    /**
     * Adds an observer to the list. The observer cannot be null and it must not already
     * be registered.
     * @param observer the observer to register
     * @throws IllegalArgumentException the observer is null
     * @throws IllegalStateException the observer is already registered
     */
    public void registerObserver(T observer) {
        if (observer == null) {
            throw new IllegalArgumentException("The observer is null.");
        }
        synchronized(mObservers) {
            if (mObservers.contains(observer)) {
                throw new IllegalStateException("Observer " + observer + " is already registered.");
            }
            mObservers.add(observer);
        }
    }

    /**
     * Removes a previously registered observer. The observer must not be null and it
     * must already have been registered.
     * @param observer the observer to unregister
     * @throws IllegalArgumentException the observer is null
     * @throws IllegalStateException the observer is not yet registered
     */
    public void unregisterObserver(T observer) {
        if (observer == null) {
            throw new IllegalArgumentException("The observer is null.");
        }
        synchronized(mObservers) {
            int index = mObservers.indexOf(observer);
            if (index == -1) {
                throw new IllegalStateException("Observer " + observer + " was not registered.");
            }
            mObservers.remove(index);
        }
    }

    。。。
}

DataSetObserver

public abstract class DataSetObserver {
    /**
     * This method is called when the entire data set has changed,
     * most likely through a call to {@link Cursor#requery()} on a {@link Cursor}.
     */
    public void onChanged() {
        // Do nothing
    }

    /**
     * This method is called when the entire data becomes invalid,
     * most likely through a call to {@link Cursor#deactivate()} or {@link Cursor#close()} on a
     * {@link Cursor}.
     */
    public void onInvalidated() {
        // Do nothing
    }
}
3、例子

使用方式和ListView一样

public class CommonHorizontalListView extends HorizontalScrollView {

    private ListAdapter mAdapter;
    private AdapterDataObserver mObserver;
    private LinearLayout mContainer;
    private Context mContext;

    private int paddingLeft;
    private int paddingRight;
    private int paddingTopBottom;
    private int leftItemMargin;
    private int rightItemMargin;
    private float density;

    public CommonHorizontalListView(Context context) {
        this(context, null);
    }

    public CommonHorizontalListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mContext = context;
        density = context.getResources().getDisplayMetrics().density;
        init();
    }

    private void init() {
        paddingLeft = (int) (10 * density);
        paddingRight = (int) (10 * density);
        paddingTopBottom = (int) (3 * density);
        leftItemMargin= (int) (5 * density);
        rightItemMargin= (int) (5 * density);
        mContainer = new LinearLayout(mContext);
        mContainer.setOrientation(LinearLayout.HORIZONTAL);
        LayoutParams p = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        mContainer.setPadding(paddingLeft, paddingTopBottom, paddingRight, paddingTopBottom);
        addView(mContainer, p);
        setBackgroundColor(Color.WHITE);
        setSmoothScrollingEnabled(true);
        setHorizontalScrollBarEnabled(false);
    }

    public void setAdapter(ListAdapter adapter) {
        if (mAdapter != null && mObserver != null) {
            mAdapter.unregisterDataSetObserver(mObserver);
        }
        if (adapter != null) {
            this.mAdapter = adapter;
            mObserver = new AdapterDataObserver();
            mAdapter.registerDataSetObserver(mObserver);
            addViews();
        }
    }

    private void addViews() {
        mContainer.removeAllViews();
        int itemCount = mAdapter.getCount();
        for (int i = 0; i < itemCount; i++) {
            View child = mAdapter.getView(i, null, null);
            LinearLayout.LayoutParams params=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            params.leftMargin=leftItemMargin;
            params.rightMargin=rightItemMargin;
            mContainer.addView(child,params);
        }
    }

    private class AdapterDataObserver extends DataSetObserver {
        @Override
        public void onChanged() {
            addViews();
            //requestLayout();
        }

        @Override
        public void onInvalidated() {
            //addViews();
            //requestLayout();
        }
    }

}
观察者模式 练习

注意:requeLayout() : 控件会重新执行 onMesure() 、onLayout() ,重新测量和定位。

3、分析EventBus

EventBus,事件总线,它使用发布订阅模式支持组件之间的通信,不需要显式地注册回调,比观察者模式更灵活,可用于替换Java中传统的事件监听器。

1、自己实现简单的事件总线

定义一个订阅者列表(Map<Class<?>,ArrayList< EventSubscription >>),其中EventSubscription存储了target、事件接收器方法和参数;在注册时,以事件接收器对象作为map的key;publish时,调用EventSubscription的onChanged()方法,通知每个订阅者发生了改变。并在这个方法中通过反射调用事件接收器方法(onEventMainThread)。

method.invoke(target,args);

为了灵活的定义方法接收器,可以使用注解。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Subscriber {
}

使用方法

EventBus.getInstance().subscribe(MainActivity.this);
EventBus.getInstance().unsubscribe(this);
EventBus.getInstance().publish("update from SecondFragment");


@Subscriber
public void onEventMainThread(String str){
    Toast.makeText(this,"MainActivity --- "+ str,Toast.LENGTH_LONG).show();
}
MY——EventBus
public class EventBus {

    //订阅者列表
    private Map<Class<?>,ArrayList<EventSubscription>> subscriptionByEvent=new HashMap<>();

    private EventBus(){
    }

    public static EventBus getInstance(){
        return Singleton.INSTANCE;
    }

    static class Singleton {
        static EventBus INSTANCE=new EventBus();
    }

    // register(target) 方法用于注册事件接收器的目标对象,我们需要保存这个对象,
    // 同时还需要查找这个对象中存在的事件接收器方法
    public void subscribe(Object target){
        Class<?> clazz=target.getClass();
        Method[] methods=clazz.getMethods();
        for (Method method:methods){
            Subscriber subscriber=method.getAnnotation(Subscriber.class);
            if (subscriber!=null){
                Class[] parameters=method.getParameterTypes();
                if (parameters!=null && parameters.length==1){
                    ArrayList<EventSubscription> subscriptions;
                    if (subscriptionByEvent.containsKey(parameters[0])){
                        subscriptions = subscriptionByEvent.get(parameters[0]);
                    }else {
                        subscriptions =new ArrayList<>();
                    }
                    EventSubscription subscription =new EventSubscription(target,method,parameters[0]);
                    subscriptions.add(subscription);
                    subscriptionByEvent.put(parameters[0], subscriptions);
                }
            }
//            if (method.getName().equals("onEventMainThread")){
//                Class[] parameters=method.getParameterTypes();
//                ArrayList<EventSubscription> subscriptions;
//                if (subscriptionByEvent.containsKey(parameters[0])){
//                    subscriptions = subscriptionByEvent.get(parameters[0]);
//                }else {
//                    subscriptions =new ArrayList<>();
//                }
//                EventSubscription subscription =new EventSubscription(target,method,parameters[0]);
//                subscriptions.add(subscription);
//                subscriptionByEvent.put(parameters[0], subscriptions);
//            }
        }
    }

    //取消注册目标对象
    public void unsubscribe(Object target){
        Class<?> clazz=target.getClass();
        Method[] methods=clazz.getMethods();
        for (Method method:methods){
            Subscriber subscriber=method.getAnnotation(Subscriber.class);
            if (subscriber!=null){
                Class[] parameters=method.getParameterTypes();
                if (parameters!=null && parameters.length==1){
                    if (subscriptionByEvent.containsKey(parameters[0])){
                        subscriptionByEvent.remove(parameters[0]);
                    }
                }
            }
//            if (method.getName().equals("onEventMainThread")){
//                Class[] parameters=method.getParameterTypes();
//                ArrayList<EventSubscription> eventObservers;
//                if (subscriptionByEvent.containsKey(parameters[0])){
//                    subscriptionByEvent.remove(parameters[0]);
//                }
//            }
        }
    }

    // 发送事件给 目标对象和事件接收器(onEventMainThread)方法。
    public void publish(Object event){
        Class<?> clazz=event.getClass();
        if (subscriptionByEvent.containsKey(clazz)){
            ArrayList<EventSubscription> subscriptions =subscriptionByEvent.get(clazz);
            for (EventSubscription eventObserver : subscriptions){
                eventObserver.onChanged(event);
            }
        }
    }

}
MainActivity的Toast,有SecondFragmnent触发
2、EventBus解析

参考:
Android源码设计模式教你自己实现一个事件总线EventBus跟我一起写EventBus(一)Android事件总线(一)EventBus3.0用法全解析

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,264评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,549评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,389评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,616评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,461评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,351评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,776评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,414评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,722评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,760评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,537评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,381评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,787评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,030评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,304评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,734评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,943评论 2 336

推荐阅读更多精彩内容

  • 一,词典 与前面介绍的映射结构一样,词典结构也是用来存放条目对象的一种容器,不过,词典与映射之间有一个非常重要的差...
    峰峰小阅读 924评论 0 0
  • 1、抗拒学英文 在国内几乎所有的变成语言都是外国的,所以学技术必定要学会看英文文档,如果不学英文,是绝对无法从菜鸟...
    名真好取阅读 153评论 0 0
  • “哪怕生活让你遍体鳞伤,也不要对自己投降。”这句话一下子就戳中了我的泪点。 一切好运气都好像在高二的之前用光了...
    阿回阅读 637评论 3 3
  • 文/筱安时光 01 高中理科生,大学工科专业,2015年毕业后阴差阳错地成了一名乡村小学教师。 我没有系统地学过心...
    筱安时光阅读 293评论 0 0