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来实现通信,
可以参考http://www.jianshu.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之后,其性能提高了许多倍。

EventBus,轻松实现跨组件跨线程通信_第1张图片
性能对比

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

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成功,如下图所示。

EventBus,轻松实现跨组件跨线程通信_第2张图片
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 getStickyEvent(Class 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 ;
}

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

你可能感兴趣的:(EventBus,轻松实现跨组件跨线程通信)