EventBus,轻松实现跨组件跨线程通信

安卓基础开发库,让开发简单点。
DevRing & Demo地址https://github.com/LJYcoder/DevRing

学习/参考地址:
http://blog.csdn.net/itachi85/article/details/52205464
http://blog.csdn.net/Tencent_Bugly/article/details/51354693
http://blog.csdn.net/qq_28746251/article/details/51476389

前言

EventBus是一个基于发布/订阅的事件总线(数据通信框架),它简化了组件之间、线程之间的数据通信操作,并且耦合度低、开销小。
3.0版本后,使用注解来声明订阅者函数及其相关属性,使得操作流程更加便捷,还提供index帮助提升其性能。

(如果你不喜欢用EventBus,而想用RxJava自己封装一个RxBus来实现通信,
可以参考//www.greatytc.com/p/3a3462535b4d


介绍

下面从 配置、基本使用、粘性事件、使用index优化、简单封装、混淆 这几个部分来介绍EventBus。

1. 配置

在Module下的build.gradle中添加

//EventBus
compile 'org.greenrobot:eventbus:3.0.0'
annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.0.1'//用于eventbus开启Index加速

2. 基本使用

使用步骤分为定义事件、订阅事件、发送事件、处理事件、取消订阅五步

2.1 定义事件

先定义一个你打算发送的事件类,里面添加你要发送的数据变量。
变量的类型除了基本数据类型,也可以是自定义的实体类。

public class MovieEvent {
    private int count;

    public MovieEvent(int count) {
        this.count = count;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }
}

2.2 订阅事件

在你要接收事件的地方订阅事件:

public class MovieActivity{
    ....

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //确保之前未订阅过,再调用订阅语句,以免报错
        if(!EventBus.getDefault().isRegistered(subscriber)){
            EventBus.getDefault().register(subscriber);
        }
    }

    ....
}

2.3 发送事件

在你要发送事件的地方,调用

EventBus.getDefault().post(new MovieEvent(1));

这里需要注意,发送的事件是属于引用传递,也就是说,发送事件后,你在事件处理函数中对接收到的事件进行了修改,那么发送源头的事件也会跟着改变。所以如果不想影响到发送源头的数据,建议new对象后再发送。

另外,EventBus提供了一个方法用于发送粘性事件,粘性事件相关内容会在下面另外介绍。

EventBus.getDefault().postSticky(new MovieEvent(1));

2.4 处理事件

在接收事件的地方添加处理事件的方法,请与订阅事件方法处于同一个类下,以保证能成功订阅事件

public class MovieActivity{
    ....

    //声明处理事件的方法
    @Subscribe
    public void handlerEvent(MovieEvent event) {
        //处理事件
        int count = event.getCount();
        ...
    }

     ....
}

你可以自定义处理事件方法的名称,但必须加上@Subscribe注解来声明该方法为事件接收处理方法。
方法的参数用于指定你要接收事件类型。比如参数为MovieEvent event,则表示接收类型为MovieEvent的事件,其他类型的事件将不会接收到。

另外@Subscribe里面可以对 处理事件时所在的线程、事件接收的优先级、是否为粘性事件 进行设置

  • 处理事件时所在的线程
@Subscribe(threadMode = 线程类型)

线程类型有以下四种选择:

  1. ThreadMode.POSTING:默认的类型。表示处理事件时所在的线程将会与事件发送所在的线程一致,也就是两者的执行都处于同一个线程。
  2. ThreadMode.MAIN:表示处理事件时所在的线程将切换为UI主线程。如果发送事件所在的线程本来就是UI主线程,则不会切换。
  3. ThreadMode.BACKGROUND:表示处理事件时所在的线程将切换为后台线程。如果发送事件所在的线程本来就是后台线程,则不会切换。
  4. ThreadMode.ASYNC:表示处理事件时所在的线程将会切换为一个新建的独立子线程。
  • 优先级
@Subscribe(priority = 100)

priority用于指定接收事件的优先级,默认值为0。
优先级高的事件处理函数将先收到发送的事件,你可以在优先级高的事件处理函数中拦截事件,不让它继续往下传递,拦截方法如下

EventBus.getDefault().cancelEventDelivery(event);
  • 粘性事件
@Subscribe(sticky = true)

sticky用来声明是否接收订阅前就已发出的粘性事件,默认值为false,具体介绍请看后面的“粘性事件”

2.5 取消订阅

在退出或者不需要接收事件时,取消订阅

public class MovieActivity{
    ....

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //取消订阅
        if (EventBus.getDefault().isRegistered(subscriber)) {
            EventBus.getDefault().unregister(subscriber);
        }
    }

    ....
}

3. 粘性事件

一般我们的使用流程为:订阅事件---》发送事件---》接收处理事件。
那如果现在希望发送事件---》订阅事件---》接收处理事件,这可以实现吗?答案是可以的。
EventBus提供的粘性事件便可实现这一场景。

3.1 使用步骤

使用步骤和普通事件的基本一样,但有两点需注意:

  • 注册事件接收的操作(EventBus.getDefault().register(this);)需在控件初始化后再执行,否则会接收不到,这里建议放在onStart的生命周期中执行。
  • 事件的发送调用的是postSticky(event),事件处理函数需声明@Subscriber(sticky = true)。
//事件发送方

//发送粘性事件
EventBus.getDefault().postSticky(new MovieEvent(1));
//事件接收处理方

//发送完粘性事件后再进行订阅事件
EventBus.getDefault().register(this);//注册事件接收

//接收处理订阅前发出的粘性事件
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void handleEvent(MovieEvent event) {
    //处理事件
    int count = event.getCount();
}

3.2 使用场景

这里举一个使用场景:Activity间跳转传值。参考自http://www.cnblogs.com/ldq2016/p/5387444.html
我们平时都是使用Intent携带数据来实现,如果要传递的是自定义的实体类,还需要进行序列化操作。下面大致演示如何使用EventBus的粘性事件来实现这一场景。

ActivityA跳转到ActivityB,并将Movie对象传递过去。

//ActivityA中的代码

Movie movie = new Movie();
//发送粘性事件,传送movie
EventBus.getDefault().postSticky(movie);
//跳转到ActivityB
startActivity(new Intent(this, ActivityB.class));
//ActivityB中的代码

//订阅事件
EventBus.getDefault().register(this);

//获取订阅前ActivityA发送的粘性事件
@Subscribe(sticky = true)
public void getDataFromOtherActivity(Movie movie) {
    //得到ActivityA的Movie对象,进行具体操作。
}

如果大家还有其他使用场景,欢迎留言分享~

3.3 补充

1)EventBus仅保存最新发送的粘性事件。
2)手动获取、移除粘性事件

//手动获取粘性事件
MovieEvent movieEvent = EventBus.getDefault().getStickyEvent(MovieEvent.class);

if(movieEvent != null) {
    //移除粘性事件
    EventBus.getDefault().removeStickyEvent(movieEvent);
}

3)
发送粘性事件后,对于在发送前就已经订阅事件的订阅者,它们都会收到类型相符的粘性事件,不管它们的事件处理方法是否声明为sticky=true。
而对于在发送后才进行订阅事件的订阅者,其事件处理方法必须声明为sticky=true才能收到类型相符的粘性事件。

4. 使用index优化

在3.0版本之前,为了保证性能,EventBus在遍历寻找订阅者的回调方法时使用反射而不是注解,而在3.0版本由于采用注解,从下图可以看到,其性能比2.4版本要下降很多。
为了在使用注解的情况下保证高性能,EventBus提供了通过开启Index来提升性能的方法,从下图可以看到,开启了Index之后,其性能提高了许多倍。

性能对比

下面介绍如何生成和开启索引。

4.1 生成索引

网上很多关于Index的文章中,是通过添加以下配置来生成的

//project下的build.gradle文件

buildscript {
    dependencies {
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}

//module下的build.gradle文件

apply plugin: 'com.neenbedankt.android-apt'
apt {
    arguments {
        eventBusIndex "com.dev.base.MyEventBusIndex"
    }
}
dependencies {
    compile 'org.greenrobot:eventbus:3.0.0'
    apt 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}

但是,如果你的项目中也使用了ButterKnife库,那么添加上面的配置后会导致ButterKnife无法正常工作;另外,android-apt的作者已在官网发表声明表示后续将不会继续维护android-apt,所以并不推荐这个方式实现,而是使用Android推出的官方插件annotationProcessor来替代apt。
通过annotationProcessor来设置生成index的配置会更加便捷,如下:

//module下的build.gradle文件

android{
    defaultConfig {
            //...省略其他配置

            javaCompileOptions {
                annotationProcessorOptions {
                    arguments = [ eventBusIndex : "com.dev.base.MyEventBusIndex" ]
               }
            }
    }
}


dependencies {
    compile 'org.greenrobot:eventbus:3.0.0'
    annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}

添加以上配置后,编译 运行 项目,执行了一次“发送事件”操作后,如果在\build\generated\source\apt\debug\项目包名\下生成了你指定的Index类,则表示生成index成功,如下图所示。

index生成后的位置

4.2 开启索引

生成index之后,在Appliction中调用addIndex()方法开启即可。

EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();

5. 简单封装

public class EventBusManager {

    //开启Index加速
    public static void openIndex() {
        EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
    }

    //订阅事件
    public static void register(Object subscriber) {
        if(!EventBus.getDefault().isRegistered(subscriber)){
            EventBus.getDefault().register(subscriber);
        }
    }

    //取消订阅
    public static void unregister(Object subscriber) {
        if (EventBus.getDefault().isRegistered(subscriber)) {
            EventBus.getDefault().unregister(subscriber);
        }
    }

    //终止事件继续传递
    public static void cancelDelivery(Object event) {
        EventBus.getDefault().cancelEventDelivery(event);
    }

    //获取保存起来的粘性事件
    public static <T> T getStickyEvent(Class<T> classType){
        return EventBus.getDefault().getStickyEvent(classType);
    }

    //删除保存中的粘性事件
    public static void removeStickyEvent(Object event) {
        EventBus.getDefault().removeStickyEvent(event);
    }

    //发送事件
    public static void postEvent(Object event){
        EventBus.getDefault().post(event);
    }

    //发送粘性事件
    public static void postStickyEvent(Object event) {
        EventBus.getDefault().postSticky(event);
    }

}

DevRing/Demo中已对EventBus进行了封装,在Activity和Fragment中,只需重写isUseEventBus()方法并返回true,则会自动对该页面进行订阅和解除订阅的操作,详情请查阅代码。

6. 混淆

在proguard-rules.pro文件中添加以下内容进行混淆配置

#EventBus开始
-keepattributes *Annotation*
#如果使用了EventBus index进行优化加速,就必须加上这个
-keepclassmembers class ** {
    @org.greenrobot.eventbus.Subscribe <methods>;
}

-keep enum org.greenrobot.eventbus.ThreadMode { *; }
#如果使用了Async类型的线程
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
    <init>(java.lang.Throwable);
}
#EventBus结束

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

推荐阅读更多精彩内容