EventBus

作者: 会写代码的小猿猴 | 来源:发表于2021-11-24 15:42 被阅读0次

    最近做项目时有这样一个需求,在我点击拍照的时候跳转到一个自定义的拍照活动,用户拍完照片以后关闭活动页面并携带照片的Uri返回上一个活动,简单说一下实现,拍照用的是Android新组件cameraX,界面跳转可以采用navigation、intent,我这里采用的是Arouter,携带参数返回也很简单,前三种跳转方式都可以携带参数返回,但我无意间看到了一个最近很流行的框架EventBus,这个框架也可以在活动甚至线程之间进行参数的传递,那它相较于前三者的优势在哪里呢,且往下看。

    什么是EventBus

    首先我们先了解一下什么是EventBus,官方给出的解释非常简单:
    EventBus是一个使用发布者/订阅者模式 并且低耦合的Android开源库, EventBus只需几行代码即可实现中央通信解耦类:简化代码,删除依赖关系,加快应用程序开发速度。
    可以说非常惜字如金,我们通过官方文档只知道它是一个发布者/订阅者模式,而且可以实现通信的低耦合,那它具体是什么?有什么用?
    我是这样理解的:EventBus是一个Android端优化的消息总线,可以实现应用程序内各组件间、组件与后台线程间的通信。它的作用就是在应用程序内各组件间、组件与后台线程间传递数据。

    EventBus的优势

    1.简化组件通信
    2.解耦事件发送端和接收端
    3.在Activity,Fragment和后天线程之间运行良好
    4.避免复杂且易出错的依赖问题和生命周期问题
    5.运行速度快。为了高性能专门做过优化
    6.jar包小(<50K)
    7.在实践中已通过100,000,000次安装的应用程序证明
    8.具有指定分发线程和用户优先级等高级特性
    这里着重说明几个优势:
    第二个解耦事件发送端和接收端,接收端首先注册EventBus,并且订阅事件,发送端根据订阅给接收端发送事件,接收端和发送端通过EventBus来通信,降低了二者之间的依赖性。
    第四个避免复杂且易出错的依赖问题和生命周期问题,EventBus是接收者注册之后才能收到事件,它必须在总线注册,也必须在不需要的时候注销,在通常使用中我们根据组件的生命周期来注册和注销,比如在Activity的Oncreate()方法里面注册,在ondestory()方法里面注销。

    EventBus的特性

    简单但强大:EventBus是一个微小的库,具有超级容易学习的API。然而,您的软件架构可能因组件解耦而受益:订阅者在使用事件时不用关心这个事件是谁发送的。

    大量验证:EventBus是最常用的Android库之一:成千上万的应用程序使用EventBus,其中不乏非常受欢迎的应用程序。我们认为差不多有十亿应用程序使用EventBus。

    高性能:Android系统上,性能尤为重要。 EventBus被针对性的做了大量的分析和优化;EventBus可能是这类开源库中速度最快的 解决方案。

    基于API的便捷注解(不牺牲性能):只需将@Subscribe注解添加订阅方法即可。由于构建的时候花费了时间来建立注解的索引,所以EventBus不需要在应用程序的运行时间执行注释反射,注解反射的方式在Android上相当慢。

    Android主线程发送:当与UI交互时,EventBus可以在主线程中传递事件,而不用去关心事件是从如何发布的。

    后台线程发送:如果您的订阅者长时间运行任务,EventBus也可以使用后台线程来避免UI阻塞。

    事件和订阅者继承:在EventBus中,面向对象的范例适用于事件和订阅者类。让我们假设事件类A是B的父类。类型B的发布事件也将被发布到对A感兴趣的订阅者。类似地,考虑订阅类的继承。

    零配置:您可以从代码中的任何地方立即使用默认的EventBus实例。

    可配置:要根据需要调整EventBus,可以使用构建器模式调整其行为。

    EventBus的使用小Demo

    我们上面说了那么多可能还是很模糊,开发就是在实践中检验真理的过程,所以我们来给它简单地用起来,我把EventBus的使用分为以下几步:

    • 注入依赖
    • 注册订阅者
    • 定义接收方法
    • 注销订阅者
    • 发布订阅事件
      比如我需要实现这样的效果:


      1637739152730.gif
    注入依赖
     implementation 'org.greenrobot:eventbus:3.0.0'
    
    注册订阅者

    创建第一个activity,并在它的onCreate()方法里面注册订阅者

     EventBus.getDefault().register(this)
    
    定义接收方法
     @Subscribe(threadMode = ThreadMode.MAIN)
        fun getEventBusMsg(name: String) {
            hello.text = "你好!$name"
        }
    
    注销订阅者

    在FirstActivity的onDestroy()方法注销EventBus

     EventBus.getDefault().unregister(this)
    
    发布订阅事件

    在第二个activity里面发布订阅事件

      EventBus.getDefault().post(edit_name.text.toString())
    

    下面是所有代码:
    FirstActivity:

    class FirstActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            EventBus.getDefault().register(this)
            sure.setOnClickListener {
                val inetntToSecond = Intent(this, SecondActivity::class.java)
                startActivity(inetntToSecond)
            }
        }
    
        override fun onDestroy() {
            super.onDestroy()
            EventBus.getDefault().unregister(this)
        }
    
        @Subscribe(threadMode = ThreadMode.MAIN)
        fun getEventBusMsg(name: String) {
            hello.text = "你好!$name"
        }
    }
    

    activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".FirstActivity">
    
        <TextView
            android:id="@+id/hello"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="你好!Android"
            android:textSize="25sp"
            android:textStyle="bold"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
        <Button
            android:id="@+id/sure"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="确定"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/hello" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    SecondActivity:

    class SecondActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_second)
            doit.setOnClickListener {
                if (edit_name.equals("")) {
                    return@setOnClickListener
                }
                EventBus.getDefault().post(edit_name.text.toString())
                finish()
            }
        }
    }
    

    activity_second.xml

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".SecondActivity">
    
        <EditText
            android:id="@+id/edit_name"
            android:layout_width="200dp"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
        <Button
            android:text="确定"
            android:id="@+id/doit"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/edit_name" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    EventBus的多种线程模式

    EventBus在实现接收方法时有多种线程模式,比如我们上面的接收方法

     @Subscribe(threadMode = ThreadMode.MAIN)
        fun getEventBusMsg(name: String) {
            hello.text = "你好!$name"
        }
    

    -我们可以看到在注解后面threadMode= ThreadMode.MAIN,下面我们来看看它的所有线程模式

    • ThreadMode.POSTING
      订阅者方法将在发布事件所在的线程中被调用。这是 默认的线程模式。事件的传递是同步的,一旦发布事件,所有该模式的订阅者方法都将被调用。这种线程模式意味着最少的性能开销,因为它避免了线程的切换。因此,对于不要求是主线程并且耗时很短的简单任务推荐使用该模式。使用该模式的订阅者方法应该快速返回,以避免阻塞发布事件的线程,这可能是主线程。例如:
    // Called in the same thread (default)
    // ThreadMode is optional here
    @Subscribe(threadMode = ThreadMode.POSTING)
    public void onMessage(MessageEvent event) {
        log(event.message);
    }
    
    • ThreadMode: MAIN
      订阅者将在Android的主线程(有时称为UI线程)中调用。 如果发布线程是主线程,则将直接调用事件处理方法(与ThreadMode.POSTING描述的同步)。 使用此模式的事件处理程序必须快速返回,以避免阻塞主线程。
      例如:
    // Called in Android UI's main thread
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessage(MessageEvent event) {
    textField.setText(event.message);
    }
    

    ThreadMode: ASYNC
    事件处理方法在单独的线程中调用。 这个线程总是独立于发布线程和主线程。 这种模式下,发布事件不会等待事件处理方法的执行结果。 如果事件处理方法的执行需要一些时间,则应该使用此模式,如网络访问。 避免同时触发大量长时间运行的异步处理方法以限制并发线程的数量。 EventBus使用线程池来有效地重用已完成任务的线程。
    例如:

    // Called in a separate thread
    @Subscribe(threadMode = ThreadMode.ASYNC)
    public void onMessage(MessageEvent event){
        backend.send(event.message);
    }
    

    粘性事件

    粘性事件是什么?我们先看看官方文档的翻译:
    一些事件携带在事件发布之后感兴趣的信息。 例如,事件表示某些初始化完成。 或者如果您有一些传感器或位置数据,并且想要保持最近的值。 而不是实现自己的缓存,你可以使用粘性事件。 所以EventBus保持内存中某个类型的最后一个粘性事件。 然后粘性事件可以传递给订阅者或明确地查询。 因此,您不需要任何特殊的逻辑来考虑已有的数据

    简单理解一下:上面说的也很官方,其实就是说粘性事件就是可以让EventBus实现一次发布,多次接收,并且可以在后续再接收,只要注册就会自动接收,就像它会一直存在一样,有地方注册了以后就能接收到它。
    举个例子辅助理解,比如我直接先发布了一个事件,但没有定义接收方法,我在后续需要的时候才去注册并定义接收方法,这样也能够接收到该事件,这就是EventBus的粘性事件,我们来做个简单的例子吧。(先发布,需要的时候再接收)

    我在活动开始的时候去发布一个事件,跳转到新的页面时再来注册EventBus和定义接收方法,看看能不能就收到该事件。

    class SecondActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_second)
            doit.setOnClickListener {
                if (edit_name.equals("")) {
                    return@setOnClickListener
                }
                EventBus.getDefault().postSticky(edit_name.text.toString())
               startActivity(Intent(this,FirstActivity::class.java))
            }
        }
    }
    

    页面布局和上面例子一样,我把开始的界面改成了SecondActivity ,然后在里面发布了一个事件,值得注意的是发布时需要使用postSticky方法而不是post方法

    class FirstActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            EventBus.getDefault().register(this)
        }
    
        override fun onDestroy() {
            super.onDestroy()
            EventBus.getDefault().unregister(this)
        }
    
        @Subscribe(threadMode = ThreadMode.MAIN,sticky = true)
        fun getEventBusMsg(name: String) {
            hello.text = "你好!$name"
        }
    }
    

    跳转到FirstActivity 页面时我注册了EventBus并且定义了接收方法,和一般不同的是接收方法里面需要添加sticky = true的参数,否则无法接收到粘性事件。
    效果如下:


    1637829724001.gif

    如果你还需要在其他活动里面接收该事件也是一样的,在需要的地方进行注册和接收就可以了,当然,如果接收完以后其他地方不再需要接收该事件了,你也可以手动注销粘性事件:

     @Subscribe(threadMode = ThreadMode.MAIN,sticky = true)
        fun getEventBusMsg(name: String) {
            hello.text = "你好!$name"
            EventBus.getDefault().removeStickyEvent(name);
        }
    

    参考文章:
    EventBus官网地址
    Android | EventBus使用详解
    android EventBus的简单使用

    相关文章

      网友评论

          本文标题:EventBus

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