EventBus使用總結(jié)

概述

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

源碼分析

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

注冊:EventBus.getDefault().register(this);
發(fā)布: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方法的實現(xiàn)

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

一起看一下subscriberMethodFinder是怎么打到觀察者里的監(jiān)聽方法的

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

        // 這里有個判斷,是否忽略編譯時用工具生成的index文件
        // EventBus有一個工具可以在編譯期間就把觀察者和注解的監(jiān)聽方法關(guān)聯(lián)起來,生成一個java文件,避免運行時通過反射進行查找,加快了運行速度,提高性能
        if (ignoreGeneratedIndex) {
            // 通過反射的方式去找到所有注解的監(jiān)聽方法
            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;
        }
    }

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

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

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

找到所有監(jiān)聽的方法后,會通過subscribe方法進行關(guān)聯(lián),來看具體實現(xiàn):

    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        Class<?> eventType = subscriberMethod.eventType;
        // Subscription是監(jiān)聽方法subscriberMethod和觀察者subscriber綁定關(guān)系,用于后面反射調(diào)用方法的
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        // subscriptions是此監(jiān)聽方法中的事件類型對應(yīng)的所有subscriptions,是以事件類型為key的
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        // 如果不存在則新建一個,再把新找到的SubscriberMethod添加進去,這樣后面發(fā)送此事件類型時它才能被觸發(fā)
        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);
            }
        }
        // 這里是根據(jù)優(yōu)先級調(diào)整位置
        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;
            }
        }

        // 這里是從另一個角度看了,獲取觀察者所有監(jiān)聽的事件類型,之前是根據(jù)事件類型找不同觀察者的方法,這里是根據(jù)觀察者類找它所有監(jiān)聽的事件類型,這是為了反注冊時用的
        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);

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

總結(jié)下注冊的邏輯:

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

再來看下發(fā)送的源碼實現(xiàn);

先看下相關(guān)的類說明:

 /** For ThreadLocal, much faster to set (and get multiple values). */
    final static class PostingThreadState {
        final List<Object> eventQueue = new ArrayList<Object>(); //事件隊列
        boolean isPosting; //是否正在發(fā)送
        boolean isMainThread; //是否在主線程發(fā)送的
        Subscription subscription; //監(jiān)聽者的信息
        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()) {
                    // 這里是發(fā)送事件的方法
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }

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

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {
            // 這里都是通過invokeSubscriber反射的形式調(diào)用監(jiān)聽方法的
            // 默認(rèn)值,哪個線程拋出的事件就在哪個線程處理
            case POSTING:
                invokeSubscriber(subscription, event);
                break;
            // 主線程處理,判斷發(fā)送者是否在主線程,如果是,直接調(diào)用,如果不是,切換到主線程調(diào)用
            case MAIN:
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            // 后臺線程處理,如果發(fā)送者在主線程,切換到后臺線程調(diào)用,如果不是,直接調(diào)用
            case BACKGROUND:
                if (isMainThread) {
                    backgroundPoster.enqueue(subscription, event);
                } else {
                    invokeSubscriber(subscription, event);
                }
                break;
            // 總是空閑線程去調(diào)用,它和BACKGROUND的區(qū)別就是總是開一個新線程去處理,BACKGROUND如果發(fā)送線程本來就是非主線程,就直接在當(dāng)前線程處理了
            case ASYNC:
                asyncPoster.enqueue(subscription, event);
                break;
            default:
                throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
        }
    }

postSticky的實現(xiàn)只是在調(diào)用前把事件先存到stickyEvents,用于注冊支持sticky的監(jiān)聽方法時使用,后面還是調(diào)用了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);
    }

總結(jié)下發(fā)送的邏輯:

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

最后看下反注冊的源碼實現(xiàn):

   public synchronized void unregister(Object subscriber) {
        // 根據(jù)觀察者找到所有監(jiān)聽的事件類型
        List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
        if (subscribedTypes != null) {
            // 把此觀察者從事件類型對應(yīng)的所有方法中移除
            for (Class<?> eventType : subscribedTypes) {
                unsubscribeByEventType(subscriber, eventType);
            }
            typesBySubscriber.remove(subscriber);
        } else {
            Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
        }
    }

總結(jié)下反注冊的邏輯:
就是把注冊時存儲的對應(yīng)關(guān)系一一解除。

總結(jié)

EventBus的優(yōu)點:

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

EventBus的缺點:

代碼變得不易讀,不好發(fā)現(xiàn)哪里調(diào)用了事件發(fā)送及哪里做了事件;

如果項目里濫用了,會出現(xiàn)要區(qū)分來源的問題。

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

解決辦法有兩個:

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

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

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

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

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