手撕EventBus

思惟導圖

使用方法

app下的build.gradledependencies中進行引入,固然高版本也容易出現問題。java

implementation 'org.greenrobot:eventbus:3.2.0'
複製代碼

使用三步驟:設計模式

(1) 定義事件緩存

public class MessageEvent {
    public final String message;
 
    public MessageEvent(String message) {
        this.message = message;
    }
}
複製代碼

(2)定義註冊和註銷app

// 當對應的消息發送到時會被調用的方法 
// 根據對應的實體類來對應處理的方式
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
    Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show();
}

@Subscribe
public void handleSomethingElse(SomeOtherEvent event) {
    doSomethingWith(event);
}
// 生命週期start中註冊
@Override
public void onStart() {
    super.onStart();
    EventBus.getDefault().register(this);
}
// 生命週期stop中註銷
@Override
public void onStop() {
    EventBus.getDefault().unregister(this);
    super.onStop();
}
複製代碼

(3)發送消息框架

EventBus.getDefault().post(new MessageEvent("Hello everyone!"));
複製代碼

源碼導讀

每次在看源碼以前,咱們都須要乾的事情是知道這個東西要幹什麼。 EventBus做爲消息的發佈/訂閱總線框架,最終基於的仍是一個觀察者模式。而方法將是咱們對源碼的整個切入點,因此咱們要知道他能幹什麼,才知道怎麼去讀源碼。異步

(1)Subscribe註解的使用ide

(2)註冊和註銷函數

(3)發送和處理事件post

(4)粘性事件學習

Subscribe註解的使用

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
    // 線程的模式,默認爲POSTING
    ThreadMode threadMode() default ThreadMode.POSTING;
    // 是否堅持粘性事件,默認不支持
    boolean sticky() default false;
    // 一個優先級標識,默認爲0
    int priority() default 0;
}
複製代碼

關於上述中的線程的模式也是大有文章。

public enum ThreadMode {
    // 在當前的線程中直接處理。
    POSTING,
    // 在主線程中發送事件,則直接處理,可是要求處理量小;在子線程中,就經過Handler發送後,再由主線程處理
    MAIN,
    // 老是入隊列進行等待,經過Handler發送事件後,再由主線程處理
    MAIN_ORDERED,
    // 在主線程中發送事件,入隊列依次處理;在子線程中發送事件,則直接處理。
    BACKGROUND,
    // 老是入隊列等待,經過線程池異步處理。
    ASYNC
}
複製代碼

註冊和註銷

EventBus.getDefault().register(this);
EventBus.getDefault().unregister(this);
複製代碼

註銷

通常來講麻煩的都是註冊的過程,因此咱們反其道而行之,咱們先看註銷。

public synchronized void unregister(Object subscriber) {
        // 得到context對應的訂閱事件的參數類型集合
        List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
        if (subscribedTypes != null) {
            // 循環遍歷,刪去對應的數據
            for (Class<?> eventType : subscribedTypes) {
                unsubscribeByEventType(subscriber, eventType);
            }
            // 本質就是一個map,而context就是一個key,刪去key罷了。
            typesBySubscriber.remove(subscriber);
        } else {
            logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
        }
    }
複製代碼

註冊

上面講過了註銷,當前就是重頭戲的註冊了。 在整個的代碼中,其實我尚未介紹getDefault()這個方法,因此先看看這個方法。

static volatile EventBus defaultInstance;

public static EventBus getDefault() {
        EventBus instance = defaultInstance;
        if (instance == null) {
            synchronized (EventBus.class) {
                instance = EventBus.defaultInstance;
                if (instance == null) {
                    instance = EventBus.defaultInstance = new EventBus();
                }
            }
        }
        return instance;
    }
複製代碼

DCL不清楚的讀者能夠看看聊一聊設計模式(二)-- 建立型設計模式

很輕鬆,就是一個DCL的方式來建立一個單例,那麼這個時候咱們也就擁有了EventBus的一個實例了,天然就能註冊了。

public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        // 其實就是尋找到帶有@Subscribe註解的方法集合
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            // 同步方式完成訂閱事件的綁定
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }
複製代碼

從上述的代碼中,咱們知道他的大體流程其實就是分爲兩步,查詢和註冊。

查詢

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        // 先是從緩存中取,有則直接返回,無則尋找
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if (subscriberMethods != null) {
            return subscriberMethods;
        }
        // 由於咱們通常使用的都是getDefault()來進行一個註冊,因此ignoreGeneratedIndex默認爲false。去查看EventBusBuilder便可,會看到未賦值,也就是默認值的false
        if (ignoreGeneratedIndex) {
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
            // 默認調用的方法
            subscriberMethods = findUsingInfo(subscriberClass); // 1
        }
        if (subscriberMethods.isEmpty()) {
            throw new EventBusException("Subscriber " + subscriberClass
                    + " and its super classes have no public methods with the @Subscribe annotation");
        } else {
            // 對查詢到的數據進行一個緩存
            METHOD_CACHE.put(subscriberClass, subscriberMethods);
            return subscriberMethods;
        }
    }
複製代碼

這個工做流程是很是清晰的,咱們的下一步就是要進入findUsingInfo()這個函數。

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
            findState.subscriberInfo = getSubscriberInfo(findState);
            if (findState.subscriberInfo != null) {
                SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
                for (SubscriberMethod subscriberMethod : array) {
                    if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                        findState.subscriberMethods.add(subscriberMethod);
                    }
                }
            } else {
                // 通常進入這個方法,經過反射的方式判斷方法使用是否知足一些基本條件
                // 是不是public修飾,是否爲一個參數,是否爲靜態,是否爲抽象類
                findUsingReflectionInSingleClass(findState);
            }
            // 對subscriberClass的父類再進行查詢
            findState.moveToSuperclass();
        }
        // 對遍歷得到的方法進行重構
        // 對findState進行回收
        return getMethodsAndRelease(findState);
    }
複製代碼

到此爲止咱們就可以得到咱們打過註解的方法們了。

註冊

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        Class<?> eventType = subscriberMethod.eventType;
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        // 若是subscriptionsByEventType中並不存在對應的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;
            }
        }
        // 以context做爲key,來存儲訂閱事件
        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);
        // 對粘性事件的一個具體操做
        if (subscriberMethod.sticky) {
           // 。。。。。。
        }
    }
複製代碼

這麼一大串看下來,想來不少人很跳過,畢竟代碼真的太多了。其實仍是同樣抽主幹,它針對的變量只有兩個typesBySubscribersubscriptionsByEventType。爲何這麼說的?就是一個反推的思想,註冊中要保存的東西,在註銷的時候是必定要刪除的,那咱們去看看註銷就知道了。

發送和處理事件

發送事件

EventBus.getDefault().post(new MessageEvent("MessageEvent"));
複製代碼

這裏惟一的須要瞭解的就是post()這個函數。

public void post(Object event) { 
        // 一個PostingThreadState 類型的ThreadLocal
        // PostingThreadState是存儲了事件隊列,線程模式等等信息的類
        PostingThreadState postingState = currentPostingThreadState.get();
        List<Object> eventQueue = postingState.eventQueue;
        // 將事件塞到事件隊列中
        eventQueue.add(event);
        
        if (!postingState.isPosting) {
            // 判斷是否爲主線程
            postingState.isMainThread = isMainThread();
            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;
            }
        }
    }
複製代碼

咱們已經知道了,最開始是一系列的斷定,到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) {
                logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
            }
            if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                    eventClass != SubscriberExceptionEvent.class) {
                post(new NoSubscriberEvent(this, event));
            }
        }
    }
複製代碼

上述的查詢方法中出現了一個共同的特徵,就是調用了postSingleEventForEventType()

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
        CopyOnWriteArrayList<Subscription> subscriptions;
        synchronized (this) {
            // 獲取事件對應的subscriptions集合
            subscriptions = subscriptionsByEventType.get(eventClass);
        }
        if (subscriptions != null && !subscriptions.isEmpty()) {
            for (Subscription subscription : subscriptions) {
                postingState.event = event;
                postingState.subscription = subscription;
                boolean aborted;
                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;
    }
複製代碼

這個postToSubscription()方法,最後斷定就是咱們最開始說的threadMode,根據設置來給出相對應的處理方式。

處理事件

這裏只講解一種,由於其他的終究仍是基於Handler來進行通訊,因此只挑默認方法的POSTING講解。

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {
            case POSTING:
                invokeSubscriber(subscription, event);
                break;
            // 。。。。。
        }
}
複製代碼

這是發送部分的未講解部分,其實已經和處理部分重合,因此就一塊兒講解。 咱們說過POSTING方法是直接在當前的線程做出處理的。 查看源碼也可以發現是不對isMainThread這個變量進行斷定的。

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);
        }
    }
複製代碼

顯然最後也就是調用了一個Method.invoke()方法來對咱們的方法進行處理,這裏也就直接對應了咱們的相對應的定義的方法了。

粘性事件

EventBus.getDefault().postSticky(new MessageEvent("MessageEvent"));
複製代碼

經過postSticky()方法來進行一個調用

public void postSticky(Object event) {
        synchronized (stickyEvents) {
            stickyEvents.put(event.getClass(), event);
        }
        // 用鎖機制存放後再發送,防止直接消費
        post(event);
    }
複製代碼

最後調用的仍是一個post()方法,可是這裏咱們須要想起的是咱們以前還沒有分析的subscribe()中針對粘性事件作出處理的方法。(能夠回到註冊中進行查看)

if (subscriberMethod.sticky) {
            if (eventInheritance) {
                // 也就是對父類中的一種歸併考慮
                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); // 1
                    }
                }
            } else {
                Object stickyEvent = stickyEvents.get(eventType);
                checkPostStickyEventToSubscription(newSubscription, stickyEvent); // 1
            }
        }
// 不管是if仍是else最終都會調用到的註釋1方法
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, isMainThread());
        }
    }
複製代碼

其實粘性事件的最終和發送事件異曲同工,都是須要調用postToSubscription()來將方法進行一個發送,只是,他多作了一個緩存的地方, 也就是stickyEvents這個變量拿來對粘性事件進行一個存儲操做。

總結

圖片轉載自SheHuan大佬的文章

register post unregister

以上就是個人學習成果,若是有什麼我沒有思考到的地方或是文章內存在錯誤,歡迎與我分享。


相關文章推薦:

手撕OkHttp

手撕AsyncTask

手撕ButterKnife

手撕Handler

相關文章
相關標籤/搜索