EventBus源碼分析(一)

簡介

EventBus是針對(duì)Android優(yōu)化的發(fā)布-訂閱事件總線,簡化了Android組件間的通信。EventBus以其簡單易懂、優(yōu)雅撬统、開銷小等優(yōu)點(diǎn)而備受歡迎,基本在每一個(gè)APP都會(huì)用到敦迄,Eventbus源碼較為簡單宪摧,可作為開源框架源碼學(xué)習(xí)入門教材

如何使用

gradle中添加依賴:

implementation 'org.greenrobot:eventbus:3.2.0'

在需要發(fā)布事件的組件里調(diào)用如下代碼即可實(shí)現(xiàn)事件發(fā)送

EventBus.getDefault().post(TestEvent())

在需要訂閱事件的頁面進(jìn)行注冊(cè),并添加相應(yīng)@Subscribe注解即可實(shí)現(xiàn)事件訂閱

    override fun onStart() {
        super.onStart()
        EventBus.getDefault().register(this)
    }

    override fun onStop() {
        super.onStop()
        EventBus.getDefault().unregister(this)
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public fun onRecEvent(event:TestEvent) {
        Log.e(TAG, "收到事件:$event")
    }

源碼分析(register和unregister)

EventBus.getDefault() 由字面感覺是個(gè)單例颅崩,點(diǎn)進(jìn)去看看:
    static volatile EventBus defaultInstance;
    /** Convenience singleton for apps using a process-wide EventBus instance. */
    //可以看到是采用雙重鎖的一個(gè)單例模式
    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;
    }

register注冊(cè)
    /**
     * Registers the given subscriber to receive events. Subscribers must call {@link #unregister(Object)} once they
     * are no longer interested in receiving events.
     * <p/>
     * Subscribers have event handling methods that must be annotated by {@link Subscribe}.
     * The {@link Subscribe} annotation also allows configuration like {@link
     * ThreadMode} and priority.
     */
    public void register(Object subscriber) {
        //獲取訂閱者對(duì)象類型
        Class<?> subscriberClass = subscriber.getClass();
        //獲取該訂閱者的所有訂閱方法(即被打上 @Subscribe()注解的方法)
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        //上鎖
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                //遍歷逐一訂閱每一個(gè)方法
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

上述流程几于,第一步先是獲取了subscriber的對(duì)象類型,然后通過findSubscriberMethods()方法獲取了該subscriber的所有訂閱方法沿后,并將每個(gè)方法包裝為SubscriberMethod對(duì)象沿彭,最后遍歷逐一調(diào)用subscribe()去訂閱每一個(gè)方法。

這里看一下findSubscriberMethods()是如何查找訂閱者所訂閱的方法并將其包裝為SubscriberMethod返回的:

    //緩存map尖滚,key是訂閱者對(duì)象類型喉刘,value是該訂閱者下的所有訂閱方法
    private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();
    
    List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        //1.先從緩存獲取該訂閱者下的所有訂閱方法瞧柔,獲取到的話直接返回
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if (subscriberMethods != null) {
            return subscriberMethods;
        }
        //是否忽略注解器生成的MyEventBusIndex
        //默認(rèn)ignoreGeneratedIndex為false,即忽略睦裳,走findUsingInfo分支
        if (ignoreGeneratedIndex) {
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
            subscriberMethods = findUsingInfo(subscriberClass);
        }
        //找不到@Subscribe()注解的方法造锅,拋出異常,這就是平時(shí)調(diào)用EventBus.getDefault().register(this)然后沒有注冊(cè)接收事件閃退的原因
        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;
        }
    }

ignoreGeneratedIndex不設(shè)置的話默認(rèn)為false哥蔚,所以這里直接走到了findUsingInfo()方法,看一下findUsingInfo()具體是怎么查找訂閱方法的:

    private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        //這里通過FindState來儲(chǔ)存找到的方法信息
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        // 循環(huán)蛛蒙,從當(dāng)前類開始遍歷該類的所有父類
        while (findState.clazz != null) {
            //獲取訂閱者的信息糙箍,因?yàn)闆]有默認(rèn)沒有使用MyEventBusIndex,這里獲取到會(huì)是null牵祟,走findUsingReflectionInSingleClass分支
            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 {
                //正常沒有使用MyEventBusIndex來到這里
                findUsingReflectionInSingleClass(findState);
            }
            // 將findState.clazz設(shè)置為當(dāng)前的findState.clazz的父類
            findState.moveToSuperclass();
        }
        //獲取methodsList并釋放資源
        return getMethodsAndRelease(findState);
    }
    
    static class FindState {
        //某個(gè)訂閱者下的所有訂閱方法(這個(gè)list就是我們最終要拿到返回回去到第一步的)
        final List<SubscriberMethod> subscriberMethods = new ArrayList<>();
        //map集合诺苹,key為EventType汁果,value為訂閱者的某一個(gè)方法
        final Map<Class, Object> anyMethodByEventType = new HashMap<>();
        //map集合谓传,key為訂閱方法名等參數(shù)拼接起來的string槽袄,value為方法的getDeclaringClass()
        final Map<String, Class> subscriberClassByMethodKey = new HashMap<>();
        final StringBuilder methodKeyBuilder = new StringBuilder(128);

        Class<?> subscriberClass;
        Class<?> clazz;
        boolean skipSuperClasses;
        SubscriberInfo subscriberInfo;

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

        //省略
        .....
    }

上一步findUsingInfo()主要是走到了findUsingReflectionInSingleClass()這個(gè)方法里凡蜻,那他又是如何查找訂閱方法的呢。單看命名筹淫,翻譯過來好像是“使用反射查找”?呢撞?损姜?,即使用反射從訂閱者中得到訂閱方法殊霞?摧阅?看一下代碼:

  private void findUsingReflectionInSingleClass(FindState findState) {
        Method[] methods;
        try {
            // This is faster than getMethods, especially when subscribers are fat classes like Activities
            // 獲取該類中聲明的所有方法
            methods = findState.clazz.getDeclaredMethods();
        } catch (Throwable th) {
            // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
            try {
                methods = findState.clazz.getMethods();
            } catch (LinkageError error) { // super class of NoClassDefFoundError to be a bit more broad...
                String msg = "Could not inspect methods of " + findState.clazz.getName();
                if (ignoreGeneratedIndex) {
                    msg += ". Please consider using EventBus annotation processor to avoid reflection.";
                } else {
                    msg += ". Please make this class visible to EventBus annotation processor to avoid reflection.";
                }
                throw new EventBusException(msg, error);
            }
            findState.skipSuperClasses = true;
        }
        //遍歷找到的方法數(shù)組
        for (Method method : methods) {
            //獲取方法聲明的修飾符,如private绷蹲,public棒卷,protected等
            int modifiers = method.getModifiers();
            //進(jìn)行位運(yùn)算,取出用public修飾的方法祝钢,這就是為什么我們聲明接收事件必須要使用public的原因
            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                //獲取該方法下的參數(shù)類型
                Class<?>[] parameterTypes = method.getParameterTypes();
                //只處理一個(gè)參數(shù)的方法
                if (parameterTypes.length == 1) {
                    //獲取該方法下的注解比规,用來從注解中獲取聲明的信息
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                    if (subscribeAnnotation != null) {
                         // 獲取該方法的第一個(gè)參數(shù)對(duì)象
                        Class<?> eventType = parameterTypes[0];
                        // 檢查是否需要添加進(jìn)findState的訂閱方法列表,后面看怎么較驗(yàn)
                        if (findState.checkAdd(method, eventType)) {
                            //獲取注解的threadMode 信息拦英,即我們常使用的@Subscribe(threadMode = ThreadMode.MAIN)
                            ThreadMode threadMode = subscribeAnnotation.threadMode();
                            //添加進(jìn)訂閱方法列表
                            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");
            }
        }
    }

由上面代碼可以知道蜒什,findUsingReflectionInSingleClass()確實(shí)是使用了很多反射方法,第一步先調(diào)用getDeclaredMethods獲取了Method[]數(shù)組疤估,然后遍歷該方法數(shù)組灾常,取出用public修飾符修飾且只有單個(gè)參數(shù)的方法霎冯,緊接著取出該方法下的注解(必須是Subscribe類型的注解),然后通過調(diào)用findState.checkAdd(method, eventType)方法钞瀑,判斷是否需要將該方法添加進(jìn)findState.subscriberMethods數(shù)組中沈撞,這個(gè)subscriberMethods數(shù)組也就是我們最終要返回回第一步的數(shù)據(jù)源。

看一下findState.checkAdd(method, eventType)方法是如何判斷是如何較驗(yàn)這個(gè)訂閱方法是否有效的

static class FindState {
        //省略
        .....
        boolean checkAdd(Method method, Class<?> eventType) {
            // 2 level check: 1st level with event type only (fast), 2nd level with complete signature when required.
            // Usually a subscriber doesn't have methods listening to the same event type.
            //map集合雕什,key為EventType缠俺,value為訂閱者的某一個(gè)method方法
            Object existing = anyMethodByEventType.put(eventType, method);
            if (existing == null) {
                //之前沒有其他方法訂閱了該事件類型,返回true,表示需要添加進(jìn)subscriberMethods數(shù)組
                return true;
            } else {
                if (existing instanceof Method) {
                    //之前已經(jīng)有方法訂閱過該事件類型监徘,例如void1 void2都訂閱接受String類型晋修,則此時(shí)會(huì)走到這個(gè)分支,進(jìn)行二次較驗(yàn)
                    if (!checkAddWithMethodSignature((Method) existing, eventType)) {
                        // Paranoia check
                        throw new IllegalStateException();
                    }
                    // Put any non-Method object to "consume" the existing Method
                    //放進(jìn)map
                    anyMethodByEventType.put(eventType, this);
                }
                return checkAddWithMethodSignature(method, eventType);
            }
        }
         
        //二次較驗(yàn)
        private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {
            //key為方法名+"<"+事件類型
            methodKeyBuilder.setLength(0);
            methodKeyBuilder.append(method.getName());
            methodKeyBuilder.append('>').append(eventType.getName());

            String methodKey = methodKeyBuilder.toString();
            Class<?> methodClass = method.getDeclaringClass();
            Class<?> methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);
            if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) {
                // Only add if not already found in a sub class
                // 首次注冊(cè)methodClassOld為null凰盔,符合條件墓卦,之后methodClassOld不為空
                //使用methodClassOld.isAssignableFrom(methodClass)判斷methodClassOld是否為methodClass的父類,然后之前了解到我們是從子類開始遍歷到父類的户敬,
                //顯然這個(gè)條件不滿足落剪,這也是使用EventBus的時(shí)候子類不能重寫父類的函數(shù)原因?尿庐?忠怖??抄瑟?凡泣?
                return true;
            } else {
                // Revert the put, old class is further down the class hierarchy
                subscriberClassByMethodKey.put(methodKey, methodClassOld);
                return false;
            }
        }

        //省略
        .....
    }

到這里獲取訂閱方法的流程就已經(jīng)結(jié)束了,獲取到的訂閱方法已經(jīng)被打包成 SubscriberMethod對(duì)象并添加到findState.subscriberMethods數(shù)組里面去了皮假,接下來只要把它取出來就好了鞋拟。

回到我們剛開始分析注冊(cè)進(jìn)來的地方,我們?cè)谝幌盗胁僮骱竽玫搅擞嗛喺咚嗛喌乃蟹椒↙ist<SubscriberMethod>惹资,接下來還需要給每個(gè)方法添加訂閱贺纲,subscribe(subscriber, subscriberMethod)方法又是怎樣的呢?

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

subscribe(subscriber, subscriberMethod)代碼如下:

   // Must be called in synchronized block
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        //獲取該訂閱方法的訂閱類型
        Class<?> eventType = subscriberMethod.eventType;
        //通過訂閱者和訂閱方法構(gòu)建Subscription 對(duì)象
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        //通過事件類型褪测,獲取該事件類型下的所有訂閱事件
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions == null) {
            subscriptions = new CopyOnWriteArrayList<>();
            //放入map緩存
            subscriptionsByEventType.put(eventType, subscriptions);
        } else {
            //如果重復(fù)訂閱則會(huì)報(bào)錯(cuò)猴誊,這也是為什么有時(shí)我們多次調(diào)用 EventBus.getDefault().register(this)閃退的原因
            if (subscriptions.contains(newSubscription)) {
                throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                        + eventType);
            }
        }
         //根據(jù)新加入的訂閱方法的優(yōu)先級(jí)決定插入到隊(duì)列中的位置
        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;
            }
        }
        //將該訂閱方法存放到typesBySubscriber中
        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);
        ...省略粘性事件相關(guān)代碼
    }

簡單講,訂閱流程就是將獲取到的subscriberMethod數(shù)組逐一添加到subscriptionsByEventType和typesBySubscriber這兩個(gè)map中去侮措,為了給在事件發(fā)送的時(shí)候使用懈叹,以此形成通信。

這里比較復(fù)雜的就是獲取方法的流程分扎,簡單總結(jié)調(diào)用順序如下:
findSubscriberMethods()->findUsingInfo()->包裝成FindState對(duì)象存儲(chǔ)相關(guān)信息和映射關(guān)系->findUsingReflectionInSingleClass()

unregister解注冊(cè)
/** Unregisters the given subscriber from all event classes. */
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());
    }
}

private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
        // 找到與事件類型相匹配的訂閱方法
        List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions != null) {
            int size = subscriptions.size();
            for (int i = 0; i < size; i++) {
                Subscription subscription = subscriptions.get(i);
                // 將訂閱者中的訂閱方法移除
                if (subscription.subscriber == subscriber) {
                    subscription.active = false;
                    subscriptions.remove(i);
                    i--;
                    size--;
                }
            }
        }
    }

取消訂閱的方法中跟訂閱反著來项阴,subscriptionsByEventType和typesBySubscriber有沒有很熟悉!!环揽!首先通過獲取typesBySubscriber map獲取該訂閱者所訂閱的所有訂閱類型略荡,接著通過subscriptionsByEventType map獲取每一個(gè)訂閱類型下的所有屬于該訂閱者的訂閱方法,然后逐一移除掉歉胶。

以上就是訂閱/取消訂閱的流程汛兜,關(guān)于訂閱后是如何接受到事件的,EventBus.getDefault().post(TestEvent())是如何發(fā)送給訂閱者的通今,下一篇講粥谬。。

下一篇地址:http://www.reibang.com/p/4792c5a30c30

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末辫塌,一起剝皮案震驚了整個(gè)濱河市漏策,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌臼氨,老刑警劉巖掺喻,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異储矩,居然都是意外死亡感耙,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門持隧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來即硼,“玉大人,你說我怎么就攤上這事屡拨≈凰郑” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵呀狼,是天一觀的道長裂允。 經(jīng)常有香客問我,道長赠潦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任草冈,我火速辦了婚禮她奥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘怎棱。我一直安慰自己哩俭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布拳恋。 她就那樣靜靜地躺著凡资,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上隙赁,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天垦藏,我揣著相機(jī)與錄音,去河邊找鬼伞访。 笑死掂骏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的厚掷。 我是一名探鬼主播弟灼,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼冒黑!你這毒婦竟也來了田绑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤抡爹,失蹤者是張志新(化名)和其女友劉穎掩驱,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體豁延,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡昙篙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了诱咏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片苔可。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖袋狞,靈堂內(nèi)的尸體忽然破棺而出焚辅,到底是詐尸還是另有隱情,我是刑警寧澤苟鸯,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布同蜻,位于F島的核電站,受9級(jí)特大地震影響早处,放射性物質(zhì)發(fā)生泄漏湾蔓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一砌梆、第九天 我趴在偏房一處隱蔽的房頂上張望默责。 院中可真熱鬧,春花似錦咸包、人聲如沸桃序。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽媒熊。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間芦鳍,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來泰國打工怜校, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留间影,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓魂贬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親裙顽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子付燥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354