觀察者模式(觸發聯動)

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 終章

無論項目的大小、復雜度如何,觀察者的主線索其實一直很清晰——發布/訂閱,這對于我們理解它真的很重要。

觀察者模式的本質:觸發聯動。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容