天天看點

【Bugly幹貨分享】老司機教你 “飙” EventBus 3

EventBus對于Android開發老司機來說肯定不會陌生,它是一個基于觀察者模式的事件釋出/訂閱架構,開發者可以通過極少的代碼去實作多個子產品之間的通信,而不需要以層層傳遞接口的形式去單獨建構通信橋梁。進而降低因多重回調導緻的子產品間強耦合,同時避免産生大量内部類。它擁有使用友善,性能高,接入成本低和支援多線程的優點,實乃子產品解耦、代碼重構必備良藥。

Bugly 技術幹貨系列内容主要涉及移動開發方向,是由 Bugly 邀請騰訊内部各位技術大咖,通過日常工作經驗的總結以及感悟撰寫而成,内容均屬原創,轉載請标明出處。

作為Markus Junginger大神耗時4年打磨、超過1億接入量、Github 9000+ star的明星級元件,分析EventBus的文章早已是數不勝數。本文的題目是“教你飙巴士”,而這輛Bus之是以可以飙起來,是因為作者在EventBus 3中引入了EventBusAnnotationProcessor(注解分析生成索引)技術,大大提高了EventBus的運作效率。而分析這個加速器的資料在網上很少,是以本文會把重點放在分析這個EventBus 3的新特性上,同時分享一些踩坑經驗,并結合源碼分析及UML圖,以直覺的形式和大家一起學習EventBus 3的用法及運作原理。

1. 新手上路——使用EventBus

1.1 導入元件:

打開App的build.gradle,在dependencies中添加最新的EventBus依賴:

compile 'org.greenrobot:eventbus:3.0.0'
           

如果不需要索引加速的話,就可以直接跳到第二步了。而要應用最新的EventBusAnnotationProcessor則比較麻煩,因為注解解析依賴于android-apt-plugin。我們一步一步來,首先在項目gradle的dependencies中引入apt編譯插件:

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

然後在App的build.gradle中應用apt插件,并設定apt生成的索引的包名和類名:

apply plugin: 'com.neenbedankt.android-apt'
apt {
    arguments {
        eventBusIndex "com.study.sangerzhong.studyapp.MyEventBusIndex"
    }
}
           

接着在App的dependencies中引入EventBusAnnotationProcessor:

apt 'org.greenrobot:eventbus-annotation-processor:3.0.1'
           

這裡需要注意,如果應用了EventBusAnnotationProcessor卻沒有設定arguments的話,編譯時就會報錯:

No option eventBusIndex passed to annotation processor

此時需要我們先編譯一次,生成索引類。編譯成功之後,就會發現在

\ProjectName\app\build\generated\source\apt\PakageName\

下看到通過注解分析生成的索引類,這樣我們便可以在初始化EventBus時應用我們生成的索引了。

1.2 初始化EventBus

EventBus預設有一個單例,可以通過

getDefault()

擷取,也可以通過

EventBus.builder()

構造自定義的EventBus,比如要應用我們生成好的索引時:

EventBus mEventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();
           

如果想把自定義的設定應用到EventBus預設的單例中,則可以用

installDefaultEventBus()

方法:

EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
           

1.3 定義事件:

所有能被執行個體化為Object的執行個體都可以作為事件:

public class DriverEvent { public String info; }
           

在最新版的eventbus 3中如果用到了索引加速,事件類的修飾符必須為public,不然編譯時會報錯:

Subscriber method must be public

1.4 監聽事件:

首先把作為訂閱事件的子產品通過EventBus注冊監聽:

mEventBus.register(this);
           

在3.0之前,注冊監聽需要區分是否監聽黏性(sticky)事件,監聽EventBus事件的子產品需要實作以onEvent開頭的方法。如今改為在方法上添加注解的形式:

@Subscribe(threadMode = ThreadMode.POSTING, priority = 0, sticky = true)
public void handleEvent(DriverEvent event) {
    Log.d(TAG, event.info);
}
           

注解有三個參數,threadMode為回調所在的線程,priority為優先級,sticky為是否接收黏性事件。排程機關從類細化到了方法,對方法的命名也沒有了要求,友善混淆代碼。但注冊了監聽的子產品必須有一個标注了Subscribe注解方法,不然在register時會抛出異常:

Subscriber class XXX and its super classes have no public methods with the @Subscribe annotation

1.5 發送事件:

調用post或者postSticky即可:

mEventBus.post(new DriverEvent("magnet:?xt=urn:btih……"));
           

到此我們就完成了使用EventBus的學習,可以在代碼中盡情地飚車了。項目接入了EventBus之後會有什麼好處呢?舉一個常見的用例:ViewPager中Fragment的互相通信,就不需要在容器中定義各種接口,可以直接通過EventBus來實作互相回調,這樣就把邏輯從ViewPager這個容器中剝離出來,使代碼閱讀起來更加直覺。

在實際項目的使用中,register和unregister通常與Activity和Fragment的生命周期相關,ThreadMode.MainThread可以很好地解決Android的界面重新整理必須在UI線程的問題,不需要再回調後用Handler中轉(EventBus中已經自動用Handler做了處理),黏性事件可以很好地解決post與register同時執行時的異步問題(這個在原理中會說到),事件的傳遞也沒有序列化與反序列化的性能消耗,足以滿足我們大部分情況下的子產品間通信需求。

2. 變身老司機——EventBus原理分析

在平時使用中我們不需要關心EventBus中對事件的分發機制,但要成為能夠快速排查問題的老司機,我們還是得熟悉它的工作原理,下面我們就透過UML圖來學習一下。

2.1 核心架構

EventBus的核心工作機制透過作者Blog中的這張圖就能很好地了解:

訂閱者子產品需要通過EventBus訂閱相關的事件,并準備好處理事件的回調方法,而事件釋出者則在适當的時機把事件post出去,EventBus就能幫我們搞定一切。在架構方面,EventBus 3與之前稍老版本有不同,我們直接看架構圖:

先看核心類EventBus,其中subscriptionByEventType是以事件的類為key,訂閱者的回調方法為value的映射關系表。也就是說EventBus在收到一個事件時,就可以根據這個事件的類型,在subscriptionByEventType中找到所有監聽了該事件的訂閱者及處理事件的回調方法。而typesBySubscriber則是每個訂閱者所監聽的事件類型表,在取消注冊時可以通過該表中儲存的資訊,快速删除subscriptionByEventType中訂閱者的注冊資訊,避免周遊查找。注冊事件、發送事件和登出都是圍繞着這兩個核心資料結構來展開。上面的Subscription可以了解為每個訂閱者與回調方法的關系,在其他子產品發送事件時,就會通過這個關系,讓訂閱者執行回調方法。

回調方法在這裡被封裝成了SubscriptionMethod,裡面儲存了在需要反射invoke方法時的各種參數,包括優先級,是否接收黏性事件和所線上程等資訊。而要生成這些封裝好的方法,則需要SubscriberMethodFinder,它可以在regster時得到訂閱者的所有回調方法,并封裝傳回給EventBus。而右邊的加速器子產品,就是為了提高SubscriberMethodFinder的效率,會在第三章詳細介紹,這裡就不再啰嗦。

而下面的三個Poster,則是EventBus能在不同的線程執行回調方法的核心,我們根據不同的回調方式來看:

  1. POSTING(在調用post所在的線程執行回調):不需要poster來排程,直接運作。
  2. MAIN(在UI線程回調):如果post所線上程為UI線程則直接執行,否則則通過mainThreadPoster來排程。
  3. BACKGROUND(在Backgroud線程回調):如果post所線上程為非UI線程則直接執行,否則則通過backgroundPoster來排程。
  4. ASYNC(交給線程池來管理):直接通過asyncPoster排程。

可以看到,不同的Poster會在post事件時,排程相應的事件隊列PendingPostQueue,讓每個訂閱者的回調方法收到相應的事件,并在其注冊的Thread中運作。而這個事件隊列是一個連結清單,由一個個PendingPost組成,其中包含了事件,事件訂閱者,回調方法這三個核心參數,以及需要執行的下一個PendingPost。

至此EventBus 3的架構就分析完了,與之前EventBus老版本最明顯的差別在于:分發事件的排程機關從訂閱者,細化成了訂閱者的回調方法。也就是說每個回調方法都有自己的優先級,執行線程和是否接收黏性事件,提高了事件分發的靈活程度,接下來我們在看核心功能的實作時更能展現這一點。

2.2 register

簡單來說就是:根據訂閱者的類來找回調方法,把訂閱者和回調方法封裝成關系,并儲存到相應的資料結構中,為随後的事件分發做好準備,最後處理黏性事件:

值得注意的是,老版本的EventBus是允許事件訂閱者以不同的ThreadMode去監聽同一個事件的,即在一個訂閱者中有多個方法訂閱一個事件,此時是無法保證這幾個回調的先後順序的,因為不同的線程回調是通過Handler排程的,有可能單個線程中的事件過多,事件受阻,回調則會比較慢。如今EventBus 3使用了注解來表示回調後,還可以出現相同的ThreadMode的回調方法監聽相同的事件,此時會根據注冊的先後順序,先注冊先分發事件,注意不是先收到事件,收到事件的順序還是得看poster中Handler的排程。

2.3 post

總的來說就是分析事件,得到所有監聽該事件的訂閱者的回調方法,并利用反射來invoke方法,實作回調:

這裡就能看到poster的排程事件功能,同時可以看到排程的機關細化成了Subscription,即每一個方法都有自己的優先級和是否接收黏性事件。在源代碼中為了保證post執行不會出現死鎖,等待和對同一訂閱者發送相同的事件,增加了很多線程保護鎖和标志位,值得我們每個開發者學習。

2.4 unregister

登出就比較簡單了,把在注冊時往兩個資料結構中添加的訂閱者資訊删除即可:

上面經常會提到黏性事件,為什麼要有這個設計呢?這裡舉個例子:我想在登陸成功後自動播放歌曲,而登陸和監聽登陸監聽是同時進行的。在這個前提下,如果登陸流程走得特别快,在登陸成功後播放子產品才注冊了監聽。此時播放子產品便會錯過了【登陸成功】的事件,出現“雖然登陸成功了,回調卻沒執行”的情況。而如果【登陸成功】這個事件是一個黏性事件的話,那麼即使我後來才注冊了監聽(并且回調方法設定為監聽黏性事件),則回調就能在注冊的那一刻被執行,這個問題就能被優雅地解決,而不需要額外去定義其他标志位。

至此大家對EventBus的運作原理應該有了一定的了解,雖然看起來像是一個複雜耗時的自動機,但大部分時候事件都是一瞬間就能分發到位的,而大家關心的性能問題反而是發生在注冊EventBus的時候,因為需要周遊監聽者的所有方法去找到回調的方法。作者也提到運作時注解的性能在Android上并不理想,為了解決這個問題,作者才會以索引的方式去生成回調方法表(下一章會詳細介紹)。而EventBus源碼分析的文章早已是數不勝數,這裡就不再大段大段地貼代碼了,主要以類圖和流程圖的形式讓大家直覺地了解EventBus3的整體架構及核心功能的實作原理,把源碼分析留到後面介紹EventBusAnnotationProcessor中再進行。大家如果想要深入學習EventBus 3的話,在本文結尾的參考文章中有很多寫得很棒的源碼分析。

3. 渦輪引擎——索引加速

在EventBus 3的介紹中,作者提到以前的版本為了保證性能,在周遊尋找訂閱者的回調方法時使用反射而不是注解。但現在卻能在使用注解的前提下,大幅度提高性能,同時作者在部落格中放出了這張對比圖:

可以看到在性能方面,EventBus 3由于使用了注解,比起使用反射來周遊方法的2.4版本遜色不少。但開啟了索引後性能像打了雞血一樣,遠遠超出之前的版本。這裡我們就來分析一下這個提高EventBus性能的“渦輪引擎”。(下面的源碼分析為了友善閱讀,添加了部分注釋,并删減了部分源碼,如果有疑問的話可以到官方的github上檢視原版源碼)

首先我們知道,索引是在初始化EventBus時通過

EventBusBuilder.addIndex(SubscriberInfoIndex index)

方法傳進來的,我們就先看看這個方法:

public EventBusBuilder addIndex(SubscriberInfoIndex index) {
    if(subscriberInfoIndexes == null) {
        subscriberInfoIndexes = new ArrayList<>();
    }
    subscriberInfoIndexes.add(index);
    return this;
}
           

可以看到,傳進來的索引資訊會儲存在subscriberInfoIndexes這個List中,後續會通過EventBusBuilder傳到相應EventBus的SubscriberMethodFinder執行個體中。我們先來分析SubscriberInfoIndex這個參數:

public interface SubscriberInfoIndex {
    SubscriberInfo getSubscriberInfo(Class<?> subscriberClass);
}
           

可見索引隻需要做一件事情——就是能拿到訂閱者的資訊。而實作這個接口的類如果我們沒有編譯過,是找不到的。這裡就得看我們在一開始在配置gradle時導入的EventBusAnnotationProcessor:

@SupportedAnnotationTypes("org.greenrobot.eventbus.Subscribe")
@SupportedOptions(value = {"eventBusIndex", "verbose"})
public class EventBusAnnotationProcessor extends AbstractProcessor {
    /** Found subscriber methods for a class (without superclasses). 被注解表示的方法資訊 */ 
    private final ListMap<TypeElement, ExecutableElement> methodsByClass = new ListMap<>();
    private final Set<TypeElement> classesToSkip = new HashSet<>(); // checkHasErrors檢查出來的異常方法

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
        Messager messager = processingEnv.getMessager();
        try {
            String index = processingEnv.getOptions().get(OPTION_EVENT_BUS_INDEX);
            if (index == null) { // 如果沒有在gradle中配置apt的argument,編譯就會在這裡報錯
                messager.printMessage(Diagnostic.Kind.ERROR, "No option " + OPTION_EVENT_BUS_INDEX +
                        " passed to annotation processor");
                return false;
            }
            /** ... */
            collectSubscribers(annotations, env, messager); // 根據注解拿到所有訂閱者的回調方法資訊
            checkForSubscribersToSkip(messager, indexPackage); // 篩掉不符合規則的訂閱者
            if (!methodsByClass.isEmpty()) {
                createInfoIndexFile(index); // 生成索引類
            } 
            /** 列印錯誤 */
    }

    /** 下面這些方法就不再貼出具體實作了,我們了解它們的功能就行 */
    private void collectSubscribers // 周遊annotations,找出所有被注解辨別的方法,以初始化methodsByClass
    private boolean checkHasNoErrors // 過濾掉static,非public和參數大于1的方法
    private void checkForSubscribersToSkip // 檢查methodsByClass中的各個類,是否存在非public的父類和方法參數
    /** 下面這三個方法會把methodsByClass中的資訊寫到相應的類中 */
    private void writeCreateSubscriberMethods
    private void createInfoIndexFile
    private void writeIndexLines
}
           

至此便揭開了索引生成的秘密,是在編譯時apt插件通過EventBusAnnotationProcessor分析注解,并利用注解辨別的相關類的資訊去生成相關的類。writeCreateSubscriberMethods中調用了很多IO函數,很容易了解,這裡就不貼了,我們直接看生成出來的類:

/** This class is generated by EventBus, do not edit. */
public class MyEventBusIndex implements SubscriberInfoIndex {
private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;
    static {
        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();
        // 每有一個訂閱者類,就調用一次putIndex往索引中添加相關的資訊
        putIndex(new SimpleSubscriberInfo(com.study.sangerzhong.studyapp.ui.MainActivity.class, true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onEvent", com.study.sangerzhong.studyapp.ui.MainActivity.DriverEvent.class, ThreadMode.POSTING, 0, false), 
            // 類中每一個被Subscribe辨別的方法都在這裡添加進來
        })); 
    }
    // 下面的代碼就是EventBusAnnotationProcessor中寫死的了
    private static void putIndex(SubscriberInfo info) {
        SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
    }

    @Override
    public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
        SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
        if (info != null) {
            return info;
        } else {
            return null;
        }
    }
}
           

可見,子類中hardcode了所有注冊了EventBus的類中被Subscribe注解辨別的方法資訊,包括方法名、方法參數類型等資訊。并把這些資訊封裝到SimpleSubscriberInfo中,我們拿到的索引其實就是以訂閱者的類為Key、SimpleSubscriberInfo為value的哈希表。而這些hardcode都是在編譯的時候生成的,避免了在在EventBus.register()時才去周遊查找生成,進而把在注冊時需要周遊訂閱者所有方法的行為,提前到在編譯時完成了。

索引的生成我們已經明白了,那麼它是在哪裡被應用的呢?我們記得在注冊時,EventBus會通過SubscriberMethodFinder來周遊注冊對象的Class的所有方法,篩選出符合規則的方法,并作為訂閱者在接收到事件時執行的回調,我們直接來看看

SubscriberMethodFinder.findSubscriberMethods()

這個核心方法:

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass); 
    if (subscriberMethods != null) {
        return subscriberMethods; // 先去方法緩存裡找,找到直接傳回
    }
    if (ignoreGeneratedIndex) { // 是否忽略設定的索引
        subscriberMethods = findUsingReflection(subscriberClass);
    } else {
        subscriberMethods = findUsingInfo(subscriberClass);
    }
    /** 把找到的方法儲存到METHOD_CACHE裡并傳回,找不到直接抛出異常 */
}
           

可以看到其中findUsingInfo()方法就是去索引中查找訂閱者的回調方法,我們戳進去看看這個方法的實作:

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
    // 最新版的EventBus3中,尋找方法時所需的臨時變量都被封裝到了FindState這個靜态内部類中
    FindState findState = prepareFindState(); // 到對象池中取得上下文,避免頻繁創造對象,這個設計很贊
    findState.initForSubscriber(subscriberClass); // 初始化尋找方法的上下文
    while (findState.clazz != null) { // 子類找完了,會繼續去父類中找
        findState.subscriberInfo = getSubscriberInfo(findState); // 獲得訂閱者類的相關資訊
        if (findState.subscriberInfo != null) { // 上一步能拿到相關資訊的話,就開始把方法數組封裝成List
            SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
            for (SubscriberMethod subscriberMethod : array) {
                if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                    // checkAdd是為了避免在父類中找到的方法是被子類重寫的,此時應該保證回調時執行子類的方法
                    findState.subscriberMethods.add(subscriberMethod);
                }
            }
        } else { // 索引中找不到,降級成運作時通過注解和反射去找
            findUsingReflectionInSingleClass(findState);
        }
        findState.moveToSuperclass(); // 上下文切換成父類
    }
    return getMethodsAndRelease(findState); // 找完後,釋放FindState進對象池,并傳回找到的回調方法
}
           

可以看到EventBus中在查找訂閱者的回調方法時是能處理好繼承關系的,不僅會去周遊父類,而且還會避免因為重寫方法導緻執行多次回調。其中需要關心的是getSubscriberInfo()是如何傳回索引資料的,我們繼續深入:

private SubscriberInfo getSubscriberInfo(FindState findState) {
    if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) { // subscriberInfo已有執行個體,證明本次查找需要查找上次找過的類的父類
        SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
        if (findState.clazz == superclassInfo.getSubscriberClass()) { // 确定是所需查找的類
            return superclassInfo;
        }
    }
    if (subscriberInfoIndexes != null) { // 從我們傳進來的subscriberInfoIndexes中擷取相應的訂閱者資訊
        for (SubscriberInfoIndex index : subscriberInfoIndexes) {
            SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
            if (info != null) { return info; }
        }
    }
    return null;
}
           

可見就在這個方法裡面,應用到了我們生成的索引,避免我們需要在findSubscriberMethods時去調用耗時的findUsingReflection方法。在實際使用中,Nexus6上一個Activity注冊EventBus需要10毫秒左右,而使用了索引後能降低到3毫秒左右,效果非常明顯。雖然這個索引的實作邏輯有點繞,而且還存在一些坑(比如後面講到的混淆問題),但實作的手段非常巧妙,尤其是“把耗時的操作在編譯的時候完成”和“用對象池減少建立對象的性能開銷”的思想值得我們開發者借鑒。

4. 駕駛寶典——踩坑與經驗

4.1 混淆問題

混淆作為版本釋出必備的流程,經常會鬧出很多奇奇怪怪的問題,且不友善定位,尤其是EventBus這種依賴反射技術的庫。通常情況下都會把相關的類和回調方法都keep住,但這樣其實會留下被人反編譯後破解的後顧之憂,是以我們的目标是keep最少的代碼。

首先,因為EventBus 3棄用了反射的方式去尋找回調方法,改用注解的方式。作者的意思是在混淆時就不用再keep住相應的類和方法。但是我們在運作時,卻會報

java.lang.NoSuchFieldError: No static field POSTING

。網上給出的解決辦法是keep住所有eventbus相關的代碼:

-keep class de.greenrobot.** {*;}
           

其實我們仔細分析,可以看到是因為在SubscriberMethodFinder的findUsingReflection方法中,在調用Method.getAnnotation()時擷取ThreadMode這個enum失敗了,是以我們隻需要keep住這個enum就可以了(如下)。

-keep public enum org.greenrobot.eventbus.ThreadMode { public static *; }
           

這樣就能正常編譯通過了,但如果使用了索引加速,是不會有上面這個問題的。因為在找方法時,調用的不是findUsingReflection,而是findUsingInfo。但是使用了索引加速後,編譯後卻會報新的錯誤:

Could not find subscriber method in XXX Class. Maybe a missing ProGuard rule?

這就很好了解了,因為生成索引GeneratedSubscriberIndex是在代碼混淆之前進行的,混淆之後類名和方法名都不一樣了(上面這個錯誤是方法無法找到),得keep住所有被Subscribe注解标注的方法:

-keepclassmembers class * {
    @de.greenrobot.event.Subscribe <methods>;
}
           

是以又倒退回了EventBus2.4時不能混淆onEvent開頭的方法一樣的處境了。是以這裡就得權衡一下利弊:使用了注解不用索引加速,則隻需要keep住EventBus相關的代碼,現有的代碼可以正常的進行混淆。而使用了索引加速的話,則需要keep住相關的方法和類。

4.2 跨程序問題

目前EventBus隻支援跨線程,而不支援跨程序。如果一個app的service起到了另一個程序中,那麼注冊監聽的子產品則會收不到另一個程序的EventBus發出的事件。這裡可以考慮利用IPC做映射表,并在兩個程序中各維護一個EventBus,不過這樣就要自己去維護register和unregister的關系,比較繁瑣,而且這種情況下通常用廣播會更加友善,大家可以思考一下有沒有更優的解決方案。

4.3 事件環路問題

在使用EventBus時,通常我們會把兩個子產品互相監聽,來達到一個互相回調通信的目的。但這樣一旦出現死循環,而且如果沒有相應的日志資訊,很難定位問題。是以在使用EventBus的子產品,如果在回調上有環路,而且回調方法複雜到了一定程度的話,就要考慮把接收事件專門封裝成一個子子產品,同時考慮避免出現事件環路。

5. 車神之路——寫在最後

當然,EventBus并不是重構代碼的唯一之選。作為觀察者模式的“同門師兄弟”——RxJava,作為功能更為強大的響應式程式設計架構,可以輕松實作EventBus的事件總線功能(RxBus)。但畢竟大型項目要接入RxJava的成本高,複雜的操作符需要開發者投入更多的時間去學習。是以想在成熟的項目中快速地重構、解耦子產品,EventBus依舊是我們的不二之選。

本文總結了EventBus 3的使用方法,運作原理和一些新特性,讓大家能直覺地看到這個元件的優點和缺點,同時讓大家在考慮是否在項目中引入EventBus時心裡有個底。最後感謝Markus Junginger大神開源了如此實用的元件,以及組内同僚在筆者探究EventBus原理時提供的幫助,希望大家在看完本文後都能有所收獲,成為NB的Android開發老司機。

參考:

  1. EventBus 3 Beta
  2. Markus Junginger - EventBus 3 beta announced at droidcon
  3. Skykai - EventBus 3.0 源代碼分析
  4. yydcdut - EventBus3.0源碼解析
  5. TmWork - EventBus源碼解析
  6. 鴻洋 - Android打造編譯時注解解析架構
  7. YoKey - 用RxJava實作事件總線(Event Bus)

更多精彩内容歡迎關注Bugly的微信公衆賬号:

騰訊 Bugly 是一款專為移動開發者打造的品質監控工具,幫助開發者快速,便捷的定位線上應用崩潰的情況以及解決方案。智能合并功能幫助開發同學把每天上報的數千條 Crash 根據根因合并分類,每日日報會列出影響使用者數最多的崩潰,精準定位功能幫助開發同學定位到出問題的代碼行,實時上報可以在釋出後快速的了解應用的品質情況,适配最新的 iOS, Android 官方作業系統,鵝廠的工程師都在使用,快來加入我們吧…