EventBus源碼閱讀筆記

一、簡介

EventBus是一個事件“發(fā)布/訂閱”總線,可用于Android或者Java項目中。它具有以下優(yōu)點(diǎn):

  1. 簡化組件間的通信
    • 解耦事件發(fā)布方和訂閱方;
    • 很好的實現(xiàn)在Activities、Fragments和后臺線程間通信;
    • 避免復(fù)雜的易錯的依賴和生命周期問題;
  2. 使代碼更簡潔;
  3. SDK體積很?。s50k);
  4. 已經(jīng)在上億的安裝應(yīng)用中使用;
  5. 具有高級特性,如跨線程事件分發(fā)、按優(yōu)先級分發(fā)等。

EventBus

下文我們把Publisher翻譯成事件發(fā)布器,把Subscriber翻譯成事件訂閱者,Event翻譯成事件,EventBus翻譯成總線。

二、EventBus使用三部曲

1. 定義事件類型

public class MessageEvent {
    public String msg;
}

2. 事件訂閱

public class MainActivity extends Activity {
    @Override
    protected void onResume() {
        super.onResume();
        // 將事件訂閱者注冊到總線中
        EventBus.getDefault().register(this);
    }

    @Override
    protected void onPause() {
        super.onPause();
        // 將事件訂閱者從總線中注銷掉
        EventBus.getDefault().unregister(this);
    }

    // 定義事件接收器處理事件的方法
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(MessageEvent event) {
        Log.d(TAG, "onMessageEvent="+event.msg);
    }
}

3. 事件發(fā)布

public void sendEventMsg(View v){
    // 事件發(fā)布器發(fā)布事件
    MessageEvent event = new MessageEvent();
    event.msg = "hello";
    EventBus.getDefault().post(event);
}

三、源碼分析

正如官方文檔介紹,sdk真的很小。核心類EventBus.java就500行代碼完成了所有功能。短小精悍、簡潔易用、穩(wěn)定高效,實為我輩開發(fā)之楷模。EvenBus核心任務(wù)就兩個:1、事件訂閱者的管理;2、事件的分發(fā)。先總的看下整體架構(gòu),類圖大致如下:

類圖

1. EventBus的創(chuàng)建

EventBus SDK使用不需要任何初始化操作。在需要訂閱或者發(fā)布事件時,通過EventBus.getDefault()方法獲取一個全局的EventBus的單例來執(zhí)行相關(guān)操作。下面是SDK源碼中單例創(chuàng)建的代碼,沒什么好說的標(biāo)準(zhǔn)的單例創(chuàng)建。

// double check lock
static volatile EventBus defaultInstance;
public static EventBus getDefault() {
    if (defaultInstance == null) {
        synchronized (EventBus.class) {
            if (defaultInstance == null) {
                defaultInstance = new EventBus(); 
            }
        }
    }
    return defaultInstance;
}

2. EventBus對事件訂閱者的管理邏輯

上面EventBus使用部分介紹了,事件接收器的注冊/注銷是通過EventBus實例的register/unregister方法來完成的。我們重點(diǎn)分析下register方法的實現(xiàn),unregister方法類似就不重復(fù)分析了。

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

事件訂閱者注冊register(Object subscriber)方法接收一個Object對象作為入?yún)ⅰ?/p>

  1. 通過findSubscriberMethods(Class<?> subscriberClass)方法分析出該對象的類型對象中所有事件處理方法。是通過反射的方式找到我們用@Subscribe注解的方法,如下代碼所示。
  2. 將Object對象和上面找到的每個事件處理方法都分別組成一個事件訂閱者Subscription,也就是說如果一個訂閱對象類里面實現(xiàn)了多個事件處理方法,那就會生成多個事件訂閱者。然后將這些事件訂閱者對象放到集合Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType中保存起來,待事件分發(fā)時使用。subscriptionsByEventType是一個map,map的key是事件類型,value是相同事件的訂閱者的列表。插入到列表的時候是按照訂閱者優(yōu)先級順序插入的,后續(xù)事件分發(fā)的時候就會按照事件優(yōu)先級來順序分發(fā)。

類比上面使用說明中的例子,我們傳入的Object對象是一個Activity對象,事件處理方法就是onMessageEvent方法。在EventBus中是將這個Activity對象和onMessageEvent方法和成了一個事件訂閱者Subscription,然后存在數(shù)劇集合中的。上面類圖中事件訂閱者Subscription的結(jié)構(gòu)清楚的展示了這一點(diǎn)。

private void findUsingReflectionInSingleClass(FindState findState) {
    Method[] methods = findState.clazz.getDeclaredMethods();
    for (Method method : methods) {
        int modifiers = method.getModifiers();
        // 1、必須是public類型,且不能是被static、abstract修飾的方法
        if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            // 2、方法有且只有一個參數(shù)
            if (parameterTypes.length == 1) {
                Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                //3、方法必須是被Subscribe注解的方法
                if (subscribeAnnotation != null) {
                    Class<?> eventType = parameterTypes[0];
                    if (findState.checkAdd(method, eventType)) {
                        ThreadMode threadMode = subscribeAnnotation.threadMode();
                        findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                    }
                }
            } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                throw new EventBusException("@Subscribe method " + methodName +
                        "must have exactly 1 parameter but has " + parameterTypes.length);
            }
        } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
            String methodName = method.getDeclaringClass().getName() + "." + method.getName();
            throw new EventBusException(methodName +
                    " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
        }
    }
}

unregister的過程我們就不分析了,類似register的逆過程,就是從數(shù)據(jù)集合中移除對應(yīng)的事件訂閱者的過程。

3. EventBus對事件的分發(fā)邏輯

事件發(fā)布post(Object event)方法接收一個事件對象作為入?yún)?。還記得我們事件訂閱者是存在以事件類型為key的map中,那事情就簡單了,通過入?yún)⒌氖录愋瞳@取所有訂閱此事件的事件訂閱者列表。

  1. 執(zhí)行事件訂閱者Subscription中事件訂閱對象的事件處理方法;
void invokeSubscriber(Subscription subscription, Object event) {
  subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
}
  1. 跨線程分發(fā)
    EventBus中有兩個poster。mainThreadPoster:如類圖所示它繼承了Handler,關(guān)聯(lián)的是主線程的Looper,所以可以通過mainThreadPoster將事件發(fā)送到主線程執(zhí)行;backgroundPoster:如果所示本身是個Runnable,關(guān)聯(lián)了一個線程池,所以它是將事件發(fā)送到線程池去執(zhí)行的。
    還有一種類型是不適用上面的poster,直接就在事件發(fā)布器調(diào)用post(Object event)方法的線程執(zhí)行事件處理方法,這就實現(xiàn)了在當(dāng)前線程分發(fā)事件。

四、學(xué)習(xí)

1、Double-Check單例創(chuàng)建法
  • Double-Check是為了執(zhí)行提高效率,只在首次獲取實例的時候有線程同步的開銷,后續(xù)獲取實例時不涉及同步;
  • 不同步的引用對象不是線程安全的,除double和float外的基本變量類型是線程安全的。所以在double check模式下引用對象、double和float變量都需要使用volatile 關(guān)鍵字來保證其線程安全。
2、注解

RetentionPolicy.RUNTIME類型的注解可以在運(yùn)行時通過反射的方式獲取到注解的內(nèi)容。如下運(yùn)行時注解Subscribe可應(yīng)用到方法上。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
    ThreadMode threadMode() default ThreadMode.POSTING;
    boolean sticky() default false;
    int priority() default 0;
}

運(yùn)行時通過反射獲取到注解的相關(guān)信息。

Method[] methods = findState.clazz.getDeclaredMethods();

for (Method method : methods) {
    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
    if (subscribeAnnotation != null) {
        ThreadMode threadMode = subscribeAnnotation.threadMode();
    }
}
3、多線程編程

根據(jù)前文的介紹可以了解整個框架中的臨界資源應(yīng)該是這個Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType數(shù)據(jù)集合對象了。在注冊/注銷時間訂閱者和事件分發(fā)是都需要操作這個數(shù)據(jù)集合,可能出現(xiàn)多個線程同時注冊/注銷時間訂閱者,或者注冊/注銷時間訂閱者和事件分發(fā)不在同一個線程,這些場景都有多線程的問題。
我們來看看作者是怎么處理的。

public void register(Object subscriber) {
    Class<?> subscriberClass = subscriber.getClass();
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
}
public synchronized void unregister(Object subscriber) {
    ... ...
}
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
    CopyOnWriteArrayList<Subscription> subscriptions;
    synchronized (this) {
        subscriptions = subscriptionsByEventType.get(eventClass);
    }
    ... ...
    return false;
}

subscriptionsByEventType的操作使用synchronized關(guān)鍵字來同步。另外對于事件訂閱者列表使用CopyOnWriteArrayList來同步修改列表,同時讓列表的遍歷更加高效。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • EventBus源碼分析(一) EventBus官方介紹為一個為Android系統(tǒng)優(yōu)化的事件訂閱總線,它不僅可以很...
    蕉下孤客閱讀 4,089評論 4 42
  • 先吐槽一下博客園的MarkDown編輯器,推出的時候還很高興博客園支持MarkDown了,試用了下發(fā)現(xiàn)支持不完善就...
    Ten_Minutes閱讀 651評論 0 2
  • EventBus基本使用 EventBus基于觀察者模式的Android事件分發(fā)總線。 從這個圖可以看出,Even...
    顧氏名清明閱讀 692評論 0 1
  • 我想跑到很遠(yuǎn)很遠(yuǎn)的地方去,沒有人認(rèn)識我,我也不認(rèn)識任何人,然后失憶,不記得任何人任何事,包括我的養(yǎng)父母。一直失憶,...
    漠北的孤獨(dú)閱讀 208評論 0 0
  • 2016-06-14 華杉 《尚書》上說:“能自得師者王,謂人莫己若者亡?!蹦芴幪幰匀藶閹熣咄跆煜?,覺得誰都不如自...
    郁萍閱讀 772評論 0 0

友情鏈接更多精彩內(nèi)容