Android開源庫——EventBus高階用法

Charming本尊發表於2018-01-04

前言

上一篇寫了EventBus的基本用法,傳送門: Android開源庫——EventBus使用教程
在上一篇中只是簡單展示了EventBus的基本用法,其實還有很多好玩和強大的功能,那麼在本篇將一步一步地去探索EventBus那些好玩又高階的用法。主要有

  • Thread Mode(執行緒模式)
  • Configuration(配置)
  • Sticky Events(粘性事件)
  • Priorities and Event Cancellation(優先順序和取消事件傳遞)
  • Subscriber Index(訂閱者索引)
  • AsyncExecutor(非同步執行器)

話不多說,馬上開始

高階用法

Thread Mode

還記得上一篇中,訂閱方法的@Subscribe中包含的資訊

@Subscribe(threadMode = ThreadMode.MAIN)
public void onUpdateUIEvent(UpdateUIEvent updateUIEvent) {
    mTextView.setText("陳奕迅只有一個");
複製程式碼

通過指定threadMode來確定訂閱方法在哪個執行緒執行,ThreadMode.MAIN只是其中一種,看一下其他的

public enum ThreadMode {
    /**
     * Subscriber will be called in the same thread, which is posting the event. This is the default. Event delivery
     * implies the least overhead because it avoids thread switching completely. Thus this is the recommended mode for
     * simple tasks that are known to complete is a very short time without requiring the main thread. Event handlers
     * using this mode must return quickly to avoid blocking the posting thread, which may be the main thread.
     */
    POSTING,

    /**
     * Subscriber will be called in Android's main thread (sometimes referred to as UI thread). If the posting thread is
     * the main thread, event handler methods will be called directly. Event handlers using this mode must return
     * quickly to avoid blocking the main thread.
     */
    MAIN,

    /**
     * Subscriber will be called in a background thread. If posting thread is not the main thread, event handler methods
     * will be called directly in the posting thread. If the posting thread is the main thread, EventBus uses a single
     * background thread, that will deliver all its events sequentially. Event handlers using this mode should try to
     * return quickly to avoid blocking the background thread.
     */
    BACKGROUND,

    /**
     * Event handler methods are called in a separate thread. This is always independent from the posting thread and the
     * main thread. Posting events never wait for event handler methods using this mode. Event handler methods should
     * use this mode if their execution might take some time, e.g. for network access. Avoid triggering a large number
     * of long running asynchronous handler methods at the same time to limit the number of concurrent threads. EventBus
     * uses a thread pool to efficiently reuse threads from completed asynchronous event handler notifications.
     */
    ASYNC
}
複製程式碼

註釋一目瞭然,總結一下四種模式的應用場景

  • POSTING:事件釋出在什麼執行緒,就在什麼執行緒訂閱。
  • MAIN:無論事件在什麼執行緒釋出,都在主執行緒訂閱。
  • BACKGROUND:如果釋出的執行緒不是主執行緒,則在該執行緒訂閱,如果是主執行緒,則使用一個單獨的後臺執行緒訂閱。
  • ASYNC:用執行緒池執行緒訂閱。

比如

@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onUpdateUIEvent(UpdateUIEvent updateUIEvent) {
    mTextView.setText("陳奕迅只有一個");
}
複製程式碼

這樣我們就是在後臺中訂閱事件,但由於更新UI只能在主執行緒中,所以會出現以下異常

Could not dispatch event: class com.charmingwong.androidtest.UpdateUIEvent to subscribing class class com.charmingwong.androidtest.MainActivity
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
複製程式碼

Configuration

我們獲取EventBus例項的預設方式是

EventBus.getDefault().register(this);
複製程式碼

這種方式的獲取到的EventBus的屬性都是預設的,有時候並不能滿足我們的要求,這時候我們可以通過EventBusBuilder來個性化配置EventBus的屬性

EventBus eventBus = EventBus.builder().eventInheritance(true)
    .ignoreGeneratedIndex(false)
    .logNoSubscriberMessages(true)
    .logSubscriberExceptions(false)
    .sendNoSubscriberEvent(true)
    .sendSubscriberExceptionEvent(true)
    .throwSubscriberException(true)
    .strictMethodVerification(true)
    .build();
eventBus.register(this);
複製程式碼

使用類似於以上這種Builder模式,來達到個性化配置的效果,builder()返回的是EventBusBuilder,來看一下EventBusBuilder各項配置的含義

// 建立預設的EventBus物件,相當於EventBus.getDefault()。
EventBus installDefaultEventBus():
// 新增由EventBus“註釋前處理器生成的索引
EventBuilder addIndex(SubscriberInfoIndex index):
// 預設情況下,EventBus認為事件類有層次結構(訂戶超類將被通知)
EventBuilder eventInheritance(boolean eventInheritance):
// 定義一個執行緒池用於處理後臺執行緒和非同步執行緒分發事件
EventBuilder executorService(java.util.concurrent.ExecutorService executorService):
// 設定忽略訂閱索引,即使事件已被設定索引,預設為false
EventBuilder ignoreGeneratedIndex(boolean ignoreGeneratedIndex):
// 列印沒有訂閱訊息,預設為true
EventBuilder logNoSubscriberMessages(boolean logNoSubscriberMessages):
// 列印訂閱異常,預設true
EventBuilder logSubscriberExceptions(boolean logSubscriberExceptions):
// 設定傳送的的事件在沒有訂閱者的情況時,EventBus是否保持靜默,預設true
EventBuilder sendNoSubscriberEvent(boolean sendNoSubscriberEvent):
// 傳送分發事件的異常,預設true
EventBuilder sendSubscriberExceptionEvent(boolean sendSubscriberExceptionEvent):
// 在3.0以前,接收處理事件的方法名以onEvent開頭,方法名稱驗證避免不是以此開頭,啟用嚴格的方法驗證(預設:false)
EventBuilder strictMethodVerification(java.lang.Class<?> clazz)
// 如果onEvent***方法出現異常,是否將此異常分發給訂閱者(預設:false)
EventBuilder throwSubscriberException(boolean throwSubscriberException) 

複製程式碼

通過這種方式,我們就可以靈活地使用EventBus。

Sticky Events

一般的事件釋出之後,後來註冊的訂閱者無法再收到這些事件,而sticky事件釋出之後,EventBus會將它儲存起來,直到有新的同事件型別的sticky事件釋出,才將舊的覆蓋,因此後續的訂閱者只要使用sticky模式,依然可以獲得這個sticky事件。使用方式如下:

在SecondActivity中訂閱sticky事件並註冊

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    // 註冊該頁面為訂閱者
    EventBus.getDefault().register(this);
}

@Subscribe(sticky = true)
public void onMyStickyEvent(MyStickyEvent myStickyEvent) {
    mTextView.setText("有人問你他是不是陳奕迅");
}

複製程式碼

在MainActivity中釋出事件

@Override
public void onClick(View v) {
    // 釋出黏性事件
    EventBus.getDefault().postSticky(new MyStickyEvent());
    Intent intent = new Intent(MainActivity.this, SecondActivity.class);
    startActivity(intent);
}
複製程式碼

看一下執行效果:

執行效果

MainActivity啟動在前,釋出sticky事件,SecondActivity再啟動,註冊成為訂閱者之後依然能夠收到sticky事件,成功說明sticky的作用,也驗證了以上說法。釋出sticky事件之後,也可以將它移除

MessageEvent stickyEvent = EventBus.getDefault().removeStickyEvent(MessageEvent.class);
// Better check that an event was actually posted before
if(stickyEvent != null) {
    // Now do something with it
}
複製程式碼

Priorities and Event Cancellation

這裡用一個例子驗證優先順序關係

// MainActivity
@Subscribe(priority = 2)
public void onUpdateUIEvent(UpdateUIEvent updateUIEvent) {
    Log.d(TAG, "onUpdateUIEvent: priority = 2");
}

// SecondActivity
@Subscribe(priority = 1)
public void onUpdateUIEvent(UpdateUIEvent updateUIEvent) {
    Log.d(TAG, "onUpdateUIEvent: priority = 1");
}
複製程式碼

兩個Activity中都訂閱相同的事件型別,但優先順序不同,在SecondActivity釋出事件

// 釋出事件
EventBus.getDefault().post(new UpdateUIEvent());
複製程式碼

看執行效果,日誌:

D/MainActivity: onUpdateUIEvent: priority = 2
D/SecondActivity: onUpdateUIEvent: priority = 1
複製程式碼

再將priority調換一下

// MainActivity
@Subscribe(priority = 1)
public void onUpdateUIEvent(UpdateUIEvent updateUIEvent) {
    Log.d(TAG, "onUpdateUIEvent: priority = 1");
}

// SecondActivity
@Subscribe(priority = 2)
public void onUpdateUIEvent(UpdateUIEvent updateUIEvent) {
    Log.d(TAG, "onUpdateUIEvent: priority = 2");
}
複製程式碼

執行,看日誌:

D/SecondActivity: onUpdateUIEvent: priority = 2
D/MainActivity: onUpdateUIEvent: priority = 1
複製程式碼

可見priority決定收到事件的先後順序,priority越大,越先收到事件。先收到的訂閱者還可以攔截該事件,並取消繼續傳遞,後續優先順序低訂閱者就不會再收到該事件。我們可以這樣設定取消事件繼續傳遞:

@Subscribe(priority = 2)
public void onUpdateUIEvent(UpdateUIEvent updateUIEvent) {
    Log.d(TAG, "onUpdateUIEvent: priority = 2");
    EventBus.getDefault().cancelEventDelivery(updateUIEvent);
}
複製程式碼

執行,看日誌:

D/SecondActivity: onUpdateUIEvent: priority = 2
複製程式碼

果然,priority=1的訂閱者就沒有收到該事件,這就是EventBus的取消事件繼續傳遞功能。

Subscriber Index

看完官方文件,我們知道Subscriber Index是EventBus 3上的新技術,所以這裡也建議還沒學習過EventBus的可以跳過2.X之前的版本直接學習最新版本。
關於EventBus的Subscriber Index技術的特點,翻譯一下官方解釋:

It is an optional optimization to speed up initial subscriber registration.

Subscriber Index是一個可選的優化技術,用來加速初始化訂閱者註冊。

The subscriber index can be created during build time using the EventBus annotation processor. While it is not required to use an index, it is recommended on Android for best performance.

Subscriber Index在編譯時使用EventBus註解處理器建立,雖然沒有規定必須使用它,但是官方推薦使用這種方式,因為它在Android上有著最佳的效能。

使用方式:在gradle做如下配置

android {
    defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [ eventBusIndex : 'com.example.myapp.MyEventBusIndex' ]
            }
        }
    }
}
 
dependencies {
    compile 'org.greenrobot:eventbus:3.1.1'
    annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.1.1'
}
複製程式碼

如果以上方法不生效,還可以使用android-apt Gradle plugin

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"
    }
}
複製程式碼

配置完了之後,我們就可以在程式碼中新增索引了

EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
// Now the default instance uses the given index. Use it like this:
EventBus eventBus = EventBus.getDefault();
複製程式碼

除了application,還可以在library中使用索引

EventBus eventBus = EventBus.builder()
    .addIndex(new MyEventBusAppIndex())
    .addIndex(new MyEventBusLibIndex()).build();
複製程式碼

好了,這就是索引的使用方式。

AsyncExecutor

先看個例子

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

private void prepare() throws Exception {
    throw new Exception("prepare failed");
}

@Subscribe(threadMode = ThreadMode.MAIN)
public void onUpdateUIEvent(UpdateUIEvent updateUIEvent) {
    Log.d(TAG, "onUpdateUIEvent: ");
}

@Subscribe(threadMode = ThreadMode.MAIN)
public void handleFailureEvent(ThrowableFailureEvent event) {
    Log.d(TAG, "handleFailureEvent: " + event.getThrowable().getMessage());
複製程式碼

通過這樣的方式,如果相關程式碼出現了異常,則會將異常封裝成ThrowableFailureEvent,自動釋出出去,只要訂閱者定義了接收ThrowableFailureEvent的方法,就可以拿到異常資訊,後續的UpdateUIEvent也不會再發布,如果沒有出現異常,則正常釋出事件。 執行,看日誌:

D/SecondActivity: handleFailureEvent: prepare failed
複製程式碼

果然,只有handleFailureEvent(ThrowableFailureEvent event)收到了異常事件。這種寫法的好處是能把異常資訊交給訂閱者,讓訂閱者根據情況處理。

總結

好了,以上就是EventBus相對高階一點的用法,除了以上提到的,還有一些別的特性,具體請看官方文件,如果有不明白的地方,也一定要看官方文件,這個非常重要,附上傳送門:

戳我,看EventBus官方文件!

下一篇著重於分析EventBus的原始碼,瞭解其實現原理。

感謝閱讀!

相關文章