观察者模式(触发联动)

0、提纲

目录:
1、举例:发起登录请求
2、Android Adapter 相关源代码分析
3、EventBus 相关源代码分析
4、观察者模式总结

需要查看其它设计模式描述可以查看我的文章《设计模式开篇》

1、举例:发起登录请求

现在假设有登录接口(login),需要传入参数(username、password)。众所周知的是网络请求本身是耗时操作,并且 android 不允许在UI 线程发起网络请求。

所以我们会另开辟线程去执行登录操作,代码看起来像下面:

// Thread.java
public class LoginThread extends Thread{

    public void run(){
        Result result = api.login("username","password");
    }
}

我们已经获得了登录的结果Result,但是怎样才能将 Result 告知给客户端调用者呢?

方案1:设置回调接口

通过将ICallback的实例传给 Thread 对象,这样当 Thread 对象内部获取到 Result 实例时即可将结果回调出去。

public interface ICallback{
      void onCallback(Result result);
}

方案2:应用观察者模式

再获取到Result 时,向发布订阅中心发送一条通知观察者的事件。由发布订阅中心将事件(依据某种规则)发送给订阅者。它与采用回调的方式相比最显著的区别是:回调只能针对单个对象进行,而观察者可以通过观察者中心触发多个观察者对象联动。

观察者模式

观察者的行为其实也很好理解,整个过程可以划分为4个部分:
1、向注册中心注册(向花店订购了每周一束花的套餐)
2、外部发送事件(每天送花人都会将花送到花店)
3、获取订阅对象(花店老板检查到你本周的花还没有配送,于是将你列入待配送的清单)
4、通知订阅对象(由送花员将花束送到你的家里)

理论的东西不讲太多,下面我们结合源代码进行分析。

2、Android Adapter 相关源代码分析

2.1、Adapter

我们都知道 Adapter 意味着数据源,往往数据源的改动会影响着 UI 的变更。所以对 Adapter 的分析自然是我们第一步要做的事情。

public interface Adapter {
    void registerDataSetObserver(DataSetObserver observer);
    void unregisterDataSetObserver(DataSetObserver observer);
    // 省略其他代码
}

Adapter的接口规范中就已经定义了注册与反注册DataSetObserver实例的方法。注册DataSetObserver的目的,是为了在适配器内的数据发生更改时进行调用。

2.2、DataSetObserver

DataSetObserver是数据观察员,它的代码定义如下。它关注两个维度的数据变更,数据发生改变 或者 数据失效。

public abstract class DataSetObserver {
    public void onChanged() {
        // Do nothing
    }

    public void onInvalidated() {
        // Do nothing
    }
}

2.3、ListAdapter

因为我们并不打算脱离ListView分析抽象的玩意,所以我们回归到实例ListAdapter。让我们看看ListAdapter的实现,ListAdapter继承了接口Adapter并扩充一些适用于List场景的接口方法。

ListAdapter的类图

2.4、BaseAdapter

由于ListAdapter是接口,所以我们仍需查找实现了该接口的类——即BaseAdapter

我们看到了DataSetObservable是被观察的对象,是真正触发观察者对象联动的源头。

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);
    }

   public void notifyDataSetChanged() {
        // 通知订阅者数据已变更
        mDataSetObservable.notifyChanged();
    }

   public void notifyDataSetInvalidated() {
        // 通知订阅者数据已失效
        mDataSetObservable.notifyInvalidated();
    }
}

2.5、DataSetObservable

你可以将DataSetObservable理解为向订阅对象触发行为的实现,它们可以选择向谁发送、发送什么样的事件。比如:DataSetObservable就是遍历所有的订阅者并向他们推送信息。

public class DataSetObservable extends Observable<DataSetObserver> {
    public void notifyChanged() {
        synchronized(mObservers) {
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }
    }

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

2.6、Observable

Observable<T>是所有订阅中心的模板类,它为提供不同的模板策略提供了抽象的实现。

public abstract class Observable<T> {

    protected final ArrayList<T> mObservers = new ArrayList<T>();

    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);
        }
    }

    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);
        }
    }

    public void unregisterAll() {
        synchronized(mObservers) {
            mObservers.clear();
        }
    }
}

2.7、总结

观察者模式(又称发布/订阅模式)相比享元或解释器等模式,它的模式实现逻辑非常清晰。

有的同学可能对observer (订阅者)与 observable(可供订阅的对象)这两个词分不清楚,建议结合上文中贴出的图再加以思考,应该可以理解它们的差异。

3、EventBus 相关源代码分析

在分析之前你要先对 EventBus 有些了解,如果还不知道可以查看EventBus

// 1、注册监听
EventBus.getDefault().register(this);

// 2、接收事件
public void onEvent(Event event) {
      //  省略细节代码
}
// 3、取消注册监听,防止内存泄露
EventBus.getDefault().unregister(this);

3.1、注册&反注册

register(this)目的,是为了将自身句柄注册到发布订阅中心中,以便发布订阅中心向this发送事件。unregister(this)的目的是为了避免内存泄露。

3.2、register()

// EventBus.java
public void register(Object subscriber) {
        register(subscriber, false, 0);
    }
private synchronized void register(Object subscriber, boolean sticky, int priority) {
  List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass());
  for (SubscriberMethod subscriberMethod : subscriberMethods) {
      subscribe(subscriber, subscriberMethod, sticky, priority);
  }
}

1、register方法有三个参数:订阅对象,是否粘性,优先级。
2、再调用注册方法时,首先会调用subscriberMethodFinder.findSubscriberMethods查找订阅对象中的订阅方法(即回调函数)
3、然后再依次使用回调函数执行订阅。

所以EventBus真正的订阅对象是回调函数。

3.3、SubscriberMethodFinder

// SubscriberMethodFinder.java
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        // STEP1:查找缓存,若有直接返回
        String key = subscriberClass.getName();
        List<SubscriberMethod> subscriberMethods;
        synchronized (methodCache) {
            subscriberMethods = methodCache.get(key);
        }
        if (subscriberMethods != null) {
            return subscriberMethods;
        }

        subscriberMethods = new ArrayList<SubscriberMethod>();
        Class<?> clazz = subscriberClass;
        HashSet<String> eventTypesFound = new HashSet<String>();
        StringBuilder methodKeyBuilder = new StringBuilder();

        while (clazz != null) {
            String name = clazz.getName();
            // STEP2:跳过系统类,有助于提高性能
            if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")) {
                break;
            }

            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
                String methodName = method.getName();
                // STEP3 :筛选出满足约定的订阅函数
                if (methodName.startsWith(ON_EVENT_METHOD_NAME)) {
                    int modifiers = method.getModifiers();
                    if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                        Class<?>[] parameterTypes = method.getParameterTypes();
                        if (parameterTypes.length == 1) {
                            String modifierString = methodName.substring(ON_EVENT_METHOD_NAME.length());
                            ThreadMode threadMode;
                            if (modifierString.length() == 0) {
                                threadMode = ThreadMode.PostThread;
                            } else if (modifierString.equals("MainThread")) {
                                threadMode = ThreadMode.MainThread;
                            } else if (modifierString.equals("BackgroundThread")) {
                                threadMode = ThreadMode.BackgroundThread;
                            } else if (modifierString.equals("Async")) {
                                threadMode = ThreadMode.Async;
                            } else {
                                if (skipMethodVerificationForClasses.containsKey(clazz)) {
                                    continue;
                                } else {
                                    throw new EventBusException("Illegal onEvent method, check for typos: " + method);
                                }
                            }
                            Class<?> eventType = parameterTypes[0];
                            methodKeyBuilder.setLength(0);
                            methodKeyBuilder.append(methodName);
                            methodKeyBuilder.append('>').append(eventType.getName());
                            String methodKey = methodKeyBuilder.toString();
                            if (eventTypesFound.add(methodKey)) {
                                // Only add if not already found in a sub class
                                subscriberMethods.add(new SubscriberMethod(method, threadMode, eventType));
                            }
                        }
                    } else if (!skipMethodVerificationForClasses.containsKey(clazz)) {
                        Log.d(EventBus.TAG, "Skipping method (not public, static or abstract): " + clazz + "."
                                + methodName);
                    }
                }
            }
            clazz = clazz.getSuperclass();
        }
            clazz = clazz.getSuperclass();
        }
        // STEP4:加入缓存
        if (subscriberMethods.isEmpty()) {
            throw new EventBusException("Subscriber " + subscriberClass + " has no public methods called "
                    + ON_EVENT_METHOD_NAME);
        } else {
            synchronized (methodCache) {
                methodCache.put(key, subscriberMethods);
            }
            return subscriberMethods;
        }

}

这段方法有点长但结构却比较清晰,大概描述下面几件事情:
1、检查缓存,若有则命中如无则往下执行。
2、检查是否是系统类,若是直接跳过。
3、检查是否以onEvent作为首字符串。
4、检查以onEvent作为首字符串的字符串后缀(value)。

4.1、value = "",则意味着使用ThreadMode.PostThread。
4.2、value="MainThread",则意味着使用ThreadMode.MainThread。
4.3、value="BackgroundThread",则意味着使用ThreadMode.BackgroundThread。
4.4、value="Async",则意味着使用ThreadMode.Async

5、如果到此都没有发现以onEvent作为首字符串,则会抛出异常。
6、如果检查到以onEvent作为首字符串,则会缓存结果。

3.4、执行订阅 subscribe

    // 必须在同步块中调用
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) {
        Class<?> eventType = subscriberMethod.eventType;
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);
        // STEP1:准备容器
        if (subscriptions == null) {
            subscriptions = new CopyOnWriteArrayList<Subscription>();
            subscriptionsByEventType.put(eventType, subscriptions);
        } else {
             // STEP2:避免重复订阅
            if (subscriptions.contains(newSubscription)) {
                throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                        + eventType);
            }
        }

        // 自从 EventBus 2.2 开始我们强制约定方法是 public 权限(或你通过注解的形式改变访问权限)
        // subscriberMethod.method.setAccessible(true);

        // STEP3:检查优先级,将订阅者放到合适的位置
        int size = subscriptions.size();
        for (int i = 0; i <= size; i++) {
            if (i == size || newSubscription.priority > subscriptions.get(i).priority) {
                subscriptions.add(i, newSubscription);
                break;
            }
        }
        // STEP4:将订阅方法与订阅方法内的参数做映射
        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<Class<?>>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);
        // STEP5:检查是否粘性事件
        if (sticky) {
            Object stickyEvent;
            synchronized (stickyEvents) {
                stickyEvent = stickyEvents.get(eventType);
            }
            if (stickyEvent != null) {
                postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());
            }
        }
    }

subscribe方法的本质是准备好订阅方法-->与订阅方法的参数的映射关系。

// 订阅方法:onEvent
// 订阅方法的参数:Event
public void onEvent(Event event) {
    
}

3.5、post

到此位置该准备好的都准备好了,接下来就等待外部触发事件了。

    public void post(Object event) {
        PostingThreadState postingState = currentPostingThreadState.get();
        List<Object> eventQueue = postingState.eventQueue;
        eventQueue.add(event);

        if (!postingState.isPosting) {
            postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
            postingState.isPosting = true;
            if (postingState.canceled) {
                throw new EventBusException("Internal error. Abort state was not reset");
            }
            try {
                while (!eventQueue.isEmpty()) {
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }

1、内部准备好List<Object> eventQueue用作事件的派发。
2、如果事件未派发,则执行派发。并设置eventQueue的派发状态为isPosting=true
3、调用postSingleEvent(eventQueue.remove(0), postingState);执行事件派发。
4、派发完成后,设置eventQueue的派发状态为isPosting= false

3.6、postSingleEvent

    private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        Class<?> eventClass = event.getClass();
        boolean subscriptionFound = false;
        // STEP1:检查事件的继承性
        if (eventInheritance) {
            // STEP2:向上查找事件类型
            List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
            int countTypes = eventTypes.size();
            for (int h = 0; h < countTypes; h++) {
                Class<?> clazz = eventTypes.get(h);
                subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
            }
        } else {
            // STEP3:若非继承性则直接派发
            subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
        }
        // STEP4:若未查找到事件,则派发没有订阅该事件
        if (!subscriptionFound) {
            if (logNoSubscriberMessages) {
                Log.d(TAG, "No subscribers registered for event " + eventClass);
            }
            if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                    eventClass != SubscriberExceptionEvent.class) {
                post(new NoSubscriberEvent(this, event));
            }
        }
    }

3.7、postToSubscription

   private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {
            case PostThread:
                invokeSubscriber(subscription, event);
                break;
            case MainThread:
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            case BackgroundThread:
                if (isMainThread) {
                    backgroundPoster.enqueue(subscription, event);
                } else {
                    invokeSubscriber(subscription, event);
                }
                break;
            case Async:
                asyncPoster.enqueue(subscription, event);
                break;
            default:
                throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
        }
    }

依据订阅方法的特性(解析其后缀)能得到需要在哪个线程中去接收该回调。postToSubscription恰恰是将对应的操作放到对应线程的策略方法。虽然其本身并没有什么神奇之处,但通过层层封装则会将客户端调用简化到足够神奇。

因为线程切换并不是本章的范畴,所以不展开对每个线程的调用分析。当前就以PostThread为根据进行后续分析。

3.8、invokeSubscriber

    void invokeSubscriber(Subscription subscription, Object event) {
        try {
            subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
        } catch (InvocationTargetException e) {
            handleSubscriberException(subscription, event, e.getCause());
        } catch (IllegalAccessException e) {
            throw new IllegalStateException("Unexpected exception", e);
        }
    }

我们观察到最终是通过Methodinvoke方法,完成对订阅方法(onEvent)的调用,并且传入的参数event

// Method.java
    public native Object invoke(Object receiver, Object... args)
            throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;

3.9 EventBus 总结

经过一大长段的代码分析,我们终于到了尾声。
1、EventBus 通过onEventXXX()的方法或 以@Subscribe注解形式,约定接收回调的方法。
2、外部调用post()方法,将参数event传入到注册中心EventBus
3、内部利用反射的 API,利用 Method反射方法method及参数event,最终能够调用约定接收回调的方法。

4 终章

无论项目的大小、复杂度如何,观察者的主线索其实一直很清晰——发布/订阅,这对于我们理解它真的很重要。

观察者模式的本质:触发联动。

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

推荐阅读更多精彩内容