美文网首页
Android 框架解析:EventBus 3.0 的特点与如何

Android 框架解析:EventBus 3.0 的特点与如何

作者: 安安_660c | 来源:发表于2022-03-17 14:23 被阅读0次

    作者:拭心

    EventBus 3.0 简介

    首先打开官方文档:http://greenrobot.org/eventbus/,一张嚣张的大图吸引了我的目光:

    “Android 第一的事件库”,看起来很牛逼的样子啊,是不是真的这么牛呢?

    首先看看介绍:

    EventBus 是一个使用“观察者模式”的、松耦合的开源框架。它使用少量的几句代码就可以实现核心类之间的通讯,帮助我们简化代码、松依赖、加速开发。

    在 EventBus 之前,有什么事件通信的方法:

    ● startActivityForResult() 发出请求 , onActivityResult() 接收回调
    ● Activity 多层嵌套调用,多次 startActivityForResult,繁琐 = 易出错
    ● 嵌套 Fragement 调用,依赖外层 Activity 进行,还是繁琐
    ● 回调
    ● 子线程运行,生命周期不同步
    ● 线程间通信

    EventBus 提出是为了解决什么问题呢:

    ● 简化关键组件间的通讯(Activity, Fragment, 线程通信)
    ● 不管什么组件,都可以通讯
    ● 避免复杂的依赖其他类,以及生命周期问题
    ● 内存 泄漏?
    ● 效率很高,优化了性能(重点关注)
    ● 体积小
    ● 被 一亿 多 app 使用,很吓人
    ● 线程间通信,也可以设置订阅者优先级

    EventBus 3.0 关键介绍

    ⒈方便的注解
    ● 使用 @Subscribe 注解描述方法,可以在编译时获取信息,不需要在运行反射

    ⒉事件执行线程多种选择
    ● 主线程
    ● 子线程

    ⒊事件的继承
    ● 发送给订阅事件 A 的消息,也会发给订阅了 A 的子类的方法
    ● 简化事件

    ⒋需要在 Application 或者其他部分配置,直接使用 EventBus.getDefault() 进行操作即可
    ● EventBus.getDefault() 做了什么呢?

    ⒌可配置的
    ● 可以通过 Builder 配置 EventBus
    http://greenrobot.org/eventbus/documentation/

    @Subscribe 注解
    EventBus 3.0 之后使用 @Subscribe 注解来描述一个注册的方法

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    public @interface Subscribe {
        //注册方法所在线程
        ThreadMode threadMode() default ThreadMode.POSTING;    //默认与发生方法在同一线程
    
        //是否为粘性事件
        boolean sticky() default false;
    
        //注册方法接收消息的优先级,当同一线程有多个注册方法时有效
        int priority() default 0;
    }
    
    

    四种 ThreadMode

    @Subscribe(threadMode = ThreadMode.POSTING)
    public void onMessage(MessageEvent event) {
        log(event.message);
    }
    
    

    EventBus 提供了四种 ThreadMode 值:

    ⒈POSTING
    ● 订阅方法将和发送者执行在同一线程,默认的值
    ● 订阅方法最好不要执行耗时操作,因为它可能会影响发送者的线程
    ● 尤其是发送者在主线程的时候

    ⒉MAIN
    ● 订阅方法执行在主线程
    ● 用于更新 UI
    ● 也要注意不执行耗时操作

    ⒊BACKGROUND
    ● 订阅方法执行在单一的一个子线程
    ● 如果发送者不要主线程,那订阅方法就会执行在发送者线程
    ● 否则,使用一个单线程发送所有消息,所有消息串行执行
    ● 也要注意避免耗时操作,影响到在同一线程的其他订阅方法

    ⒋ASYNC
    ● 订阅方法会在一个新开的子线程(不是主线程、也不是发送者所在线程)执行(类似每次都新建一个线程)
    ● 在执行耗时操作时需要使用这个,不会影响其他线程
    ● 但是要控制数量,避免创建大量线程导致的开销
    ● EventBus 使用线程池控制

    粘性事件 Sticky Event
    EventBus 还支持 粘性事件

    ● 普通事件是说,先注册,然后发送事件才能收到
    ● 而粘性事件,在发送事件之后再订阅该事件也能收到
    ● 此外,粘性事件在发送后会被保存在内存中,每次进入都会去内存中获取最新的粘性事件数据,除非你手动解除注册

    可以看到,粘性事件实现了对一些关键信息的缓存与更新,一般用于保存那些经常变化的信息,比如定位信息、传感器信息等等。

    @Subscribe(sticky = true)
    public void readStickyMsg(MessageEvent event){
        //...
    }
    
    

    优先级
    我们可以设置一个注册方法的优先级:

    @Subscribe(priority = 1);
    public void onEvent(MessageEvent event) {
        ...
    }
    
    

    默认优先级是 0,同一 线程(ThreadMode) 中优先级高的订阅者会先于低的接收到消息。

    注意,只有在同一线程中的订阅者优先级才有作用。

    有优先级后 ,高优先级的订阅者就可以取消消息往后的传播,这也符合生活和一些场景的需求。

    这通过调用 cancelEventDelivery(event) 方法:

    @Subscribe
    public void onEvent(MessageEvent event){
        // Process the event
        ...
        // Prevent delivery to other subscribers
        EventBus.getDefault().cancelEventDelivery(event) ;
    }
    Pasted from: http://greenrobot.org/eventbus/documentation/priorities-and-event-cancellation/
    
    

    注意,只有 ThreadMode 为 POSTING 的订阅方法可以拦截消息。

    优先级设置后,界面上显示不明显,因为 EventBus 的消息发送效率很高,但是如果打断点的话就可以看到,的确是高优先级的方法先被调用。

    配置

    我们一般调用 EventBus 直接使用 EventBus.getDefault() 方法即可。如果你想要自己配置 EventBus,它也支持。

    通过 EventBus.builder() 方法我们得到 EventBusBuilder 对象,然后配置 EventBus 的各种属性,比如这样:

    EventBus eventBus = EventBus.builder()
        .logNoSubscriberMessages(false)
        .sendNoSubscriberEvent(false)
        .build();
    
    

    EventBusBuilder 中可以配置的内容如下:

    boolean logSubscriberExceptions = true;
    boolean logNoSubscriberMessages = true;
    boolean sendSubscriberExceptionEvent = true;
    boolean sendNoSubscriberEvent = true;
    boolean throwSubscriberException;
    boolean eventInheritance = true;
    boolean ignoreGeneratedIndex;
    boolean strictMethodVerification;
    ExecutorService executorService = DEFAULT_EXECUTOR_SERVICE;
    List<Class<?>> skipMethodVerificationForClasses;
    List<SubscriberInfoIndex> subscriberInfoIndexes;
    
    

    可以看到,默认 EventBus 中以下属性是设为 true 的:

    ● logSubscriberExceptions: 记录订阅异常
    ● logNoSubscriberMessages :记录没有目标订阅者的消息
    ● sendSubscriberExceptionEvent :订阅方法异常时发送 SubscriberExceptionEvent 事件
    ● sendNoSubscriberEvent :发送的消息没有订阅者时发送 NoSubscriberEvent 事件
    ● eventInheritance :事件允许继承

    AsyncExecutor

    EventBus 还提供了一个异步线程池 AsyncExecutor,使用它创建的线程,如果抛出异常,它会自动捕获,然后将异常信息包裹成一个 Event 发送出去。

    AsyncExecutor 只是一个帮我们省去处理子线程抛出异常的工具类,不是 EventBus 的核心类。

    我们可以在全局范围内调用 AsyncExector.create() 方法创建一个实例,然后调用 execute 方法执行异步任务,它的参数是 AsyncExecutor.RunnableEx:

    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());
            }
        }
    );
    Pasted from: http://greenrobot.org/eventbus/documentation/asyncexecutor/
    
    

    如果 RunnableEx 中抛出异常,AsyncExecutor 会捕获这个异常,然后包裹成 ThrowableFailureEvent 发送出去,注册了这个事件的方法将会得到调用。

    订阅这个异常的例子如下:

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void handleFailureEvent(ThrowableFailureEvent event) {
        // do something
    }
    
    

    EventBus 的使用

    下面我们演示一下 EventBus 的基本使用。

    首先 gradle 引入依赖 EventBus 以及它的注解处理器:

    根目录下的 gradle 的 dependencies 中添加 apt 依赖:

    classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    
    

    app 目录下的 gradle 中添加:

    apply plugin: 'android-apt'
    apt {
        arguments {
            eventBusIndex "top.shixinzhang.MyEventBusIndex"
        }
    }
    dependencies {
      compile 'org.greenrobot:eventbus:3.0.0'
      apt 'org.greenrobot:eventbus-annotation-processor:3.0.1'
    }
    
    

    其中 eventBusIndex 你可以随意设置包名和文件名。

    然后就可以发车了!

    创建要订阅的事件:

    public class MessageEvent implements Serializable {
        private static final long serialVersionUID = -1371779234999786464L;
        private String message;
        private String time;
    
        public MessageEvent(String message, String time) {
            this.message = message;
            this.time = time;
        }
    
        public String getMessage() {
            return message;
        }
    
        public void setMessage(String message) {
            this.message = message;
        }
    
        public String getTime() {
            return time;
        }
    
        public void setTime(String time) {
            this.time = time;
        }
    }
    
    

    然后创建一个注册 EventBus 页面:

    public class EventBusRegisterActivity extends AppCompatActivity {
    
        @BindView(R.id.tv_event_info)
        TextView mTvEventInfo;
        @BindView(R.id.tv_event_info_priority)
        TextView mTvEventInfoPriority;
        @BindView(R.id.btn_cancel_delivery)
        Button mBtnCancelDelivery;
        private boolean mCancelDelivery;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_event_bus_test);
            ButterKnife.bind(this);
        }
    
        /**
         * 订阅方法,设置线程为 POSTING,优先级为 5
         * @param event
         */
        @Subscribe(threadMode = ThreadMode.POSTING, priority = 5)
        public void readMessageFirst(MessageEvent event) {
            mTvEventInfoPriority.setText("\n" + event.getMessage() + ", " + event.getTime());
            if (mCancelDelivery) {
                EventBus.getDefault().cancelEventDelivery(event);
            }
        }
    
        @Subscribe(threadMode = ThreadMode.POSTING, priority = 1)
        public void readMessage(MessageEvent event) {
            mTvEventInfo.setText("\n" + event.getMessage() + ", " + event.getTime());
        }
    
        /**
         * 为了演示效果,我们在按钮点击事件中注册事件
         */
        @OnClick(R.id.btn_register_normal)
        public void registerNormal() {
            if (!EventBus.getDefault().isRegistered(this)) {
                EventBus.getDefault().register(this);
            }
        }
    
        @OnClick(R.id.btn_unregister_normal)
        public void unregisterNormal() {
            EventBus.getDefault().unregister(this);
        }
    
    
        @OnClick(R.id.btn_cancel_delivery)
        public void cancelDelivery() {
            mCancelDelivery = !mCancelDelivery;
            mBtnCancelDelivery.setText(mCancelDelivery ? "已拦截事件传递" : "点击拦截事件传递");
        }
    
        @OnClick(R.id.btn_jump_next)
        public void jumpToNext() {
            startActivity(new Intent(this, EventBusPosterActivity.class));
        }
    
    
        @OnClick(R.id.btn_jump_sticky)
        public void jumpToSticky() {
            startActivity(new Intent(this, EventBusStickyActivity.class));
        }
    
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
        }
    }
    
    

    这个页面的功能如图所示:

    ● 有两个优先级不同的订阅方法,有两个按钮用于注册和解除注册订阅
    ● 一个用于高优先级订阅方法拦截事件向后传递的按钮
    ● 还有一个按钮用于跳转到发送事件页面中,另一个按钮用于跳转到粘性事件订阅页面。

    先看下粘性事件订阅页面:

    public class EventBusStickyActivity extends AppCompatActivity {
    
        @BindView(R.id.tv_event_info)
        TextView mTvEventInfo;
        @BindView(R.id.btn_register_sticky_event)
        Button mBtnRegisterStickyEvent;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_event_bus_sticky);
            ButterKnife.bind(this);
        }
    
        @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
        public void readStickyMsg(MessageEvent event){
            mTvEventInfo.setText(mTvEventInfo.getText() + "\n" + event.getMessage() + ", " + event.getTime());
        }
    
        @OnClick(R.id.btn_jump_next)
        public void jumpToNext(){
            startActivity(new Intent(this, EventBusPosterActivity.class));
        }
    
        @OnClick(R.id.btn_register_sticky_event)
        public void registerSticky(){
            EventBus.getDefault().register(this);
        }
    
        @OnClick(R.id.btn_unregister_sticky_event)
        public void unregisterSticky(){
            EventBus.getDefault().removeStickyEvent(MessageEvent.class);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
        }
    }
    
    

    功能如图所示:

    ● 一个显示订阅方法接收信息的文字
    ● 一个点击后跳转到发送事件页面的按钮
    ● 两个用于注册和解除注册粘性事件的按钮

    好,接着再看一下发送事件页面:

    public class EventBusPosterActivity extends AppCompatActivity {
    
        @BindView(R.id.tv_event_info)
        TextView mTvEventInfo;
        @BindView(R.id.btn_post_normal)
        Button mBtnPostNormal;
        @BindView(R.id.btn_post_sticky_event)
        Button mBtnPostStickyEvent;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_event_bus_post);
            ButterKnife.bind(this);
        }
    
        @OnClick(R.id.btn_post_normal)
        public void postNormalEvent(){
            EventBus.getDefault().post(new MessageEvent("另外界面的主线程,普通消息", DateUtils.getCurrentTime()));
            finish();
        }
    
        @OnClick(R.id.btn_post_sticky_event)
        public void postStickyEvent(){
            EventBus.getDefault().postSticky(new MessageEvent("另外界面的主线程,粘性消息", DateUtils.getCurrentTime()));
            finish();
        }
    }
    
    

    这个页面很简单,两个发送普通事件和粘性事件的按钮。

    演示下普通事件的注册、解除注册、以及高优先级拦截事件的运行效果:

    从上面的动图可以看到:

    ● 注册事件监听后,订阅的方法就可在 EventBus 发送事件后收到调用
    ● 优先级高的会比低的先收到调用,界面上显示不明显,但是打断点就可以看到先后的调用顺序
    ● 优先级高的拦截事件后,低优先级的就不会收到新的事件
    ● 解除注册后,也不会收到新的事件

    接着看一下 粘性事件的注册、解除注册的效果:


    从上面的动图可以看到:

    ● 在发送粘性事件之后注册粘性监听,也可以得到消息
    ● 发送的粘性事件会被缓存起来,以后只要注册这个事件就会得到消息
    ● 当发送新的粘性事件后,订阅粘性事件的方法会更新到最新的值
    ● 解除粘性事件的注册后,就不会再去获取值

    可以看到,粘性事件的确适用于获取一些需要缓存的关键信息,比如城市、天气等等。

    总结

    这篇文章介绍了 EventBus 3.0 的主要特点和使用,可以发现,它的确很容易使用,目前能想到的事件通讯基本都可以满足,代码耦合也不严重。

    而 EventBus 的缺点就是,会导致业务逻辑比较分散,不直观。尤其是在运行时触发多种事件、多个订阅方法时。不过这应该是解耦的双刃剑吧。

    下一篇文章我们分析下 EventBus 的核心功能是如何实现的。

    相关文章

      网友评论

          本文标题:Android 框架解析:EventBus 3.0 的特点与如何

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