EventBus源碼分析

面試有一種技巧據(jù)說叫做反客為主玫鸟,當(dāng)遇到Activity-Fragment通信导绷,甚至模塊化開發(fā)時的通信問題等等,可以將TA引導(dǎo)到你擅長的領(lǐng)域中來屎飘,比如說用EventBus來解決妥曲,(RxBus也可以)這時一般套路都會問你懂不懂EventBus的原理,這時你就可以以下文的姿勢這樣回(zhuang)答(b)了
(??????)??

簡介

今天我們來分析截止到現(xiàn)在的3.0.0最新版本
慣例介紹一下我們今天的主角钦购,EventBus檐盟,專門管理Android事件通訊。

//依賴
dependencies {
compile 'org.greenrobot:eventbus:3.0.0'
}
Github示例圖
Github示例圖

在Android開發(fā)中難免會遇到以下場景:

  • Activity之間的回調(diào)startActivityForResult
  • 某個頁面刷新后聯(lián)動其他頁面也刷新
  • Fragment之間的通信
  • more

按筆者之前的尿性就是復(fù)雜場景用本地廣播做發(fā)送與監(jiān)聽押桃,廣播大吼一聲葵萎,我叫你一聲你敢答應(yīng)嗎(??????)??,是不是很有代入感唱凯,直到后來發(fā)現(xiàn)廣播的性能與Intent傳遞數(shù)據(jù)大小有限制以及相對難以維護(hù)后來吃下了EventBus這發(fā)安利羡忘。
筆者先簡單的寫個基本用法


需要接收事件的類中注冊監(jiān)聽(不局限Activity)
EventBus.getDefault().register(this);
別忘記在銷毀時反注冊
EventBus.getDefault().unregister(this);
發(fā)送事件
EventBus.getDefault().post(Object event);
在需要接收事件的類中隨便寫個方法,例如

 //別忘了這個注解
 @Subscribe
 public void onEventReceive(String event) {

 }

這個方法只要一個參數(shù)就夠了磕昼,由于我們發(fā)送事件的時候EventBus根據(jù)類型來做校驗卷雕,例如post("test"),此時所有注冊的類中帶有Subscribe注解的方法中只要第一個參數(shù)是String類型票从,那么就會調(diào)用這個方法漫雕,你問我怎么知道的滨嘱,我等會再告訴你(__)

淺析

在EventBus光鮮的外表下我們好像看到了觀察者模式的影子,沒錯浸间,我們來從TA的入口開始EventBus.getDefault().register(this);中挨個的調(diào)用順序

public class EventBus {
  //暫時省略一些代碼
  private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
  public static EventBus getDefault() {
        if (defaultInstance == null) {
            synchronized (EventBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = new EventBus();
                }
            }
        }
        return defaultInstance;
    }
    public EventBus() {
        this(DEFAULT_BUILDER);
    }
}

一個常見的DCL單例模式+構(gòu)建者模式太雨,我們再來看看EventBusBuilder的構(gòu)造器

public class EventBusBuilder {
        //默認(rèn)的線程池
        private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();
        //是否打印出訂閱時拋出異常的日志
        boolean logSubscriberExceptions = true;
        //是否打印出沒有訂閱者異常的日志
        boolean logNoSubscriberMessages = true;
        //當(dāng)訂閱者訂閱方法執(zhí)行拋出異常時 ,是否讓EventBus發(fā)送一個特定事件
        boolean sendSubscriberExceptionEvent = true;
        //當(dāng)事件沒有訂閱者時魁蒜,是否讓EventBus發(fā)送一個特定事件
        boolean sendNoSubscriberEvent = true;
        //是否拋出訂閱者的異常
        boolean throwSubscriberException;
        //是否允許事件可以有繼承
        boolean eventInheritance = true;
        //是否忽視生成的索引
        boolean ignoreGeneratedIndex;
        //是否方法嚴(yán)格校驗
        boolean strictMethodVerification;
        //線程池
        ExecutorService executorService = DEFAULT_EXECUTOR_SERVICE;
        //某些類跳過驗證躺彬,類似綠色通道
        List<Class<?>> skipMethodVerificationForClasses;
        //訂閱者的索引集合
        List<SubscriberInfoIndex> subscriberInfoIndexes;

        EventBusBuilder() {
        }
    }

貼心的我又都給你們打上注釋了,一下子看不明白沒關(guān)系梅惯,后面我們還會再見面的

 //EventBus的一參構(gòu)造器
 EventBus(EventBusBuilder builder) {
        //根據(jù)消息類型的訂閱者們,key對應(yīng)消息的類型仿野,value對應(yīng)訂閱這個類型消息的訂閱者們
        subscriptionsByEventType = new HashMap<Class<?>, CopyOnWriteArrayList<Subscription>>();
        //根據(jù)訂閱者的類型們铣减,key對應(yīng)訂閱者,value對應(yīng)在這個訂閱者訂閱的事件類型們
        typesBySubscriber = new HashMap<Object, List<Class<?>>>();
        //粘性事件脚作,key對應(yīng)事件類型葫哗,valve對應(yīng)訂閱者
        stickyEvents = new ConcurrentHashMap<Class<?>, Object>();
        //主線程的發(fā)送器,本質(zhì)是Handler
        mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
        //后臺線程的發(fā)送器球涛,本質(zhì)是Runnable
        backgroundPoster = new BackgroundPoster(this);
        //異步線程的發(fā)送器劣针,本質(zhì)是Runnable
        asyncPoster = new AsyncPoster(this);
        //訂閱者索引數(shù)量
        indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
        //訂閱者方法搜尋器
        subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
                builder.strictMethodVerification, builder.ignoreGeneratedIndex);
        //↓接受builder的變量
        logSubscriberExceptions = builder.logSubscriberExceptions;
        logNoSubscriberMessages = builder.logNoSubscriberMessages;
        sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
        sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
        throwSubscriberException = builder.throwSubscriberException;
        eventInheritance = builder.eventInheritance;
        executorService = builder.executorService;
    }

如果看官稍微留意一點(diǎn)注釋,相信已經(jīng)對EventBus有所猜測了亿扁,沒錯捺典,在register方法中EventBus肯定對訂閱者進(jìn)行了不為人知的操作,將訂閱者和接受的事件等等統(tǒng)統(tǒng)做了記錄从祝,抱著這樣的猜測我們來瞧瞧register

  public void register(Object subscriber) {
        //獲取class
        Class<?> subscriberClass = subscriber.getClass();
        //搜尋器去尋找訂閱者要訂閱的的集合
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            //同步遍歷后一一訂閱
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

這里可以這樣打個比方襟己,subscriber比作小明小明想訂報紙牍陌,他去找EventBus擎浴,說我要訂報紙,然后EventBus派遣搜尋器小剛去整理你要的類型毒涧,一看你原來要訂旅游(String)贮预,拍照(Integer)和運(yùn)動(Boolean),然后就去專門的專欄一一訂閱契讲,以后這些專欄一更新仿吞,EventBus就把報紙送到你的手上。
這里我們來看看subscriberMethodFinder的初始化捡偏,初始化就在上文EventBus的構(gòu)造器中

 SubscriberMethodFinder(List<SubscriberInfoIndex> subscriberInfoIndexes, boolean strictMethodVerification,
                           boolean ignoreGeneratedIndex) {
        this.subscriberInfoIndexes = subscriberInfoIndexes;
        this.strictMethodVerification = strictMethodVerification;
        this.ignoreGeneratedIndex = ignoreGeneratedIndex;
    }

將訂閱者的索引集合茫藏,是否方法嚴(yán)格校驗,是否忽視索引3個參數(shù)傳入霹琼,有關(guān)索引做這塊先賣個關(guān)子务傲,先提前劇透一下凉当,今天是小明要訂報紙,明天又來個小張售葡,小剛每次拿筆都要記一下再去專欄訂閱看杭,小張覺得太累了,于是想了個法子挟伙,每次要來訂報紙楼雹,你們先填張表,填完給我尖阔,這樣將就省事多了贮缅。

深入

接下來我們來看看搜尋器的尋找方法有沒有什么貓膩

 List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        //優(yōu)先先從訂閱者的緩存集合中尋找
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if (subscriberMethods != null) {
            return subscriberMethods;
        }
      
        if (ignoreGeneratedIndex) {
            //根據(jù)名字,應(yīng)該是反射獲取對應(yīng)的訂閱方法
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
            //默認(rèn)不忽視介却,也就是從索引中尋找
            subscriberMethods = findUsingInfo(subscriberClass);
        }
        if (subscriberMethods.isEmpty()) {
            //如果訂閱者沒有對應(yīng)訂閱方法異常
            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;
        }
    }

同理我們先跳過索引谴供,先來分析findUsingReflection方法

private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
        //保存記錄
        FindState findState = prepareFindState();
        //初始化
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
            //通過反射來獲取訂閱方法
            findUsingReflectionInSingleClass(findState);
            //移動到父類
            findState.moveToSuperclass();
        }
        //將最后結(jié)果返回并回收資源
        return getMethodsAndRelease(findState);
    }

FindState就在SubscriberMethodFinder下作為靜態(tài)內(nèi)部類,具體如下

static class FindState {
        //尋找到的訂閱方法
        final List<SubscriberMethod> subscriberMethods = new ArrayList<>();
        //所有符合事件類型的方法齿坷,包含父類的
        //key對應(yīng)事件類型桂肌,value對應(yīng)方法Method
        final Map<Class, Object> anyMethodByEventType = new HashMap<>();
        //訂閱者可接受的
        //key對應(yīng)字符串Method名稱+">"+事件類型名稱,value對應(yīng)Method的聲明類
        final Map<String, Class> subscriberClassByMethodKey = new HashMap<>();
        //拼接
        final StringBuilder methodKeyBuilder = new StringBuilder(128);
        //訂閱者的類型
        Class<?> subscriberClass;
        //記錄的當(dāng)前的類
        Class<?> clazz;
        //是否跳過父類
        boolean skipSuperClasses;
        //索引
        SubscriberInfo subscriberInfo;

        //初始化
        void initForSubscriber(Class<?> subscriberClass) {
            this.subscriberClass = clazz = subscriberClass;
            skipSuperClasses = false;
            subscriberInfo = null;
        }

        //回收資源
        void recycle() {
            subscriberMethods.clear();
            anyMethodByEventType.clear();
            subscriberClassByMethodKey.clear();
            methodKeyBuilder.setLength(0);
            subscriberClass = null;
            clazz = null;
            skipSuperClasses = false;
            subscriberInfo = null;
        }

        //通過方法和事件類型是否能夠添加到隊列中
        boolean checkAdd(Method method, Class<?> eventType) {
            Object existing = anyMethodByEventType.put(eventType, method);
            if (existing == null) {
                //集合中之前未添加過這個事件
                return true;
            } else {
                if (existing instanceof Method) {
                    if (!checkAddWithMethodSignature((Method) existing, eventType)) {
                        // Paranoia check
                        //迷之校驗永淌,↑原文注釋都在吐槽了
                        throw new IllegalStateException();
                    }
                    anyMethodByEventType.put(eventType, this);
                }
                return checkAddWithMethodSignature(method, eventType);
            }
        }

        //通過方法和事件類型是否能夠添加到隊列中崎场,二次校驗
        private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {
            methodKeyBuilder.setLength(0);
            methodKeyBuilder.append(method.getName());
            methodKeyBuilder.append('>').append(eventType.getName());
            //按照指定格式拼接作為String key
            String methodKey = methodKeyBuilder.toString();
            Class<?> methodClass = method.getDeclaringClass();
            Class<?> methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);
            if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) {
                //集合未添加過 || 添加過并且后續(xù)添加的訂閱者方法聲明類和之前覆蓋的值有子父關(guān)系或者同屬一類
                return true;
            } else {
                //添加過了且沒有類關(guān)系
                subscriberClassByMethodKey.put(methodKey, methodClassOld);
                return false;
            }
        }

        //移動到父類
        void moveToSuperclass() {
            if (skipSuperClasses) {
                clazz = null;
            } else {
                clazz = clazz.getSuperclass();
                String clazzName = clazz.getName();
                //跳過系統(tǒng)類
                if (clazzName.startsWith("java.") || clazzName.startsWith("javax.") || clazzName.startsWith("android.")) {
                    clazz = null;
                }
            }
        }
    }

理解起來比較雞肋,沒什么營養(yǎng)遂蛀,我們結(jié)合findUsingReflectionInSingleClass一起來看

 private void findUsingReflectionInSingleClass(FindState findState) {
        Method[] methods;
        try {
            //獲取當(dāng)前類中的聲明方法集合
            //原文注釋說比下面的效率高
            methods = findState.clazz.getDeclaredMethods();
        } catch (Throwable th) {
            // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
            methods = findState.clazz.getMethods();
            findState.skipSuperClasses = true;
        }
        for (Method method : methods) {
            //開始遍歷
            //方法修飾符
            int modifiers = method.getModifiers();
            //方法只能是public谭跨,并且不能為static,abstract和2個筆者不懂的迷之修飾符_(:з」∠)_
            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                //獲取方法需要的參數(shù)
                Class<?>[] parameterTypes = method.getParameterTypes();
                if (parameterTypes.length == 1) {
                    //一個參數(shù)
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                    if (subscribeAnnotation != null) {
                        Class<?> eventType = parameterTypes[0];
                        //判斷是否可以添加
                        if (findState.checkAdd(method, eventType)) {
                            //提取注解的線程修飾mode
                            ThreadMode threadMode = subscribeAnnotation.threadMode();
                            //一并添加隊列中
                            findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                    subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                        }
                    }
                } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                    //嚴(yán)格校驗下就要拋出異常
                    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)) {
                //嚴(yán)格校驗下就要拋出異常
                String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                throw new EventBusException(methodName +
                        " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
            }
        }
    }

仔細(xì)一看就明白多了李滴,在findUsingReflection方法中先初始化了一個FindState對象饺蚊,然后對訂閱者類型的父子繼承關(guān)系以及事件類型的匹配進(jìn)行層層篩選,將篩選結(jié)果作為List<SubscriberMethod>返回給上級悬嗓,篩選過程比較復(fù)雜污呼,感興趣的同學(xué)可以參照筆者上面的注釋仔細(xì)品味。
總結(jié)如下:

  • 訂閱的方法需要@Subscribe修飾包竹,里面可以附帶線程模式和和優(yōu)先級燕酷;
  • 訂閱方法必須為public,不能為abstract和static周瞎;
  • 允許訂閱者之間的繼承關(guān)系苗缩;

其中SubscriberMethod內(nèi)部存放一些事件的信息,我們一會再提

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

在將結(jié)果返回的方法getMethodsAndRelease中声诸,還做了緩存處理

 private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
        List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
        findState.recycle();
        synchronized (FIND_STATE_POOL) {
            for (int i = 0; i < POOL_SIZE; i++) {
                if (FIND_STATE_POOL[i] == null) {
                    FIND_STATE_POOL[i] = findState;
                    break;
                }
            }
        }
        return subscriberMethods;
    }

好了酱讶,萬里長征已經(jīng)進(jìn)行了一半了,什么1宋凇?你和我說才一半
(╯‵□′)╯︵┻━┻。休里。。堅持一下琉朽,馬上就革命勝利了
┬─┬ ノ( ' - 'ノ)
我們回到EventBus的register方法,下一步是加鎖遍歷搜尋器返回的結(jié)果稚铣,也就是小剛要把小明箱叁,小張的訂閱表去訂閱道指定專欄

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        //獲取事件類型
        Class<?> eventType = subscriberMethod.eventType;
        //一重簡單的封裝
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        //從這個事件類型的map中取出訂閱者們
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions == null) {
            //第一次的初始化
            subscriptions = new CopyOnWriteArrayList<>();
            subscriptionsByEventType.put(eventType, subscriptions);
        } else {
            if (subscriptions.contains(newSubscription)) {
                //已經(jīng)添加過,拋出異常
                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) {
                //根據(jù)訂閱者方法的優(yōu)先級存放
                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) {
                //消息允許繼承時
                //遍歷map
                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();
                        //判斷粘性事件是否立即分發(fā)
                        checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                    }
                }
            } else {
                Object stickyEvent = stickyEvents.get(eventType);
                //判斷粘性事件是否立即分發(fā)
                checkPostStickyEventToSubscription(newSubscription, stickyEvent);
            }
        }
    }

subscribe方法中對事件和訂閱者的關(guān)系都保存在了map中惕医,然后對粘性事件進(jìn)行了處理耕漱。
這里我們又遇到了粘性事件,打個比方抬伺,小張訂閱了音樂頻道的報紙螟够,想在訂閱時希望過往的報紙也能看到。其實和四大組件的粘性廣播相似沛简,又比如我們開啟一個Activity,我們在Activity的onCreate中注冊了事件斥废,但是在傳遞數(shù)據(jù)時我們的消息已經(jīng)發(fā)出了椒楣,而Activity的初始化不是startActivity一下就能開啟的,所以我們在Activity中的訂閱方法要改一改牡肉,將注解改成@Subscribe(sticky = true)捧灰,就能接受到粘性事件。
那么EventBus的注冊方法我們從頭到尾看了一遍统锤,接下來我們來看看分發(fā)方法post

public void post(Object event) {
        //獲取當(dāng)前線程的狀態(tài)
        PostingThreadState postingState = currentPostingThreadState.get();
        //當(dāng)前線程的消息隊列
        List<Object> eventQueue = postingState.eventQueue;
        //添加事件
        eventQueue.add(event);

        if (!postingState.isPosting) {
            //當(dāng)前線程不處于發(fā)送中狀態(tài)
            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()) {
                    //循環(huán)發(fā)送隊列中事件
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }

post方法很簡單毛俏,隊列中有消息就發(fā)送出去,如何獲取當(dāng)前線程的狀態(tài)原理基于ThreadLocal來實現(xiàn)饲窿,這里筆者簡要提一下好了煌寇,ThreadLocal是一個線程內(nèi)部的數(shù)據(jù)存儲類,通過它可以在指定的線程中存儲一些數(shù)據(jù)逾雄,而這段數(shù)據(jù)是不與其他線程共享的阀溶。內(nèi)部原理是通過泛型對象數(shù)組,在不同的線程會有不同的數(shù)組索引值鸦泳,這樣就可以在不同線程這種調(diào)用get 方法時银锻,取到對應(yīng)線程的數(shù)據(jù)。
有關(guān)PostingThreadState代碼如下

private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
        @Override
        protected PostingThreadState initialValue() {
            return new PostingThreadState();
        }
    };
final static class PostingThreadState {
        final List<Object> eventQueue = new ArrayList<Object>();
        boolean isPosting;
        boolean isMainThread;
        Subscription subscription;
        Object event;
        boolean canceled;
    }

我們繼續(xù)順藤摸瓜來看看發(fā)送消息的具體實現(xiàn)

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        Class<?> eventClass = event.getClass();
        //事件是否找到對應(yīng)訂閱者
        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);
                //按位或击纬,把結(jié)果賦值給subscriptionFound
                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) {
                //EventBus發(fā)送一個沒人認(rèn)領(lǐng)的事件555。钾麸。更振。
                post(new NoSubscriberEvent(this, event));
            }
        }
    }

原來這個方法還不是真身炕桨,只是對消息類型進(jìn)行了繼承關(guān)系的判斷,并且獲取到消息發(fā)送結(jié)果殃饿,如果無訂閱者訂閱則發(fā)送一個特定事件谋作,那么再來看看postSingleEventForEventType

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
        CopyOnWriteArrayList<Subscription> subscriptions;
        synchronized (this) {
            //同步將能接受到這個消息類型的訂閱者們?nèi)〕鰜?            subscriptions = subscriptionsByEventType.get(eventClass);
        }
        if (subscriptions != null && !subscriptions.isEmpty()) {
            for (Subscription subscription : subscriptions) {
                //遍歷
                postingState.event = event;
                postingState.subscription = subscription;
                boolean aborted = false;
                try {
                    //將消息發(fā)送給訂閱者
                    postToSubscription(subscription, event, postingState.isMainThread);
                    //消息是否被高優(yōu)先級的訂閱者中斷
                    aborted = postingState.canceled;
                } finally {
                    postingState.event = null;
                    postingState.subscription = null;
                    postingState.canceled = false;
                }
                if (aborted) {
                    //中斷的話這個消息類型后續(xù)訂閱者就接受不到了
                    break;
                }
            }
            return true;
        }
        return false;
    }

果然,EventBus按照這個類型的消息依次發(fā)送給訂閱者們乎芳,高優(yōu)先級的訂閱者甚至可以中斷低優(yōu)先級的訂閱者們遵蚜,那么我們來看消息是怎么調(diào)度給訂閱者的呢

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

我們看到了四種類型

  • POSTING:默認(rèn)線程環(huán)境,直接調(diào)用invokeSubscriber方法奈惑,表示在post消息的線程環(huán)境直接執(zhí)行訂閱者的訂閱方法
  • MAIN:如果當(dāng)前在主線程中吭净,那么直接執(zhí)行,否則就切到主線程去響應(yīng)事件
  • BACKGROUND:如果當(dāng)前在工作線程中肴甸,那么直接執(zhí)行寂殉,否則就轉(zhuǎn)到工作線程響應(yīng)事件
  • ASYNC:不管post線程情況,直接切到一個新線程中處理事件

事件響應(yīng)線程和優(yōu)先級0-100都可以在注解中配置

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
    ThreadMode threadMode() default ThreadMode.POSTING;

    boolean sticky() default false;

    int priority() default 0;
}

其中我們先來看看invokeSubscriber

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

果然原在,直接通過反射直接調(diào)用了invoke友扰,那么MAIN模式是怎么切換的呢,還是老樣子庶柿,我們從入口來
mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);

HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {
        super(looper);
        this.eventBus = eventBus;
        this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
        queue = new PendingPostQueue();
    }

可以從命名猜測HandlerPoster內(nèi)部維護(hù)了一個隊列

 //EventBus調(diào)用的入口
 void enqueue(Subscription subscription, Object event) {
        //從后續(xù)隊列池中獲取一條
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        synchronized (this) {
            queue.enqueue(pendingPost);
            if (!handlerActive) {
                handlerActive = true;
                if (!sendMessage(obtainMessage())) {
                    throw new EventBusException("Could not send handler message");
                }
            }
        }
    }

    @Override
    public void handleMessage(Message msg) {
        boolean rescheduled = false;
        try {
            long started = SystemClock.uptimeMillis();
            while (true) {
                PendingPost pendingPost = queue.poll();
                if (pendingPost == null) {
                    synchronized (this) {
                        //再次校驗村怪,有點(diǎn)像DCL單例。
                        pendingPost = queue.poll();
                        if (pendingPost == null) {
                            handlerActive = false;
                            return;
                        }
                    }
                }
                //反射執(zhí)行
                eventBus.invokeSubscriber(pendingPost);
                long timeInMethod = SystemClock.uptimeMillis() - started;
                if (timeInMethod >= maxMillisInsideHandleMessage) {
                    //保證2條EventBus之間處理消息間隔不小于10s浮庐,防止主線程卡頓
                    if (!sendMessage(obtainMessage())) {
                        throw new EventBusException("Could not send handler message");
                    }
                    rescheduled = true;
                    return;
                }
            }
        } finally {
            handlerActive = rescheduled;
        }
    }

看來最后涉及線程間調(diào)度后處理消息還有貓膩甚负,我們在PendingPost里瞧瞧看

final class PendingPost {
    //待處理的  事件對應(yīng)訂閱者的 請求池 
    private final static List<PendingPost> pendingPostPool = new ArrayList<PendingPost>();

    Object event;
    Subscription subscription;
    PendingPost next;

    private PendingPost(Object event, Subscription subscription) {
        this.event = event;
        this.subscription = subscription;
    }
    //
    static PendingPost obtainPendingPost(Subscription subscription, Object event) {
        synchronized (pendingPostPool) {
            int size = pendingPostPool.size();
            if (size > 0) {
                //同步在池中取出一個對象池
                PendingPost pendingPost = pendingPostPool.remove(size - 1);
                pendingPost.event = event;
                pendingPost.subscription = subscription;
                pendingPost.next = null;
                return pendingPost;
            }
        }
        return new PendingPost(event, subscription);
    }

    static void releasePendingPost(PendingPost pendingPost) {
        pendingPost.event = null;
        pendingPost.subscription = null;
        pendingPost.next = null;
        synchronized (pendingPostPool) {
            //池中增長速度不要過快,1000數(shù)量的閾值
            if (pendingPostPool.size() < 10000) {
                pendingPostPool.add(pendingPost);
            }
        }
    }

}

這么一看好像就只是個對象池嘛审残,不知道有沒有分析錯梭域,敬請dalao指正,那么BACKGROUNDASYNC我們也就快速過一遍好了搅轿,上文概要的提到這2個mode其實都是實現(xiàn)Runnable接口病涨,只不過BACKGROUND內(nèi)部的run方法是同步單線程處理的,而ASYNC是相當(dāng)于多線程的璧坟。
那么Eventbus的post事件我們已經(jīng)了解完了原理,無非就是遍歷所有該處理這個事件的訂閱者沸柔,然后根據(jù)方法的注解來分配到指定的線程環(huán)境中invoke執(zhí)行循衰,那么發(fā)送粘性事件是在怎么樣的呢

  public void postSticky(Object event) {
        synchronized (stickyEvents) {
            stickyEvents.put(event.getClass(), event);
        }
        //也是調(diào)用post
        post(event);
    }

其實本質(zhì)發(fā)送粘性事件和普通發(fā)送是一個原理,只不過追加了這條事件到粘性隊列中褐澎,而后來訂閱者如果要響應(yīng)之前發(fā)的粘性事件那么就應(yīng)該是在訂閱的時候会钝,就是在subscribe方法下的checkPostStickyEventToSubscription,方法內(nèi)部直接調(diào)用postToSubscription
那么EventBus的一套正常流程我們就基本分析完了(°?°)?其余還有一些EventBus的取消后續(xù)事件cancelEventDelivery迁酸,移除粘性事件removeStickyEvent先鱼,反注冊unregister,都比較簡單奸鬓,這里就不分析了焙畔。

Plus

你們以為這就把EventBus源碼分析完了,當(dāng)然沒有串远!EventBus還有一個必殺技我們還沒分析宏多,綜上所述,EventBus對訂閱者信息多出用到了反射澡罚,這是在一定程度上消耗性能的伸但,那么EventBus的開源作者是怎么優(yōu)化的呢,沒錯就是通過預(yù)編譯技術(shù)來生成索引留搔,相像一下更胖,我們每次要查詢一個單詞就要從頭去字典找,如果現(xiàn)在有個字典的目錄隔显,我們不是就能很快的找到嗎却妨?



這張據(jù)說是開源作者博客里的性能對比圖,可以看到加了索引的EventBus性能簡直就是打了雞血括眠,蹭蹭蹭的上去彪标。
完整依賴如下
在項目根目錄gradle中導(dǎo)入apt編譯插件

dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}

Moudle處

apply plugin: 'com.neenbedankt.android-apt'
apt {
    arguments {
        //改成你的索引
        eventBusIndex "com.tk.test.EventBusIndex"
    }
}
dependencies {
        compile 'org.greenrobot:eventbus:3.0.0'
        apt 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}

編譯過后我們會在這個路徑下找到索引app>build>generated>source>apt>debug>com.tk.test.EventBusIndex

/** This class is generated by EventBus, do not edit. */
public class EventBusIndex implements SubscriberInfoIndex {
    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;

    static {
        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();

        putIndex(new SimpleSubscriberInfo(com.tk.test.activity.SplashActivity.class, true, new SubscriberMethodInfo[] {
            //通過apt的方式直接把方法名等等參數(shù)全放這里了
            new SubscriberMethodInfo("onEventReceive", String.class, ThreadMode.POSTING, 0, true),
        }));
 
    }

    private static void putIndex(SubscriberInfo info) {
        SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
    }
     //實現(xiàn)方法,用來將指定訂閱者類型的相關(guān)信息提取出來
    @Override
    public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
        SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
        if (info != null) {
            return info;
        } else {
            return null;
        }
    }
}
//初始化導(dǎo)入索引
EventBus.builder()
                .addIndex(new EventBusIndex())
                .installDefaultEventBus();

EventBusIndex只有一條記錄哺窄,因為筆者只有一個Activity作為訂閱者╮(╯▽╰)╭捐下,那么索引又是在哪里被讀取到EventBus的map中呢账锹,我們再回過頭來看看搜尋器的findSubscriberMethods方法萌业,在里面有一個從索引中尋找的方法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 {
                findUsingReflectionInSingleClass(findState);
            }
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }

private SubscriberInfo getSubscriberInfo(SubscriberMethodFinder.FindState findState) {
        if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) {
            //訂閱者有繼承關(guān)系
            SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
            if (findState.clazz == superclassInfo.getSubscriberClass()) {
                return superclassInfo;
            }
        }
        if (subscriberInfoIndexes != null) {
            for (SubscriberInfoIndex index : subscriberInfoIndexes) {
                //遍歷索引
                SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
                if (info != null) {
                    return info;
                }
            }
        }
        return null;
    }

其實基本和這個方法findUsingReflection雷同,然后從FindState的subscriberInfo或者builder中的索引集合中取出來奸柬,然后遍歷索引再進(jìn)行篩選生年。后面的流程就一樣了
apt筆者不太懂,這里就拋磚引玉了廓奕,大抵就是編譯時把之前的搜尋器SubscriberMethodFinder的掃描過程以另外一種模式又執(zhí)行了一遍抱婉,然后將結(jié)果生成了一個類,原理和大名鼎鼎的ButterKnife類似桌粉。

Ps:EventBus在開發(fā)中可以封裝成一個統(tǒng)一的抽象Event或者接口蒸绩,訂閱者們通過消息的key來區(qū)分;
如果對RxJava了解比較深入的同學(xué)可以用RxBus來代替铃肯,能節(jié)省一些代碼

這幾天學(xué)習(xí)源碼的過程有點(diǎn)累患亿,文章如有錯誤,敬請指正!
最后再上一張筆者整理的流程圖

耶穌說:“我就是道路步藕、真理惦界、生命;若不藉著我咙冗,沒有人能到父那里去沾歪。 (約翰福音 14:6 和合本)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市雾消,隨后出現(xiàn)的幾起案子灾搏,更是在濱河造成了極大的恐慌,老刑警劉巖仪或,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件确镊,死亡現(xiàn)場離奇詭異,居然都是意外死亡范删,警方通過查閱死者的電腦和手機(jī)蕾域,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來到旦,“玉大人旨巷,你說我怎么就攤上這事√硗” “怎么了采呐?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長搁骑。 經(jīng)常有香客問我斧吐,道長,這世上最難降的妖魔是什么仲器? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任煤率,我火速辦了婚禮,結(jié)果婚禮上乏冀,老公的妹妹穿的比我還像新娘蝶糯。我一直安慰自己,他們只是感情好辆沦,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布昼捍。 她就那樣靜靜地躺著,像睡著了一般肢扯。 火紅的嫁衣襯著肌膚如雪妒茬。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天蔚晨,我揣著相機(jī)與錄音乍钻,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛团赁,可吹牛的內(nèi)容都是我干的育拨。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼欢摄,長吁一口氣:“原來是場噩夢啊……” “哼熬丧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起怀挠,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤析蝴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后绿淋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體闷畸,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年吞滞,在試婚紗的時候發(fā)現(xiàn)自己被綠了佑菩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡裁赠,死狀恐怖殿漠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情佩捞,我是刑警寧澤绞幌,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站一忱,受9級特大地震影響莲蜘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜帘营,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一票渠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧仪吧,春花似錦庄新、人聲如沸鞠眉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽械蹋。三九已至出皇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間哗戈,已是汗流浹背郊艘。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人纱注。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓畏浆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親狞贱。 傳聞我的和親對象是個殘疾皇子刻获,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345

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