Android开源库——EventBus高级用法

前言

上一篇写了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的源码,了解其实现原理。

感谢阅读!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • title: android 初识EventBusdate: 2016-04-17tags: eventbus E...
    梵依然阅读 4,393评论 7 107
  • EventBus 简介 EventBus 直译过来就是事件总线,熟悉计算机原理的人一定很熟悉总线的概念,所有设备都...
    DanieX阅读 1,035评论 0 1
  • 项目到了一定阶段会出现一种甜蜜的负担:业务的不断发展与人员的流动性越来越大,代码维护与测试回归流程越来越繁琐。这个...
    fdacc6a1e764阅读 3,158评论 0 6
  • EventBus源码分析(一) EventBus官方介绍为一个为Android系统优化的事件订阅总线,它不仅可以很...
    蕉下孤客阅读 3,972评论 4 42
  • 那年冬天,天气很冷,教室里面只有寥寥可数的几个人,因为连续的补课和低温,很多同学对于寒冷都无力抵抗,我也是一样。 ...
    草瓣阅读 495评论 1 1