美文网首页Android开发Android开发经验谈Android知识
EventBus,轻松实现跨组件跨线程通信

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

作者: JYcoder | 来源:发表于2017-10-27 15:38 被阅读1113次

    安卓基础开发库,让开发简单点。
    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之后,其性能提高了许多倍。

    性能对比

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

    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结束
    

    相关文章

      网友评论

        本文标题:EventBus,轻松实现跨组件跨线程通信

        本文链接:https://www.haomeiwen.com/subject/fetdpxtx.html