Android事件總線框架設計:EventBus3.0源碼詳解與架構分析(上)

發佈/訂閱事件總線。它可讓咱們很輕鬆的實如今Android各個組件之間傳遞消息,而且代碼的可讀性更好,耦合度更低。
java

  • 簡化組件之間的通信
  • 事件的發送着與接受者徹底解耦
  • 完美解決UI(如:Activities、Fragments)和後臺線程之間切換
  • 避免複雜且容易出錯的依賴關係和生命週期問題

1.開始EventBus之旅

在使用EventBus以前,須要添加依賴:模塊的build.gradle文件中android

dependencies {
    implementation 'org.greenrobot:eventbus:3.1.1'
}

一、步驟一:定義事件
事件沒有任何特定的要求,一個普通的java對象網絡

public class MessageEvent {
    public final String message;

    public MessageEvent(String message) {
        this.message = message;
    }
}

二、步驟二:準備訂閱者
訂閱者實現事件處理方法(也稱爲 訂閱方法),這些方法在事件發佈時調用。它們要用@Subscribe註解定義,並指定線程模型。
注意:EventBus3.0方法名能夠隨意起,不像EventBus2.0版本有命名約定。併發

// 當MessageEvent事件發佈時,該方法在UI線程調用(由於線程模型指定爲UI線程)
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(MessageEvent event) {
        Log.d(TAG, event.message + " ### receive thread name: " + Thread.currentThread().getName());
    }

    // 該方法在發佈MessageEvent事件的線程調用(沒有指定線程模型,會在發佈事件線程執行)
    @Subscribe
    public void handleSomethingElse(MessageEvent event) {     
    }

訂閱者也須要註冊或者註銷到總線,只有訂閱者被註冊才能收到消息。在Android中,一般根據Activity或者Fragment的生命週期進行註冊,例如:Activity中的onCreate/onDestroyapp

// Activity 中

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        EventBus.getDefault().register(this);
    }

    @Override
    protected void onDestroy() {
        EventBus.getDefault().unregister(this);
        super.onDestroy();
    }

三、發佈事件
在代碼的任何地方均可以發佈事件。全部註冊訂閱者將接收匹配事件類型async

EventBus.getDefault().post(new MessageEvent("post thread name: " + Thread.currentThread().getName());

2.線程模型(ThreadMode)

EventBus提供了線程模型,能夠幫助咱們處理線程:訂閱方法能夠在不一樣於發佈事件的線程中處理。常見用例涉及到UI更新,在Android中,UI更新必須在UI(main)線程執行。另外一方面,網絡或者任何耗時的任務不能運行在主線程中。EventBus幫助咱們處理這個任務並與UI線程同步(無需深刻研究線程切換,或使用AsyncTask等)。ide

在EventBus中,可使用四個線程模型之必定義調用事件處理函數的線程。函數

ThreadMode: POSTING (默認)

訂閱事件函數指定了線程模型爲POSTING:該事件在哪一個線程發佈出來的,事件處理函數就會在這個線程中運行,也就是說發佈事件和事件處理函數在同一個線程。在線程模型爲POSTING的事件處理函數中儘可能避免執行耗時操做,由於它會阻塞事件的傳遞,甚至有可能會引發ANR。post

// ThreadMode 是可選的,默認值爲ThreadMode.POSTING
// 與發佈事件在同一個線程調用
@Subscribe(threadMode = ThreadMode.POSTING)
public void onMessageEventPosting(MessageEvent event) {
    Log.d(TAG, "posting: " + Thread.currentThread().getName());
}
ThreadMode: MAIN

訂閱事件函數指定了線程模型爲MAIN:不論事件是在哪一個線程中發佈出來的,該事件處理函數都會在UI線程中執行。該方法能夠用來更新UI,可是不能處理耗時操做。性能

// 在Android UI 主線程調用
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEventMain(MessageEvent event) {
    Log.d(TAG, "main: " + Thread.currentThread().getName());
}
ThreadMode: MAIN_ORDERED

訂閱事件函數指定了線程模型爲MAIN_ORDERED:不論事件在哪一個線程中發佈出去,該事件處理函數都會在UI線程中執行。與MAIN模型區別在於:事件處理函數老是在主線程排隊執行

// 在Android UI 主線程調用
@Subscribe(threadMode = ThreadMode.MAIN_ORDERED)
public void onMessageEventMainOrdered(MessageEvent event) {
    Log.d(TAG, "main_ordered: " + Thread.currentThread().getName());
}
ThreadMode: BACKGROUND

訂閱事件函數指定了線程模型爲BACKGROUND:若是事件是在UI線程中發佈出來的,那麼該事件處理函數就會在單線程串行執行,儘可能不要堵塞線程;若是事件原本就是子線程中發佈出來的,那麼該事件處理函數直接在發佈事件的線程中執行。在此事件處理函數中禁止進行UI更新操做。

// 在後臺線程調用
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onMessageEventBackground(MessageEvent event) {
    Log.d(TAG, "background: " + Thread.currentThread().getName());
}
ThreadMode: ASYNC

訂閱事件函數指定了線程模型爲ASYNC:不管事件在哪一個線程發佈,該事件處理函數都會在新建的子線程中執行。發佈事件不須要等待,適合耗時的事件處理函數,但避免同時觸發大量長時間的事件處理函數。 一樣,此事件處理函數中禁止進行UI更新操做。

// 在單獨的線程調用
@Subscribe(threadMode = ThreadMode.ASYNC)
public void onMessageEventAsync(MessageEvent event) {
    Log.d(TAG, "async: " + Thread.currentThread().getName());
}

爲了驗證這些線程模型,寫了一個簡單的例子:

@Subscribe(threadMode = ThreadMode.POSTING)
public void onMessageEventPosting(MessageEvent event) {
    Log.d(TAG, "posting: " + Thread.currentThread().getName());
}

@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEventMain(MessageEvent event) {
    Log.d(TAG, "main: " + Thread.currentThread().getName());
}

@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onMessageEventBackground(MessageEvent event) {
    Log.d(TAG, "background: " + Thread.currentThread().getName());
}

@Subscribe(threadMode = ThreadMode.ASYNC)
public void onMessageEventAsync(MessageEvent event) {
    Log.d(TAG, "async: " + Thread.currentThread().getName());
}

分別使用上面四個方法訂閱同一事件,打印他們運行所在的線程。首先咱們在UI線程中發佈一條MessageEvent的消息,看下日誌打印結果是什麼。

findViewById(R.id.post_events).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Log.d(TAG, "postEvent: " + Thread.currentThread().getName());
        EventBus.getDefault().post(new MessageEvent());
    }
});

打印結果以下:

8614-8614/com.example.eventbus.demo D/event_bus: postEvent: main
8614-8664/com.example.eventbus.demo D/event_bus: ASYNC: pool-1-thread-1
8614-8614/com.example.eventbus.demo D/event_bus: MAIN: main
8614-8614/com.example.eventbus.demo D/event_bus: POSTING: main
8614-8665/com.example.eventbus.demo D/event_bus: BACKGROUND: pool-1-thread-2

從日誌打印結果能夠看出,若是在UI線程中發佈事件,則線程模型爲POSTING的事件處理函數也執行在UI線程,與發佈事件的線程一致。線程模型爲ASYNC的事件處理函數執行在名字叫作pool-1-thread-1的新的線程中。而MAIN的事件處理函數執行在UI線程,BACKGROUND的時間處理函數執行在名字叫作pool-1-thread-2的新的線程中。

咱們再看看在子線程中發佈一條MessageEvent的消息時,會有什麼樣的結果。

findViewById(R.id.post_events).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "postEvent: " + Thread.currentThread().getName());
                EventBus.getDefault().post(new MessageEvent());
            }
        }).start();
    }
});

打印結果以下:

8754-8807/com.example.eventbus.demo D/event_bus: postEvent: Thread-2
8754-8807/com.example.eventbus.demo D/event_bus: BACKGROUND: Thread-2
8754-8807/com.example.eventbus.demo D/event_bus: POSTING: Thread-2
8754-8808/com.example.eventbus.demo D/event_bus: ASYNC: pool-1-thread-1
8754-8754/com.example.eventbus.demo D/event_bus: MAIN: main

從日誌打印結果能夠看出,若是在子線程中發佈事件,則線程模型爲POSTING的事件處理函數也執行在子線程,與發佈事件的線程一致(都是Thread-2)。BACKGROUND事件模型也與發佈事件在同一線程執行。ASYNC則在一個名叫pool-1-thread-1的新線程中執行。MAIN仍是在UI線程中執行。

3.配置

EventBus提供一個EventBusBuilder配置類。例如:如何構建沒有log信息輸出的EventBus,例如沒有訂閱者沒有註冊。

EventBus eventBus = EventBus.builder()
        .logNoSubscriberMessages(false)
        .sendNoSubscriberEvent(false)
        .build();

另外一個例子,當事件處理函數執行拋出異常

EventBus eventBus = EventBus.builder().throwSubscriberException(true).installDefaultEventBus();

注意:默認狀況,EventBus會捕獲訂閱函數拋出的異常,併發送SubscriberExceptionEvent事件,該事件不是必須處理的。

配置默認的EventBus實例對象

咱們能夠在代碼的任何地方經過EventBus.getDefault()獲取共享的EventBus實例對象。EventBusBuilder也可使用installDefaultEventBus()函數配置默認的EventBus實例對象。

EventBus.builder().throwSubscriberException(BuildConfig.DEBUG).installDefaultEventBus();

3.黏性事件 Sticky Events

除了上面講的普通事件外,EventBus還支持發送黏性事件。何爲黏性事件呢?簡單講,就是在發送事件以後再訂閱該事件也能收到該事件,跟黏性廣播相似。具體用法以下:

黏性事件例子

寫一個簡單的黏性事件例子說明:

int index = 0;
findViewById(R.id.post_events).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // 發佈黏性事件
        EventBus.getDefault().postSticky(new MessageEvent("event: " + index++));
    }
});

findViewById(R.id.register).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // 註冊
        EventBus.getDefault().register(MainActivity.this);
    }
});

@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void onMessageEventMain(MessageEvent event) {
    Log.d(TAG, "MAIN: " + event.message);
}

代碼很簡單,有兩個點擊按鈕,一個用來發布黏性事件(沒發送一次,index+1),一個用來註冊。在沒有點擊註冊按鈕以前發送黏性事件,而後點擊註冊按鈕,會看到打印的log日誌以下:

11260-11260/com.example.eventbus.demo D/event_bus: MAIN: event: 0

這就是粘性事件,可以收到註冊以前發送的消息。可是它只能收到最新的一次消息,好比說在未註冊以前已經發送了多條黏性消息了,而後再註冊只能收到最近的一條消息。這個咱們能夠驗證一下,咱們連續點擊5次發送黏性事件按鈕(index自增到4),而後再點擊註冊按鈕,打印結果以下:

11166-11166/com.example.eventbus.demo D/event_bus: MAIN: event: 4

由打印結果能夠看出,發送了5次黏性事件,但只收到最近的一條黏性事件。

獲取和刪除黏性事件

最後一個黏性事件會在註冊時自動傳遞給匹配的訂閱者,但有時須要手動檢查黏性事件更方便,另外還有可能須要刪除黏性事件,不須要在註冊時傳遞給匹配的訂閱者。

MessageEvent event = EventBus.getDefault().getStickyEvent(MessageEvent.class);
if (event != null) {
    EventBus.getDefault().removeStickyEvent(event);
}

removeStickyEvent有重載函數,當傳入的是Class時,返回保存的黏性事件。

MessageEvent event = EventBus.getDefault().removeStickyEvent(MessageEvent.class);
if (event !=  null) {
    // Now do something with it
}

4.訂閱索引 Subscriber Index

訂閱索引在EventBus3.0是一個新的特性功能。可選的,能夠加快訂閱的註冊速度。

訂閱索引經過APT技術在編譯器生成的,在Android中,推薦使用。

訂閱索引前提條件:被@Subscriber註解標記且是public修飾。因爲APT技術自身的緣由,匿名中@Subscriber註解不能被識別。

當EventBus不可以使用索引,會自動回退使用反射技術實現,依然可以工做,僅僅影響性能。

怎麼生成索引呢?

若是你不是使用Android Gradle Plugin 版本是 2.2.0 或者更高,使用android-apt配置。

爲了啓動索引生成,須要使用annotationProcessor屬性將EventBus註解處理器添加到構建中,此外還須要設置eventBusIndex參數,以指定要生成的索引的徹底限定類。

android {
    defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [ eventBusIndex : 'com.example.myapp.MyEventBusIndex' ]
            }
        }
    }
}
 
dependencies {
    implementation 'org.greenrobot:eventbus:3.1.1'
    annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.1.1'
}
使用kapt

若是你在kotlin代碼中使用EventBus,須要kapt替代annotationProcessor

apply plugin: 'kotlin-kapt' // ensure kapt plugin is applied
 
dependencies {
    implementation 'org.greenrobot:eventbus:3.1.1'
    kapt 'org.greenrobot:eventbus-annotation-processor:3.1.1'
}
 
kapt {
    arguments {
        arg('eventBusIndex', 'com.example.myapp.MyEventBusIndex')
    }
}
使用android-apt

若是上面的內容不能工做,您可使用Android-APT Gradle插件將EventBus註釋處理器添加到您的構建中

buildscript {
    dependencies {
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}
 
apply plugin: 'com.neenbedankt.android-apt'
 
dependencies {
    compile 'org.greenrobot:eventbus:3.1.1'
    apt 'org.greenrobot:eventbus-annotation-processor:3.1.1'
}
 
apt {
    arguments {
        eventBusIndex "com.example.myapp.MyEventBusIndex"
    }
}
怎麼使用索引呢?

成功構建項目後,將生成使用eventBusIndex指定的類。

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

或者若是想使用默認的EventBus實例對象

EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
// Now the default instance uses the given index. Use it like this:
EventBus eventBus = EventBus.getDefault();
Libraries中使用索引
EventBus eventBus = EventBus.builder()
    .addIndex(new MyEventBusAppIndex())
    .addIndex(new MyEventBusLibIndex()).build();

5.混淆 ProGuard

-keepattributes *Annotation*
-keepclassmembers class * {
    @org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
 
# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
    <init>(java.lang.Throwable);
}

6.AsyncExecutor

AsyncExecutor與線程池相似,但處理失敗(異常)。失敗將引起異常,AsyncExecutor將這些異常包裝在一個事件中,該事件將自動發佈.

一般,能夠調用AsyncExecutor.create()來建立實例,並將其保存在應用程序範圍內。而後,實現RunnableEx接口並將其傳遞給AsyncExecutor的Execute方法。

與Runnable不一樣,RunnableEx可能引起異常。若是RunnableEx實現拋出異常,它將被捕獲幷包裝到ThrowableFailureEvent中,該事件將被髮布。

AsyncExecutor.create().execute(
    new AsyncExecutor.RunnableEx() {
        @Override
        public void run() throws LoginException {
            // No need to catch any Exception (here: LoginException)
            remote.login();
            EventBus.getDefault().postSticky(new LoggedInEvent());
        }
    }
);

接收事件

@Subscribe(threadMode = ThreadMode.MAIN)
public void handleLoginEvent(LoggedInEvent event) {
    // do something
}
 
@Subscribe(threadMode = ThreadMode.MAIN)
public void handleFailureEvent(ThrowableFailureEvent event) {
    // do something
}
AsyncExecutor Builder

若是要自定義AsyncExecutor實例,請調用靜態方法AsyncExecutor.builder()。它將返回一個生成器,用於自定義EventBus實例、線程池和失敗事件的類。

另外一個自定義選項是執行範圍,它給出故障事件上下文信息。例如,故障事件可能僅與特定活動實例或類別相關。

若是自定義故障事件類實現HasExecutionScope接口,AsyncExecutor將自動設置執行範圍。與此相似,您的用戶能夠查詢其執行範圍的失敗事件,並根據它作出反應。

相關文章
相關標籤/搜索