Mvc淺析

定義

Modle是數(shù)據(jù)模型,用于操作數(shù)據(jù),一般結(jié)構(gòu)如下:

const M={
     data:{n:100},
     update(){},
     delete(){}
}

View是視圖,負責所有的UI頁面,一般結(jié)構(gòu)如下;

const V = {
    el: null,  //功能區(qū)域,可以先為空,然后通過傳參賦值
    template:
        `<div>html內(nèi)容</div>
      `,
    render() {
        $(this.el).html(this.template)
    }
}

Controller是控制器,負責其他內(nèi)容,一般為:

const C={
     init(container){
            V.el=container        
            V.render()
            this.bindEvents()
            this.bindEventHub()
     },
    bindEvents(){},//綁定事件
    bindEvnetHub(){}//綁定事件中心
}

最后一般會把C.init暴露出去作為接口給其他模塊調(diào)用。
最大的缺點是當有數(shù)據(jù)改變的時候,render()時很可能是從新渲染整個模塊,包括那些沒有改變的元素,性能較差。
優(yōu)點是可以實現(xiàn)代碼的模塊化管理,各個模塊之間互不影響。

意義

讓我從面條式代碼過渡到框架式代碼的基石,主要是為了DRY(Don't Repeat Yourself)原則
不要讓一個代碼重復(fù)2-3次,
也不要做一個類似頁面做>10次
所以就要學(xué)會封裝+造輪子

指導(dǎo)思想

抽象思維,也就是盡量少的在模塊中引入別的東西

過程

一開始:
html,css,js
然后:
html,js(想辦法把css用別的替代)
最后
js(把html塞進js)

代價:

如果網(wǎng)速慢頁面會空白,無內(nèi)容/無樣式

解決方法

加一些加載圖片,使得用戶在等的時候不會很煩,也就是俗稱加菊花

MVC具體思維

就是把所有的東西分別用一個M,一個V,一個C對象來分類解決

MVC好處

就是可以恒定復(fù)雜度,寫一行是這么寫,寫一百行也是這么寫


mvc和dom思維的差別

MVC是讓數(shù)據(jù)流向全部都是從JS流到html中,dom則是從html到j(luò)s,再從js到html

表驅(qū)動編程是什么

  • 將所有的數(shù)據(jù)方法放在表中,直接在表中查找,而不必使用邏輯語句,好處是簡單直接。
  • 它的本質(zhì)是,從表里查詢信息來代替邏輯語句

以下一段代碼,通過if,else來判斷月份,給出當月的天數(shù)。

if (month === 1) {
    return 31;
} else if (month == 2) {
    return 28;
}else if (month == 3) {
    return 31;
}
...
else if (month == 12) {
    return 31;
} else {
    return 0;
}</pre>

上述代碼冗長復(fù)雜,我們可以把它改寫為

array daysPerMonth = [31,28,31,30,31,30,31,31,30,31,30,31];
days = daysPerMonth[month - 1];

這樣就簡潔明了了。

優(yōu)點:

  1. 更加易讀和直白;
  2. 用數(shù)據(jù)代替邏輯,容易維護;
  3. 可以把表中的數(shù)據(jù)存放在文件中,運行時讀取,減少代碼體量。數(shù)據(jù)變更時只需要修改文件;
  4. 降低復(fù)雜度。

以上只是一個最簡單的例子,MVC中我們把一個個數(shù)據(jù)和方法放在了表中,同樣實現(xiàn)了這些優(yōu)點。

MVC不得不提到的重要屬性

EventBus

這是官方的圖。


EventBus-Publish-Subscribe

作用

解耦

實際上他就是解耦了事件的發(fā)送者和接收者,意思就是我們稱subscriber為細節(jié)層,EventBus為膠水層,膠水層是為了隔絕細節(jié)的,如果沒有膠水層,萬一哪天我們不依賴這個publisher整出來的東西,我們就要把所有的細節(jié)層一一修改,但現(xiàn)在我們把依賴publisher整出來的東西搞到EventBus,使得細節(jié)層依賴膠水層,膠水層則依賴publisher,如果要改直接改膠水層即可,這樣工作量大大減小。避免了復(fù)雜的、易于出錯的依賴及生命周期問題,可以使我們的代碼更加簡潔、健壯。

簡化機制

EventBus是一個 發(fā)布/訂閱 模式的消息總線庫,它簡化了應(yīng)用程序內(nèi)各組件間、組件與后臺線程間的通信

在不使用EventBus的情況下,我們也可能會使用諸如 Observable/Observer 這樣得一些機制來處理事件的監(jiān)聽/發(fā)布。如果在我們的應(yīng)用程序中,有許多地方需要使用事件的監(jiān)聽/發(fā)布,則我們應(yīng)用程序的整個結(jié)構(gòu)可能就會像下面這個樣子:

image

每一處需要用到事件的監(jiān)聽/發(fā)布的地方,都需要實現(xiàn)一整套監(jiān)聽/發(fā)布的機制,比如定義Listener接口,定義Notification Center/Observable,定義事件類,定義注冊監(jiān)聽者的方法、移除監(jiān)聽者的方法、發(fā)布事件的方法等。我們不得不寫許多繁瑣的,甚至常常是重復(fù)的冗余的代碼來實現(xiàn)我們的設(shè)計目的。

引入EventBus庫之后,事件的監(jiān)聽/發(fā)布將變得非常簡單,我們應(yīng)用程序的結(jié)構(gòu)也將更加簡潔,會如下面這樣:

image

我們可以將事件監(jiān)聽者的管理,注冊監(jiān)聽者、移除監(jiān)聽者,事件發(fā)布等方法等都交給EventBus來完成,而只定義事件類,實現(xiàn)事件處理方法即可。

過程

1、首先,定義要傳遞的事件實體

public class CollectEvent { ... }

2、準備訂閱者:聲明并注解你的訂閱方法

@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(CollectEvent event) {
    LogHelper.d("OK");
}

在2中,也就是訂閱中所在的類中,注冊和解注冊你的訂閱者

@Override
public void onStart() {
    super.onStart();
    EventBus.getDefault().register(this);
}

@Override
public void onStop() {
    super.onStop();
    EventBus.getDefault().unregister(this);
}

4、發(fā)送事件

EventBus.getDefault().post(new CollectEvent());

每一過程中重要的api

這部分借鑒了蕉下孤客的文章
其實我對于源碼還是很不熟悉,這個借鑒是為了以后邊用邊學(xué)可以方便查詢和學(xué)習(xí)

1. 構(gòu)造器

EventBus的構(gòu)造方式是典型的建造者模式,首先看我們獲取EventBus最常用的方法,getDefault()

/** Convenience singleton for apps using a process-wide EventBus instance. */
public static EventBus getDefault() {
    if (defaultInstance == null) {
        synchronized (EventBus.class) {// synchronized是同步的意思
            if (defaultInstance == null) {
                defaultInstance = new EventBus();
            }
        }
    }
    return defaultInstance;
}

如注釋所說,這里是傳統(tǒng)的單例模式,通常在一個應(yīng)用中使用同一個EventBus即可,這樣方便事件的處理,同時EventBus對事件的注冊和調(diào)度方法都是線程安全的,我們在后面就會看到,所以單例非常適合。接下來看默認構(gòu)造器:

 public EventBus() {
        this(DEFAULT_BUILDER);
    }

DEFAULT_BUILDER是一個靜態(tài)的構(gòu)造者,其中包含一系列的默認屬性,最后就是以建造者為參數(shù)的構(gòu)造器,過程就是一系列的參數(shù)初始化,代碼如下:

EventBus(EventBusBuilder builder) {
        subscriptionsByEventType = new HashMap<>();  //以事件類的class對象為鍵值,記錄注冊方法信息,值為一個Subscription的列表
        typesBySubscriber = new HashMap<>();   //以注冊的類為鍵值,記錄該類所注冊的所有事件類型,值為一個Event的class對象的列表
        stickyEvents = new ConcurrentHashMap<>();  //記錄sticky事件
        //三個Poster, 負責在不同的線程中調(diào)用訂閱者的方法
        mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);//mainThread主線程
        backgroundPoster = new BackgroundPoster(this);
        asyncPoster = new AsyncPoster(this);//async異步
        ...
        //方法的查找類,用于查找某個類中有哪些注冊的方法
        subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
                builder.strictMethodVerification, builder.ignoreGeneratedIndex);
        ...
        //后面是一些表示開關(guān)信息的boolean值以及一個線程池
    }

這里省略了一部分暫時沒有分析到的參數(shù)。首先來看管理注冊信息的兩個Map, 由于一個類中可能有多個方法監(jiān)聽多個事件,所以Subscription這個類封裝一個注冊信息,這個類很簡單,只有三個屬性,如下:

final class Subscription {
    final Object subscriber;
    final SubscriberMethod subscriberMethod;
    /**
     * Becomes false as soon as {@link EventBus#unregister(Object)} is called, which is checked by queued event delivery
     * {@link EventBus#invokeSubscriber(PendingPost)} to prevent race conditions.
     */
    volatile boolean active;

    //之后為構(gòu)造器以及覆寫的equal和hashCode方法。
    ...
}

public class SubscriberMethod {
    final Method method;
    final ThreadMode threadMode;
    final Class<?> eventType;
    final int priority;
    final boolean sticky;
    /** Used for efficient comparison */
    String methodString;

    //之后為構(gòu)造器以及覆寫的equal和hashCode方法。
    ...

所以類和方法唯一確定一條注冊信息,active表示該注冊信息是否有效(如注釋所說它的作用)。所以在EventBus中,使用subscriptionsByEventType,以Event的class對象為鍵值,管理注冊信息,值為一個處理事件類型為該鍵值的Subscription的列表。typesBySubscriber則相對簡單,就是記錄一個類注冊了哪些事件類型。雖然二者有所冗余,它們在后面的注冊和調(diào)度過程中都是為了便于查詢,這樣更為高效。
然后是stickyEvents,是一個線程安全的Map,用來記錄sticky事件,sticky事件的含義是指即使被觀察者發(fā)送sticky事件是在訂閱者訂閱該事件之前,訂閱者在訂閱之后,EventBus將該事件發(fā)送到該訂閱者,即調(diào)用相應(yīng)的訂閱方法。(如果在之后,那就和普通事件一樣)
接下來的三個Poster極為重要,但是這里理解很容易,就是負責在不同線程中調(diào)用方法,他們分別對應(yīng)著threadMode中除去POSTING之外三種類型。
最后是一個Finder,由于在EventBus的使用簡便,都是以對象為單位調(diào)用registe()方法,但是一個對象中可能有多個注冊方法,所以注冊過程中需要subscriberMethodFinder查找一個類中有哪些注冊方法,最后生成一個Subscrip,并存在subscriptionsByEventType的某一個列表中。

EventBus的構(gòu)造器很簡單,這里重點講述了一下其中的幾個比較重要的成員變量,尤其是管理注冊信息的數(shù)據(jù)結(jié)構(gòu),下一步就是注冊源碼的分析。

2. 注冊

注冊方法rigister()的代碼如下:

public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

思路很明確,兩個步驟,一是查找注冊方法的列表,這個利用到了SubscriberMethodFinder的對應(yīng)方法,查找一個類中有哪些注冊方法,而是調(diào)用訂閱方法,參數(shù)為類和方法兩個,即subscriber和subscriberMethod。查找我們留到下一部分,先看訂閱方法:

 // Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    Class<?> eventType = subscriberMethod.eventType;
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
        if (subscriptions.contains(newSubscription)) {
            throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                    + eventType);
        }
    }

    int size = subscriptions.size();
    for (int i = 0; i <= size; i++) {
        if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
            subscriptions.add(i, newSubscription);
            break;
        }
    }

    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    subscribedEvents.add(eventType);

    if (subscriberMethod.sticky) {
        if (eventInheritance) {
            // Existing sticky events of all subclasses of eventType have to be considered.
            // Note: Iterating over all events may be inefficient with lots of sticky events,
            // thus data structure should be changed to allow a more efficient lookup
            // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
            Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
            for (Map.Entry<Class<?>, Object> entry : entries) {
                Class<?> candidateEventType = entry.getKey();
                if (eventType.isAssignableFrom(candidateEventType)) {
                    Object stickyEvent = entry.getValue();
                    checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                }
            }
        } else {
            Object stickyEvent = stickyEvents.get(eventType);
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }
    }
}

代碼雖然有點長,但是邏輯很簡單清晰,就是構(gòu)建Subscription, 然后將該注冊信息保存到兩個數(shù)據(jù)結(jié)構(gòu)中。對于subscriptionsByEventType首先獲取EventType對應(yīng)的列表,沒有則創(chuàng)建,重復(fù)注冊則拋異常,正常情況下,則根據(jù)priority插入到列表中適合的位置。對于typesBySubscriber,則是更新該subscriber對應(yīng)的列表即可。最后是處理sticky事件,即在注冊時,如果是監(jiān)聽sticky事件,則需要從stickyEvents中取出對應(yīng)sticky事件,并發(fā)送到訂閱者。這里需要注意eventInheritance是一個開關(guān),表示是否處理EventType的繼承關(guān)系,默認為true,如代碼中,EventBus會向訂閱者發(fā)送該類型的事件,以及該類型所有子類類型的事件。由于訂閱者監(jiān)聽一個sticky事件,那么該sticky事件的子類型也可以認為是該類型的事件,所以訂閱者也同樣會接收到該事件。最后checkPostStickyEventToSubscription方法如下:

private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
    if (stickyEvent != null) {
        // If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)
        // --> Strange corner case, which we don't take care of here.
        postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());
    }
}

代碼很簡單,至于如何發(fā)送,放到第三部分統(tǒng)一分析,注釋中說的訂閱者不能終止sticky事件的發(fā)送,至于posting state則同樣放在但三部分說明。

注冊過程就是這些,還有一個重要且稍微復(fù)雜的查找過程放在第二篇中分析,在分析事件分發(fā)之前,與注冊相反的unrigister由于比較簡單,這里一并說了,下面是其代碼:

 /** Unregisters the given subscriber from all event classes. */
public synchronized void unregister(Object subscriber) {
    List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {
        for (Class<?> eventType : subscribedTypes) {
            unsubscribeByEventType(subscriber, eventType);
        }
        typesBySubscriber.remove(subscriber);
    } else {
        Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}

邏輯很明確,調(diào)用unsubscribeByEventType方法,并更新typesBySubscriber數(shù)據(jù)結(jié)構(gòu),那么你可能猜到了,unsubscribeByEventType方法中就是更新另一個數(shù)據(jù)結(jié)構(gòu),代碼如下:

/** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. */
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
    List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions != null) {
        int size = subscriptions.size();
        for (int i = 0; i < size; i++) {
            Subscription subscription = subscriptions.get(i);
            if (subscription.subscriber == subscriber) {
                subscription.active = false;
                subscriptions.remove(i);
                i--;
                size--;
            }
        }
    }
}

這里邏輯很簡單,但是有一點值得注意的是就遍歷刪除列表時,注意序號i和size的改變,容易出現(xiàn)數(shù)組越界的錯誤,遍歷刪除通常使用倒序的方式,不容易出現(xiàn)錯誤,這里動態(tài)改變size變量也是一樣。注銷過程需要考慮sticky事件,也不需要查找過程,所以過程很簡單。
register和unregister的過程除了查找訂閱方法以外,邏輯很簡單,就是更新一下管理注冊信息的數(shù)據(jù)結(jié)構(gòu),可以看出數(shù)據(jù)結(jié)構(gòu)的設(shè)計還是很重要的,設(shè)計的好,邏輯就會很清晰,程序代碼也就相對簡潔易懂。下一步就是介紹事件的分發(fā)。

3. 事件分發(fā)

在分析post()方法之前,先看EventBus的一個內(nèi)部類,PostState:

/** For ThreadLocal, much faster to set (and get multiple values). */
final static class PostingThreadState {
    final List<Object> eventQueue = new ArrayList<Object>();
    boolean isPosting;
    boolean isMainThread;
    Subscription subscription;
    Object event;
    boolean canceled;
}

這個內(nèi)部類只有幾個屬性變量,其中我們現(xiàn)在只需要注意第一個,事件消息隊列即可。如注釋所言,這個類的實例對象在EventBus中是一個ThreadLocal變量,即線程本地變量,不同線程之間不會相互影響,而eventQueue則是用來保存當前線程需要發(fā)送的事件(為什么會有隊列,是因為POST線程也就是調(diào)用post()方法的線程與調(diào)用訂閱者方法的線程不同,在POST線程中連續(xù)的調(diào)用post()方法發(fā)送事件,會造成事件的累積)。后面的四個變量都是與cancelEventDelivery()方法有關(guān),在后面對其進行分析。
下面為post()方法的代碼:

/** Posts the given event to the event bus. */
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;
        }
    }
}

首先需要明確的是,如注釋所言,post()的方法是將事件發(fā)送到EventBus,至于何時調(diào)用訂閱者的方法則有EventBus調(diào)度。從代碼中看postingState是一個ThreadLocal,用于保存當前post事件的狀態(tài)。post()方法就是設(shè)置一些postState的屬性,然后遍歷事件消息隊列,調(diào)用postSingleEvent()方法,其代碼如下:

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    Class<?> eventClass = event.getClass();
    boolean subscriptionFound = false;
    if (eventInheritance) {
        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 {
        subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
    }
    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));
        }
    }
}

這個方法中同樣也用到了eventInheritance這個開關(guān),即是否考慮Event事件類型的繼承關(guān)系,默認為true,這里的lookupAllEventTypes()方法是EventBus的靜態(tài)方法,查找eventClass所有包括自己在內(nèi)的父類以及它們所實現(xiàn)的接口,然后對于沒有eventClass,調(diào)用postSingleForEventType()方法,返回的結(jié)果為是否找到了對應(yīng)的訂閱方法,在沒有找到的情況下,會做出打印Log信息和發(fā)送事件處理,這里的logNoSubscriberMessages和sendNoSubscriberEvent是EventBus的開關(guān)屬性,與eventInheritance類似,也可以在Builder中設(shè)置,默認為true。如果當我們調(diào)用post()方法發(fā)出某個事件時想知道我們的事件有沒有被訂閱者接收,就可以在發(fā)送消息的類中接收NoSubscriberEvent事件,如果收到該事件說明應(yīng)用中沒有訂閱者接收我們發(fā)出的事件。
這里在看源碼時有一點點小疑問,就是在注冊時處理sticky事件時是找到Event的所有子類并發(fā)送給該訂閱者,而這里是Event的所有父類,并將其發(fā)送出去。前者是站在訂閱者的角度上,訂閱者在注冊時要求接收某個sticky事件,那么該事件的所有子類也是該sticky事件的一種,所以應(yīng)該發(fā)送給該訂閱者。比如一個訂閱者訂閱天氣預(yù)報的sticky事件,那么如果在stickyEvent中有一個今天下雨的事件(假設(shè)該事件繼承自天氣預(yù)報,隨便想的例子,可能不太恰當)也應(yīng)該發(fā)送給該訂閱者。而此處postSingleEvent方法中是發(fā)送單個Event,是站在發(fā)送者發(fā)送到EventBus的角度上,我需要發(fā)送某個事件通知訂閱者,比如我發(fā)送一個天氣預(yù)報的事件到EventBus, 但是EventBus由于不知道天氣預(yù)報是否表示下雨,就不應(yīng)該通知哪些是否下雨事件的訂閱者,相反那些監(jiān)聽新聞的訂閱者(假設(shè)天氣預(yù)報繼承自新聞,而天氣預(yù)報是一條新聞),EventBus需要負責通知他們這條天氣預(yù)報的新聞。被觀察者沒有將新聞發(fā)送到EventBus上,但是EventBus則因為一條天氣預(yù)報需要找出新聞并發(fā)送到相應(yīng)的訂閱者上,所以發(fā)送是兩個過程。接下來是postSingleEventForEventType方法,其代碼如下:

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
    CopyOnWriteArrayList<Subscription> subscriptions;
    synchronized (this) {
        subscriptions = subscriptionsByEventType.get(eventClass);
    }
    if (subscriptions != null && !subscriptions.isEmpty()) {
        for (Subscription subscription : subscriptions) {
            postingState.event = event;
            postingState.subscription = subscription;
            boolean aborted = false;
            try {
                postToSubscription(subscription, event, postingState.isMainThread);
                aborted = postingState.canceled;
            } finally {
                postingState.event = null;
                postingState.subscription = null;
                postingState.canceled = false;
            }
            if (aborted) {
                break;
            }
        }
        return true;
    }
    return false;
}

這里首先說明CopyOnWriteArrayList是為了線程安全,每次對List的修改都會重新一份,由于是線程安全的所以不需要同步處理,但是對HashMap的讀取操作則不是線程安全的,所以需要線程同步。這個方法的邏輯也很簡單,就是從subscriptionsByEventType中找出事件類型對應(yīng)的注冊信息列表,然后遍歷調(diào)用postToSubscription()方法,這個方法有些熟悉了,就是在注冊中處理sticky事件時調(diào)用的方法。不過這里需要注意的是abort每次都會讀取postingState的cancel狀態(tài)判斷發(fā)送事件是否被終止,而另外兩個遍歷 event和subscription的賦值和清空也是為了用于cancelEventDelivery()方法,后面會統(tǒng)一說。最后終于到了postToSubscription()方法,其代碼為:

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
    switch (subscription.subscriberMethod.threadMode) {
        case POSTING:
            invokeSubscriber(subscription, event);
            break;
        case MAIN:
            if (isMainThread) {
                invokeSubscriber(subscription, event);
            } else {
                mainThreadPoster.enqueue(subscription, event);
            }
            break;
        case BACKGROUND:
            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);
    }
}

這里就到了真正的事件分發(fā)了,分為我們所熟知的四種threadModed的情形,其代碼邏輯很清晰,根據(jù)threadMode以及isMainThread選擇調(diào)用invokeSubscriber方法還是加入相應(yīng)的隊列,異步執(zhí)行。關(guān)于入隊異步執(zhí)行放在第三篇中講述,重點分析三個Poster的實現(xiàn),這里先貼出invokeSubscriber的代碼,其實就是利用反射調(diào)用訂閱者的方法,方法存儲在subsription的subsribeMethod變量中:

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

到這里事件的分發(fā)就結(jié)束了,梳理一下就是一下流程:1. post(Object event) -> 2. postSingleEvent(Object event, PostingThreadState postingState) -> 3. postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) -> 4. postToSubscription(Subscription subscription, Object event, boolean isMainThread).
其中第一步是遍歷一個線程本地變量中保存的事件消息隊列的所有消息,第二步是遍歷一個事件類型的所有父類,第三步是遍歷一個事件類型的所有注冊信息,第四步則是事件分發(fā),根據(jù)threadMode選擇合適的處理方式。
讀到這里就感覺優(yōu)秀代碼的確是邏輯結(jié)構(gòu)十分清晰,看起來一目了然,在一個任務(wù)中合理的劃分步驟,拆分成多個方法,讓人更容易理解。

接下來就是前面一直提到的cancelEventDelivery方法:

/**
 * Called from a subscriber's event handling method, further event delivery will be canceled. Subsequent
 * subscribers
 * won't receive the event. Events are usually canceled by higher priority subscribers (see
 * {@link Subscribe#priority()}). Canceling is restricted to event handling methods running in posting thread
 * {@link ThreadMode#POSTING}.
 */
public void cancelEventDelivery(Object event) {
    PostingThreadState postingState = currentPostingThreadState.get();
    if (!postingState.isPosting) {
        throw new EventBusException(
                "This method may only be called from inside event handling methods on the posting thread");
    } else if (event == null) {
        throw new EventBusException("Event may not be null");
    } else if (postingState.event != event) {
        throw new EventBusException("Only the currently handled event may be aborted");
    } else if (postingState.subscription.subscriberMethod.threadMode != ThreadMode.POSTING) {
        throw new EventBusException(" event handlers may only abort the incoming event");
    }

    postingState.canceled = true;
}

這里重點先看一下注釋,這個方法的作用就是在事件處理方法中調(diào)用,終止事件的進一步傳遞,這個和android系統(tǒng)中的順序廣播是相同的道理(即通過提高自己的的優(yōu)先權(quán)可以率先收到事件,然后截取該事件)。但是這里有一個條件就是事件處理方法的threadMode必須是POSTING,即執(zhí)行線程與事件的發(fā)送線程為同一個線程。
從代碼中可以看出終止事件條件十分苛刻,PostState中的三個變量都是用來判斷是否可以終止事件的,第一個是isPosting,該變量在post方法中被設(shè)置true,表示一個事件類型及其父類正在被發(fā)送的狀態(tài)中,發(fā)送完畢以后才被設(shè)置為false。所以在這個過程以外都不可以調(diào)用該方法。最后兩個條件postSingleEventForEventType中被設(shè)置的,對于一個事件類型(不包括其父類)遍歷它的所有注冊信息,針對每一個注冊信息調(diào)用postToSubscription方法之前和之后這兩個變量都會被設(shè)置,就是為了在這里判斷,是否可以在某一個注冊信息的方法被調(diào)用時終止這個事件的繼續(xù)發(fā)送。(這里有一點不明白,一個事件的所有注冊信息遍歷時,事件是同一個,為什么在遍歷過程中都要賦值和清空,這里是否可以改成設(shè)置一次和清空一次即可?)。最后在設(shè)置了canceled變量以后(如果cancelEventDelivery在事件處理方法中被調(diào)用了),事件處理方法返回之后,postToSubscription接著返回,canceled被賦值到了abort變量,這時候abort如果為true, 則break跳出循環(huán),從而終止了該事件類型的繼續(xù)發(fā)送到其他的注冊信息。(注意這里不會影響其他事件類型,如其父類事件等)

看到這里也就明白了為什么限制在threadMode為POSTING的事件處理方法中調(diào)用cancelEventDelivery方法了,這是因為post()之后的一系列方法是在事件發(fā)送的線程中執(zhí)行,而這些狀態(tài)字的賦值與判斷必須處在同一線程中才能有效,所以事件處理方法必須與post()方法處在同一線程,所以也就只能是POSTING模式下才能保證。

最后invokeSubscriber方法中在調(diào)用訂閱者方法失敗時有一個異常處理方法,其代碼如下:

private void handleSubscriberException(Subscription subscription, Object event, Throwable cause) {
    if (event instanceof SubscriberExceptionEvent) {
        if (logSubscriberExceptions) {
            // Don't send another SubscriberExceptionEvent to avoid infinite event recursion, just log
            Log.e(TAG, "SubscriberExceptionEvent subscriber " + subscription.subscriber.getClass()
                    + " threw an exception", cause);
            SubscriberExceptionEvent exEvent = (SubscriberExceptionEvent) event;
            Log.e(TAG, "Initial event " + exEvent.causingEvent + " caused exception in "
                    + exEvent.causingSubscriber, exEvent.throwable);
        }
    } else {
        if (throwSubscriberException) {
            throw new EventBusException("Invoking subscriber failed", cause);
        }
        if (logSubscriberExceptions) {
            Log.e(TAG, "Could not dispatch event: " + event.getClass() + " to subscribing class "
                    + subscription.subscriber.getClass(), cause);
        }
        if (sendSubscriberExceptionEvent) {
            SubscriberExceptionEvent exEvent = new SubscriberExceptionEvent(this, cause, event,
                    subscription.subscriber);
            post(exEvent);
        }
    }
}

代碼邏輯很簡單,通常由于是發(fā)送的是我們自定義的Event, 所以會走else, 接著就會根據(jù)開關(guān)拋異常打log以及發(fā)送事件等,讀一下這段代碼也有利于我們以后調(diào)試有關(guān)EventBus的相關(guān)問題。

至此事件分發(fā)邏輯就分析結(jié)束了。下一步簡單介紹和sticky事件相關(guān)的幾個方法。

4. sticky事件

在注冊部分我們提到過sticky事件,即在訂閱時即可以接收到之前post出去的sticky事件以及其子類事件,下面為postSticky()方法:

/**
 * Posts the given event to the event bus and holds on to the event (because it is sticky). The most recent sticky
 * event of an event's type is kept in memory for future access by subscribers using {@link Subscribe#sticky()}.
 */
public void postSticky(Object event) {
    synchronized (stickyEvents) {
        stickyEvents.put(event.getClass(), event);
    }
    // Should be posted after it is putted, in case the subscriber wants to remove immediately
    post(event);
}

之前可能對于sticky的解釋不太清楚,不過這個方法的注釋則對sticky事件的解釋很清晰,淺顯易懂。接著需要注意方法中的那句注釋,就是需要先將sticky事件保存到stickyEvents中在調(diào)用post()方法,是為了防止remove失敗,如在事件處理方法中調(diào)用removeStickyEvent,remove在put之前則會造成remove失敗,post方法返回以后event又被添加到stickyEvents,與我們期望的就有差別了。接下來是sticky事件的remove方法:

 /**
 * Remove and gets the recent sticky event for the given event type.
 *
 * @see #postSticky(Object)
 */
public <T> T removeStickyEvent(Class<T> eventType) {
    synchronized (stickyEvents) {
        return eventType.cast(stickyEvents.remove(eventType));
    }
}

/**
 * Removes the sticky event if it equals to the given event.
 *
 * @return true if the events matched and the sticky event was removed.
 */
public boolean removeStickyEvent(Object event) {
    synchronized (stickyEvents) {
        Class<?> eventType = event.getClass();
        Object existingEvent = stickyEvents.get(eventType);
        if (event.equals(existingEvent)) {
            stickyEvents.remove(eventType);
            return true;
        } else {
            return false;
        }
    }
}
/**
 * Removes all sticky events.
 */
public void removeAllStickyEvents() {
    synchronized (stickyEvents) {
        stickyEvents.clear();
    }
}

這幾個方法都比較容易理解,不再解釋了,最后關(guān)于sticky還有一個get方法,如下:

/**
 * Gets the most recent sticky event for the given type.
 *
 * @see #postSticky(Object)
 */
public <T> T getStickyEvent(Class<T> eventType) {
    synchronized (stickyEvents) {
        return eventType.cast(stickyEvents.get(eventType));
    }
}

最后貼出來兩個靜態(tài)方法,就是前面提到的查找事件類型和它所有父類以及它們的實現(xiàn)的所有接口的方法,對于過程的分析無關(guān)緊要,有興趣的可以看一下源碼,如下:

/** Looks up all Class objects including super classes and interfaces. Should also work for interfaces. */
private static List<Class<?>> lookupAllEventTypes(Class<?> eventClass) {
    synchronized (eventTypesCache) {
        List<Class<?>> eventTypes = eventTypesCache.get(eventClass);
        if (eventTypes == null) {
            eventTypes = new ArrayList<>();
            Class<?> clazz = eventClass;
            while (clazz != null) {
                eventTypes.add(clazz);
                addInterfaces(eventTypes, clazz.getInterfaces());
                clazz = clazz.getSuperclass();
            }
            eventTypesCache.put(eventClass, eventTypes);
        }
        return eventTypes;
    }
}

/** Recurses through super interfaces. */
static void addInterfaces(List<Class<?>> eventTypes, Class<?>[] interfaces) {
    for (Class<?> interfaceClass : interfaces) {
        if (!eventTypes.contains(interfaceClass)) {
            eventTypes.add(interfaceClass);
            addInterfaces(eventTypes, interfaceClass.getInterfaces());
        }
    }
}

模塊化

MVC設(shè)計思想,EventBus,以及表驅(qū)動編程都在一定程度上幫助我們實現(xiàn)了程序設(shè)計的高內(nèi)聚低耦合。那么提到高內(nèi)聚低耦合,就不得不提模塊化。

隨著前端工程規(guī)模日益龐大,功能也愈加復(fù)雜,也就自然而然的提出了模塊化的需求。

在過去需求簡單的時候,我們可能僅僅需要引入jQuery一個庫,就可以完成日常的開發(fā)工作。這樣減少了網(wǎng)絡(luò)請求,提高了加載速度,但這也限制了我們所編寫代碼的組織方法。隨著Node.js的流行,webpack等打包工具的出現(xiàn),模塊化開發(fā)也成為了前端開發(fā)的主流。

通過模塊化開發(fā),我們可以保持命名空間的隔離。在前端開發(fā)中,我們不能像后端一樣控制程序的使用環(huán)境,瀏覽器插件及廣告等很有可能污染運行環(huán)境,造成命名沖突等種種問題,通過模塊化開發(fā),我們可以降低這種可能。

我們也可以通過模塊化來實現(xiàn)開發(fā)時的關(guān)注點分離,同時也使得單元測試更加方便。在理想情況下,我們可以只關(guān)注模塊內(nèi)的代碼,并為其編寫測試用例而不用擔心影響模塊外的內(nèi)容。通過把大型應(yīng)用拆解成模塊,我們可以降低應(yīng)用的復(fù)雜度,使得功能的添加與修改更加方便。

通過模塊化開發(fā)與打包工具的結(jié)合,我們還可以減少代碼體積。假如我們需要使用lodash中的某個方法,我們可以不必想以前一樣引入整個lodash庫,而是可以按需引入,僅僅導(dǎo)出我們所需要的功能,這可以在相當程度上提高我們的開發(fā)效率,減少代碼體積。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,006評論 2 374

推薦閱讀更多精彩內(nèi)容