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

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

1460000021080059

简化组件之间的通讯

事件的发送着与接受者完全解耦

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

如果我的文章对您有帮助,不妨点个赞鼓励一下(^_^)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值