EventBus3.0源碼分析(一)register

項目中用到了很多優(yōu)秀的框架,但是一直停留在會用的階段是,沒有硬著頭皮去看看它們是如何實現(xiàn)的漩氨,趁著活終于不多了饭耳,打算把項目中的用到的框架都研究研究,學(xué)習(xí)一下優(yōu)秀的代碼睛约。今天就看看EventBus吧屏镊,優(yōu)秀的框架一般使用起來都特別簡單,EventBus也不例外痰腮。EventBus的具體使用這里就不介紹了而芥,無外乎register注冊;unregister注銷膀值;@subscriber在合適的線程關(guān)心相應(yīng)的事件棍丐;post/postSticky發(fā)送事件,我們就從EventBusregister開始吧沧踏。

大多數(shù)情況下我們都是通過EventBus的靜態(tài)方法getDefault獲得默認的EventBus對象歌逢,然后register注冊。

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

很常見的單例設(shè)計模式,繼續(xù)看看EventBus初始化做了什么

public EventBus() {
    this(DEFAULT_BUILDER);
}

EventBus(EventBusBuilder builder) {
    // subscriptionsByEventType是一個map集合,存儲著每個事件對應(yīng)的subscription集合 (key:EventType value: List<subscription>)
    // subscription是對訂閱者還有訂閱方法SubscriberMethod的包裝
    // SubscriberMethod又是對method翘狱,優(yōu)先級秘案,線程類型,是否接受粘性事件的包裝
    subscriptionsByEventType = new HashMap<>();
    // typesBySubscriber也是一個map集合,存儲著每個訂閱者訂閱的事件集合( key:訂閱者 value:List<EventType>)        
    typesBySubscriber = new HashMap<>();
    // 粘性事件集合 
    stickyEvents = new ConcurrentHashMap<>(); 
    // 需要在主線程處理的事件的poster
    mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
    // 需要Background線程處理的事件的poster
    backgroundPoster = new BackgroundPoster(this);
     // 需要異步處理的事件的poster
    asyncPoster = new AsyncPoster(this);
    // 索引個數(shù)
    indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
    // 通過subscriberMethodFinder尋找訂閱者的訂閱方法 可以通過反射和索引兩種方式
    subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
            builder.strictMethodVerification, builder.ignoreGeneratedIndex);
    // 以下都是是否打印或者拋出異常的標(biāo)志位
    logSubscriberExceptions = builder.logSubscriberExceptions;
    logNoSubscriberMessages = builder.logNoSubscriberMessages;
    sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
    sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
    throwSubscriberException = builder.throwSubscriberException;
    // 是否支持事件繼承
    eventInheritance = builder.eventInheritance;
    // 異步和 BackGround 處理方式的線程池
    executorService = builder.executorService;
}

又是很常見的Builder設(shè)計模式對EventBus進行初始化潦匈。

接下來就是register

public void register(Object subscriber) {
    // 訂閱者的類型
    Class<?> subscriberClass = subscriber.getClass();
    // 通過SubscriberMethodFinder類尋找該subscriber所有帶@subscriber注解方法相關(guān)信息(SubscriberMethod)的集合阱高。
    // SubscriberMethod實際上是對帶@subscriber注解的方法一些信息的封裝。
    // 包括該方法的Method對象茬缩,事件回調(diào)的線程ThreadMode赤惊,優(yōu)先級priority,是否響應(yīng)粘性事件sticky凰锡,以及最重要的所關(guān)心的事件類型eventType未舟。
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
}

先看看上面findSubscriberMethods方法是如何獲取到SubscriberMethod集合的:

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    // 先看看緩存中有沒有
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    if (subscriberMethods != null) {
        return subscriberMethods;
    }
    // 是否忽略index,默認是false
    if (ignoreGeneratedIndex) {
        //通過反射查找
        subscriberMethods = findUsingReflection(subscriberClass);
    } else {
        //嘗試從索引中查找
        subscriberMethods = findUsingInfo(subscriberClass);
    }
    // 某些類調(diào)用了register方法,卻沒有帶@Subscribe注解的方法掂为,就會拋出如下異常(是不是很眼熟裕膀?)
    if (subscriberMethods.isEmpty()) {
        throw new EventBusException("Subscriber " + subscriberClass
                + " and its super classes have no public methods with the @Subscribe annotation");
    } else {
        // 放入緩存并且返回subscriberMethods
        METHOD_CACHE.put(subscriberClass, subscriberMethods);
        return subscriberMethods;
    }
}

從上面的代碼中可以看到有兩種方式來獲取訂閱者中帶@Subscribe注解的方法,這里其實是EventBus3.0的優(yōu)化勇哗,之前都是通過反射,而新版如果啟用了索引的話昼扛,會在編譯時期,將所有訂閱者及其帶@Subscribe注解的方法相關(guān)信息通過硬編碼的方式存放到map中智绸,這樣的話就可以直接通過key來獲取了野揪,下面是編譯時期生成的索引類的部分代碼:

 //存放所有的訂閱者及其帶`@Subscribe`注解的方法相關(guān)信息
private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;

static {
    SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();
    // 將所有的訂閱者及其關(guān)心的事件存放到map中
    putIndex(new SimpleSubscriberInfo(MainActivity.class, true, new SubscriberMethodInfo[] {
        new SubscriberMethodInfo("onEventHanlder1", String.class, ThreadMode.MAIN),
        new SubscriberMethodInfo("onEventHanlder2", String.class, ThreadMode.MAIN),
        new SubscriberMethodInfo("onEventHanlder3", String.class, ThreadMode.MAIN),
        new SubscriberMethodInfo("onEventHanlderInteger", Integer.class, ThreadMode.MAIN),
    }));

    putIndex(new SimpleSubscriberInfo(SecondActivity.class, true, new SubscriberMethodInfo[] {
        new SubscriberMethodInfo("onSecondEvent1", String.class, ThreadMode.MAIN),
        new SubscriberMethodInfo("onSecondEvent2", String.class, ThreadMode.MAIN, 0, true),
        new SubscriberMethodInfo("onSecondEvent3", Integer.class, ThreadMode.MAIN),
        new SubscriberMethodInfo("onSecondEvent4", Integer.class, ThreadMode.MAIN),
        new SubscriberMethodInfo("onSecondEvent5", Integer.class, ThreadMode.MAIN),
        new SubscriberMethodInfo("onSecondEvent6", Integer.class, ThreadMode.MAIN),
    }));

}

private static void putIndex(SubscriberInfo info) {
    SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
}

先來看看通過反射的方式獲取List<SubscriberMethod>集合

private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
    // 從對象池中取一個FindState 
    FindState findState = prepareFindState();
    // 初始化FindState
    findState.initForSubscriber(subscriberClass);
    while (findState.clazz != null) {
        // 通過反射尋找訂閱方法相關(guān)信息
        findUsingReflectionInSingleClass(findState);
        // 有父類的話 findState.clazz 設(shè)置為父類
        // 沒有的話findState.clazz = null访忿, 結(jié)束尋找
        findState.moveToSuperclass();
    }
    // 釋放資源并且方法findState中的subscriberMethods
    return getMethodsAndRelease(findState);
}

//通過反射查找
private void findUsingReflectionInSingleClass(FindState findState) {
    Method[] methods;
    try {
        // This is faster than getMethods, especially when subscribers are fat classes like Activities
        // 之所以比getMethods快,是因為getDeclaredMethods不包括繼承的方法
        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();
        // 判斷權(quán)限修飾符是否是public,是否是抽象方法斯稳,是否靜態(tài)方法
        if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            // 是否只有一個參數(shù)
            if (parameterTypes.length == 1) {
                Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                // 帶注解
                if (subscribeAnnotation != null) {
                    // 關(guān)心的事件類型
                    Class<?> eventType = parameterTypes[0];
                    // 校驗是否需要添加
                    if (findState.checkAdd(method, eventType)) {
                        // 事件回調(diào)的線程
                        ThreadMode threadMode = subscribeAnnotation.threadMode();
                        // 將該方法相關(guān)信息(method海铆,eventType,threadMode挣惰,priority卧斟,sticky)包裝下添加到subscriberMethods集合中
                        findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                    }
                }
            } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                // 帶注解的方法參數(shù)必須是一個參數(shù)
                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)) {
            // 拋異常,帶注解的方法必須是public憎茂,非靜態(tài)珍语,非抽象(是不是很眼熟,有時候手快少寫public或者寫成了private)
            String methodName = method.getDeclaringClass().getName() + "." + method.getName();
            throw new EventBusException(methodName +
                    " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
        }
    }
}

再來看看通過索引的方式吧

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
    FindState findState = prepareFindState();
    findState.initForSubscriber(subscriberClass);
    while (findState.clazz != null) {
        // 取出自動生成的索引中的subscriberInfo 
        findState.subscriberInfo = getSubscriberInfo(findState);
        // 如果僅僅啟用了索引竖幔,但未將索引添加到eventbus中板乙,還是采用反射的方式
        if (findState.subscriberInfo != null) {
            // subscriberInfo中包含除了method以外的subscriberMethods所需要的字段
            // 所以通過subscriberInfo的getSubscriberMethods方法進而通過createSubscriberMethod的方法獲取method對象
            // 并且創(chuàng)建SubscriberMethod對象
            SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
            for (SubscriberMethod subscriberMethod : array) {
                if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                    findState.subscriberMethods.add(subscriberMethod);
                }
            }
        } else {
            // 反射的方式
            findUsingReflectionInSingleClass(findState);
        }
        // 有父類的話 findState.clazz 設(shè)置為父類
        // 沒有的話findState.clazz = null, 結(jié)束尋找
        findState.moveToSuperclass();
    }
     // 釋放資源并且方法findState中的subscriberMethods
    return getMethodsAndRelease(findState);
}

通過上述的兩種方式找出該訂閱者所有的訂閱方法信息拳氢, 我們繼續(xù)回到egister方法中募逞,走完剩下的邏輯

synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    // 事件類型
    Class<?> eventType = subscriberMethod.eventType;
    // 用Subscription類包裝訂閱者以及訂閱方法
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    // 取出所有訂閱了該事件的Subscription集合
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    // 可能此eventType是第一次出現(xiàn),初始化一下
    if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
         // 該訂閱者register了兩次會出現(xiàn)此異常
         // 可以自行查看Subscription equals方法 細節(jié)還是挺多的
        if (subscriptions.contains(newSubscription)) {
            throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                    + eventType);
        }
    }
  
   // 根據(jù)優(yōu)先級重新排序下
    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;
        }
    }
    // 該訂閱者所有的關(guān)心的事件類型集合
    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    // 第一次先初始化
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    subscribedEvents.add(eventType);
    
    // 如果當(dāng)前訂閱方法接受粘性事件,并且訂閱方法關(guān)心的事件在粘性事件集合中馋评,那么將該event事件post給subscriber
    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);
        }
    }
}

register的流程大致分析完了放接,概況下就是通過合適的方式找到訂閱者中所有的訂閱方法及其信息、關(guān)心的事件留特,然后存到對應(yīng)的集合中纠脾,如果存在關(guān)心粘性事件的訂閱方法,那么在判斷是否有之前post過的sticky事件蜕青,有則立即post給該訂閱者苟蹈。最后來張流程圖裝裝逼吧:

register流程圖

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市市咆,隨后出現(xiàn)的幾起案子汉操,更是在濱河造成了極大的恐慌再来,老刑警劉巖蒙兰,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異芒篷,居然都是意外死亡搜变,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門针炉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來挠他,“玉大人,你說我怎么就攤上這事篡帕≈城郑” “怎么了贸呢?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長拢军。 經(jīng)常有香客問我楞陷,道長,這世上最難降的妖魔是什么茉唉? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任固蛾,我火速辦了婚禮,結(jié)果婚禮上度陆,老公的妹妹穿的比我還像新娘艾凯。我一直安慰自己,他們只是感情好懂傀,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布趾诗。 她就那樣靜靜地躺著,像睡著了一般蹬蚁。 火紅的嫁衣襯著肌膚如雪沧竟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天缚忧,我揣著相機與錄音悟泵,去河邊找鬼。 笑死闪水,一個胖子當(dāng)著我的面吹牛糕非,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播球榆,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼朽肥,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了持钉?” 一聲冷哼從身側(cè)響起衡招,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎每强,沒想到半個月后始腾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡空执,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年浪箭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辨绊。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡奶栖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情宣鄙,我是刑警寧澤袍镀,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站冻晤,受9級特大地震影響流椒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜明也,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一宣虾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧温数,春花似錦绣硝、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至够傍,卻和暖如春甫菠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背冕屯。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工寂诱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人安聘。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓痰洒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親浴韭。 傳聞我的和親對象是個殘疾皇子丘喻,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

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

  • 簡介 我們知道,Android應(yīng)用主要是由4大組件構(gòu)成念颈。當(dāng)我們進行組件間通訊時泉粉,由于位于不同的組件,通信方式相對麻...
    Whyn閱讀 537評論 0 1
  • 對于Android開發(fā)老司機來說肯定不會陌生榴芳,它是一個基于觀察者模式的事件發(fā)布/訂閱框架嗡靡,開發(fā)者可以通過極少的代碼...
    飛揚小米閱讀 1,475評論 0 50
  • 我每周會寫一篇源代碼分析的文章,以后也可能會有其他主題.如果你喜歡我寫的文章的話,歡迎關(guān)注我的新浪微博@達達達達s...
    SkyKai閱讀 24,927評論 23 184
  • EventBus用法及源碼解析目錄介紹1.EventBus簡介1.1 EventBus的三要素1.2 EventB...
    楊充211閱讀 1,892評論 0 4
  • 最近接二連三的事肌括,感覺自己受騙被利用,結(jié)果原來是這樣… 事件1 媽媽抱怨訴苦,我以為媽媽需要幫助谍夭,結(jié)果黑滴,我出錢出力...
    天鷺閱讀 818評論 0 0