Android事件总线框架设计:EventBus3.0源码详解与架构分析(上)

发布/订阅事件总线。它可以让我们很轻松的实现在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
}

5.订阅索引 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();

6.混淆 ProGuard

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

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

你可能感兴趣的:(android,eventbus)