EventBus使用總結

概述

EventBus是Android開發最常用的一個庫了,它給我們帶來了很好便利性,輕松實現消息的發布和訂閱。但凡事都有兩面性,合理的使用就是好事,不合理的使用可能就會有各種問題了,下面我們來看看它的源碼實現。

源碼分析

EventBus使用簡單,注冊反注冊發送監聽都非常簡單,易上手。來看一下它的使用吧。

注冊:EventBus.getDefault().register(this);
發布:EventBus.getDefault().post(event);
反注冊:EventBus.getDefault().unRegister(this);

先看看EventBus的getDefault方法

public static EventBus getDefault() {
        if (defaultInstance == null) {
            synchronized (EventBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = new EventBus();
                }
            }
        }
        return defaultInstance;
    }

就是獲取EventBus實例,這里用的是全局單例,方便使用。

再看看register方法的實現

    public void register(Object subscriber) {
        // 獲取觀察者的類型
        Class<?> subscriberClass = subscriber.getClass();
        // 通過subscriberMethodFinder找到這個觀察者里所有的監聽方法
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            // 把類和監聽的方法關聯起來,放在map中,后續有事件時才能找到對應關系觸發監聽方法
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

一起看一下subscriberMethodFinder是怎么打到觀察者里的監聽方法的

    List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        // 先從Cache里找,如果有對應關系了直接返回,沒有則去建立對應關系
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if (subscriberMethods != null) {
            return subscriberMethods;
        }

        // 這里有個判斷,是否忽略編譯時用工具生成的index文件
        // EventBus有一個工具可以在編譯期間就把觀察者和注解的監聽方法關聯起來,生成一個java文件,避免運行時通過反射進行查找,加快了運行速度,提高性能
        if (ignoreGeneratedIndex) {
            // 通過反射的方式去找到所有注解的監聽方法
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
            // 通過編譯生成的java文件進行查找
            subscriberMethods = findUsingInfo(subscriberClass);
        }
        if (subscriberMethods.isEmpty()) {
            throw new EventBusException("Subscriber " + subscriberClass
                    + " and its super classes have no public methods with the @Subscribe annotation");
        } else {
            // 找到后put到cache里
            METHOD_CACHE.put(subscriberClass, subscriberMethods);
            return subscriberMethods;
        }
    }

代碼通過注釋說清楚了監聽方法的過程,通過反射就是找使用了@Subscribe注解的函數,并讀取其中的配置,比如threadMode等,更于后面發送事件時使用。這里就不貼反射的具體代碼了。

要說的就是SubscriberMethod類了,看看這里有哪些成員變量:

public class SubscriberMethod {
    final Method method; //方法,用于反射回調的
    final ThreadMode threadMode; //指定的線程
    final Class<?> eventType; //事件的類型,后續要通過這個找相關的監聽方法
    final int priority; //優先級
    final boolean sticky; //是否接收粘性事件
}

找到所有監聽的方法后,會通過subscribe方法進行關聯,來看具體實現:

    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        Class<?> eventType = subscriberMethod.eventType;
        // Subscription是監聽方法subscriberMethod和觀察者subscriber綁定關系,用于后面反射調用方法的
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        // subscriptions是此監聽方法中的事件類型對應的所有subscriptions,是以事件類型為key的
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        // 如果不存在則新建一個,再把新找到的SubscriberMethod添加進去,這樣后面發送此事件類型時它才能被觸發
        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);

        // 如果方法是支持粘性事件的,就會從粘性事件緩存里找到最近一次的事件,直接通過checkPostStickyEventToSubscription進行事件發送
        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);
            }
        }
    }

總結下注冊的邏輯:

  1. 找到觀察者里所有的監聽方法
  2. 循環把所有監聽方法和事件類型的關系保存下來
  3. 如果支持粘性事件就找最近一次同類型事件進行發送

再來看下發送的源碼實現;

先看下相關的類說明:

 /** 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; //是否已取消 
    }
public void post(Object event) {
        // 獲取PostingThreadState,把事件加到事件隊列中
        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;
            }
        }
    }

postSingleEvent->postSingleEventForEventType->postToSubscription這里發送事件方法的調用路徑,postSingleEventForEventType其實就是通過事件類型從我們注冊時存儲的對應關系里找到此事件對應的所有監聽信息Subscriptions,然后逐個調用。我們直接看postToSubscription的實現:

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {
            // 這里都是通過invokeSubscriber反射的形式調用監聽方法的
            // 默認值,哪個線程拋出的事件就在哪個線程處理
            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;
            // 總是空閑線程去調用,它和BACKGROUND的區別就是總是開一個新線程去處理,BACKGROUND如果發送線程本來就是非主線程,就直接在當前線程處理了
            case ASYNC:
                asyncPoster.enqueue(subscription, event);
                break;
            default:
                throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
        }
    }

postSticky的實現只是在調用前把事件先存到stickyEvents,用于注冊支持sticky的監聽方法時使用,后面還是調用了post方法。

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

總結下發送的邏輯:

  1. 如果是粘性事件就把事件存起來
  2. 通過事件類型找到所有相關的監聽方法
  3. 通過反射在指定的處理線程中調用監聽方法

最后看下反注冊的源碼實現:

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

總結下反注冊的邏輯:
就是把注冊時存儲的對應關系一一解除。

總結

EventBus的優點:

  1. 易用性:使用簡單易上手
  2. 輕耦合:使用它就是為了替換回調函數,避免模塊間的強耦合
  3. 線程切換:EventBus通過注解里的threadMode可以輕松指定處理的線程

EventBus的缺點:

代碼變得不易讀,不好發現哪里調用了事件發送及哪里做了事件;

如果項目里濫用了,會出現要區分來源的問題。

舉個例子,請求獲取一個實時性比較強的數據,不同時間請求的結果會不一樣,請求結果回來后用EventBus拋出結果,需要的進行監聽。A界面執行了請求,請求沒結束前就跳轉到B界面,B界面同樣調用了這個請求,這里結果回來的速度不一樣,B界面就會收到兩個EventBus的事件,導致處理錯誤。

解決辦法有兩個:

  1. A界面跳轉到B界面后,取消A界面的請求,避免重復數據發送(正常都要做的事情)
  2. 對所有的事件增加來源標識(可以用這個界面類的hashcode),B界面收到后可以判斷來源是否是自己發的請求,如果不是,則不處理。但這就要求發請求時要把這個標識帶上了。

EventBus基本上是開發應用必備的第三方庫了,總得來說好處多多,但還是要適當使用,別看它那么好用就一沖動就把所有Callback換成EventBus實現,那樣會得不償失的。

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

推薦閱讀更多精彩內容

  • 1.EventBus是一個基于觀察者模式的事件發布/訂閱框架,開發者通過極少的代碼去實現多個模塊之間的通信,而不需...
    newszhu閱讀 426評論 0 0
  • 為什么要用EventBus:EventBus是一個事件總線框架,觀察者模式的變形,利用這個框架,我們可以方便高效地...
    文文太遠了閱讀 694評論 0 1
  • EventBus是Android中的發布/訂閱事件總線。github鏈接 目前應該還算是最流行的,另外還有Otto...
    kolen_j閱讀 267評論 0 2
  • 項目到了一定階段會出現一種甜蜜的負擔:業務的不斷發展與人員的流動性越來越大,代碼維護與測試回歸流程越來越繁瑣。這個...
    fdacc6a1e764閱讀 3,210評論 0 6
  • 筆記概述 EventBus簡介 EventBus方法介紹 EventBus實際運用 EventBus簡介 開源項目...
    凌川江雪閱讀 643評論 0 2