Android事件总线框架设计EventBus30源码详解与架构分析上

40次阅读

共计 10116 个字符,预计需要花费 26 分钟才能阅读完成。

发布 / 订阅事件总线。它可以让我们很轻松的实现在 Android 各个组件之间传递消息,并且代码的可读性更好,耦合度更低。

  • 简化组件之间的通讯
  • 事件的发送着与接受者完全解耦
  • 完美解决 UI(如:Activities、Fragments)和后台线程之间切换
  • 避免复杂且容易出错的依赖关系和生命周期问题

1. 开始 EventBus 之旅

在使用 EventBus 之前,需要添加依赖:模块的 build.gradle 文件中

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

1、步骤一:定义事件
事件没有任何特定的要求,一个普通的 java 对象

public class MessageEvent {
    public final String message;

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

2、步骤二:准备订阅者
订阅者实现事件处理方法(也称为 订阅方法),这些方法在事件发布时调用。它们要用 @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/onDestroy

// Activity 中

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

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

3、发布事件
在代码的任何地方都可以发布事件。所有注册订阅者将接收匹配事件类型

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

2. 线程模型(ThreadMode)

EventBus 提供了线程模型,可以帮助我们处理线程:订阅方法可以在不同于发布事件的线程中处理。常见用例涉及到 UI 更新,在 Android 中,UI 更新必须在 UI(main)线程执行。另一方面,网络或者任何耗时的任务不能运行在主线程中。EventBus 帮助我们处理这个任务并与 UI 线程同步(无需深入研究线程切换,或使用 AsyncTask 等)。

在 EventBus 中,可以使用四个线程模型之一定义调用事件处理函数的线程。

ThreadMode: POSTING(默认)

订阅事件函数指定了线程模型为 POSTING:该事件在哪个线程发布出来的,事件处理函数就会在这个线程中运行,也就是说发布事件和事件处理函数在同一个线程。在线程模型为 POSTING 的事件处理函数中尽量避免执行耗时操作,因为它会阻塞事件的传递,甚至有可能会引起 ANR。

// 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();

4. 黏性事件 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 将自动设置执行范围。与此类似,您的用户可以查询其执行范围的失败事件,并根据它做出反应。

正文完
 0