每天学习一个Android中的常用框架——14.EventBus

文章目录

  • 1.简介
  • 2.特性
  • 3.演示
    • 3.1 集成
    • 3.2 基本功能
      • 3.2.1 声明事件类型——Event
      • 3.2.2 声明订阅者——Subscriber
      • 3.2.3 声明发布者——Publisher
      • 3.2.4 总结
    • 3.3 拓展功能
      • 3.3.1 线程模型
      • 3.3.2 黏性事件
      • 3.3.3 优先级
  • 4.源码地址

1.简介

EventBus,作为我学习的继Android官方推出的Handler和AysncTask之后的第三个消息机制框架,在很多项目的开发中都能听说过这个框架,可见该框架的热门程度。作为一个相当有人气的框架,自然成为了需要学习的对象。

EventBus是一种用于Android的事件发布——订阅总线,由GreenRobot开发,Gihub地址是:EventBus。它简化了应用程序内各个组件之间进行通信的复杂度,尤其是碎片之间进行通信的问题,可以避免由于使用广播通信而带来的诸多不便。

关于 EventBus在开发中经常会选择使用它来进行模块间通信、解耦。平常使用这个库只是很浅显的操作三部曲:registerpostunregister,来达到基础的开发目的。因此这篇博客中除了演示EventBus的基本功能之外,还会再进阶地讲解一些其拓展功能,包括:线程模型、黏性事件、优先级,让读者可以更好地了解到EventBus的具体功能。

在演示EventBus之前,我们先在下一节简单介绍一下EventBus的特性,即发布——订阅模型。

2.特性

EventBus是一个开源库,它利用发布——订阅模式来对项目进行解耦。它可以利用很少的代码,来实现多组件间通信。Android的组件间通信,我们不由得会想到Handler消息机制和广播机制等方式,通过它们也可以进行通信。但是使用它们进行通信,代码量多,组件间容易产生耦合引用。关于EventBus的工作模式,这里引用一张官方图帮助理解:
每天学习一个Android中的常用框架——14.EventBus_第1张图片
这里按照从左到右的顺序,简单介绍一下图中出现的三个方框(角色)所代表的含义:

  • Publisher:事件的发布者,可以在任意线程里发布事件。一般情况下,使用EventBus.getDefault()就可以得到一个EventBus对象,然后再调用post(Object)方法发送消息(Event)即可。
  • Event:事件,它可以是任意类型,EventBus会根据事件类型进行全局的通知。在EventBus中,我们可以自定义消息。
  • Subscriber:事件的订阅者,在EventBus 3.0之前我们必须定义以onEvent开头的那几个方法,分别是onEventonEventMainThreadonEventBackgroundThreadonEventAsync,而在3.0之后事件处理的方法名可以随意取,不过需要加上注解@Subscribe,并且指定线程模型,默认是POSTING

从图中可以看出,Publisher(发布者)通过post()方法,把Event(事件)发布出去。之后Subscriber(订阅者)在onEvent()方法(方法名可以自定义)中接收事件,就完成了一次消息传递的过程。当然,在提及Subscriber时,还提到了关于线程模型的概念,这里我们后面再详细说明,接下来会首先演示一下EventBus的基本使用方法。


如果不能理解发布——订阅模型的读者,可以通俗地把这个模型理解成类似于小说网站中订阅小说的场景:

一般作者(Publisher,发布者)在推送小说中的最新章节(Event,事件)时,订阅了该小说的读者(Subscriber,订阅者)就会接收到这条消息(即“小说的最新章节更新了”)。这种模型的好处就是:作者可以随时随地将新章节推送出去(即发送消息),而订阅了小说的读者不需要每分每秒都去关注作者,只需要准时了解到小说的最新章节已经发布的消息(即接收消息),然后再去看小说即可。

发布——订阅模型的大概意思就是如上面的例子所说,如果还是不能理解的话,可以参看CSDN上详细讲解了该模型的博文,这里就不再赘述了。


最后,我们再看看使用EventBus的几条理由:

  • 简化了组件间交流的方式
  • 对事件通信双方进行解耦
  • 可以灵活方便的指定工作线程,通过ThreadMode
  • 速度快,性能好
  • 库比较小,不占内存
  • 使用这个库的app多,有权威性
  • 功能多,使用方便

现在,我们就来体验EventBus的强大之处吧。

3.演示

3.1 集成

老样子,在我们使用某个框架之前,都是需要先集成的。我们去EventBus的GItHub官网上获取最新的依赖,然后修改moudle的build.gradle,代码如下:

implementation 'org.greenrobot:eventbus:3.2.0'

3.2 基本功能

3.2.1 声明事件类型——Event

想要使用EventBus进行消息传递,首先需要创建一个自定义的消息事件类,消息事件类型可以是Stringint等常见类,也可以是自己自定义一个事件类,方便管理。为了更好地演示,这里自定义了一个名为EventMessage的消息事件类。事件类的编写很简单,基本上跟常见的Java Bean结构相同,代码如下:

package com.androidframelearn.event_eventbus;

public class EventMessage {

    private String type;
    private String message;

    public EventMessage() {
    }

    public EventMessage(String type, String message) {
        this.type = type;
        this.message = message;
    }

    @Override
    public String toString() {
        return "消息类型为:" + type + "===消息为:" + message;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

3.2.2 声明订阅者——Subscriber

创建好消息事件后,接下来就是要声明订阅者。为了便于演示,这里将订阅者设定到一个名为SubscriberActivity的Activity中。在该Activity中,我们首先要注册EventBus,标示这是一个被EventBus管理的订阅者。接着,我们编写onMessageEvent()方法,作为接受事件消息的回调。最后,我们再设定一个按钮,让这个按钮跳转到下一节将要编写的PublisherActivity中,我们将在那个Activity中发布消息。SubscriberActivity的Java代码和布局文件代码如下:

package com.androidframelearn.event_eventbus;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

public class SubscriberActivity extends AppCompatActivity {

    private static final String TAG = "SubscriberActivity";

    private Button btn_next_activity;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_subscriber);

        // 初始化UI
        initUI();

        // 注册点击事件
        btn_next_activity.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(SubscriberActivity.this,PublisherActivity.class);
                startActivity(intent);
            }
        });

    }

    /**
     * 初始化UI
     */
    private void initUI() {
        btn_next_activity = findViewById(R.id.btn_next_activity);
    }

    @Override
    protected void onStart() {
        super.onStart();
        // 订阅者——Subscriber需要绑定EventBus
        EventBus.getDefault().register(this);
    }

//    @Override
//    protected void onStop() {
//        super.onStop();
//        // 订阅者——Subscriber需要解绑EventBus
//        EventBus.getDefault().unregister(this);
//    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 订阅者——Subscriber需要解绑EventBus
        EventBus.getDefault().unregister(this);
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(EventMessage message) {
        Log.i(TAG,"收到消息啦!" + "消息的类型为:" + message.getType() + "消息的内容为:" + message.getMessage());
    };
}


注意:在EventBus的官方实例中,注册和注销EventBus的过程(即EventBus.getDefault().register()EventBus.getDefault().unregister())推荐是在onStart()onStop()方法中的。这里为了演示,将注销EventBus的过程放在了onDestroy()中。实际上,这两个步骤是可以根据需求进行改动的。

除此之外,EventBus还有两个问题需要留意一下:

  1. EventBus注册完了,在不用的时候千万别忘了注销;
  2. EventBus不能重复注册。即在注册了一次之后,没有注销的情况下,又注册了一次。

另外:在类中我们用@Subscribe声明了onMessageEvent()这句接受事件消息的方法。在创建这个方法时,同样需要注意几个问题:

  1. 该方法有且只有一个参数。
  2. 该方法必须是public修饰符修饰,不能用static关键字修饰,不能是抽象的(abstract
  3. 该方法需要用@Subscribe注解进行修饰。

关于@Subscribe注解的详细介绍,我们放在拓展功能再介绍,现在就按照这样写即可。



<LinearLayout 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=".SubscriberActivity">

    <Button
        android:id="@+id/btn_next_activity"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="跳转到发布者Activity"/>

LinearLayout>

3.2.3 声明发布者——Publisher

创建好消息的订阅者后,接下来就是要声明发布者。为了便于演示,这里将订阅者设定到一个名为PublisherActivity的Activity中。在该Activity中,我们同样设置一个按钮,然后在按钮的点击事件中调用EventBus的post(Object event)方法来发送消息,这样就可以在日志中看到SubscriberActivity接收到了该条消息,PublisherActivity的Java代码和布局文件代码如下:

package com.androidframelearn.event_eventbus;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import org.greenrobot.eventbus.EventBus;

public class PublisherActivity extends AppCompatActivity {

    private Button btn_send_message;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_publisher);

        // 初始化UI
        initUI();

        // 为按钮注册点击事件,发送消息
        btn_send_message.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EventMessage message = new EventMessage("测试消息","你好!");
                EventBus.getDefault().post(message);
            }
        });
    }

    /**
     * 初始化控件
     */
    private void initUI() {
        btn_send_message = findViewById(R.id.btn_send_message);
    }
}


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".PublisherActivity"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn_send_message"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="发送消息"/>

LinearLayout>

当以上的三个步骤都完成之后,我们首先从SubscriberActivity点击按钮进入PublisherActivity,然后在PublisherActivity中点击按钮后,可以看到SubscriberActivity确实收到了消息,并且打印了日志,如图所示:
在这里插入图片描述

3.2.4 总结

完成了EventBus基础功能的演示后,我们在这一小节里简单地总结一下EventBus传递消息的整个过程:

  1. 首先需要自定义一个类似于Java Bean一样的消息事件类(Event),当然也可以直接用Stringint等常见的数据类型;
  2. 接下来就需要编写消息订阅者(Subscriber),在订阅者中注册/注销EventBus,然后编写用@Subscribe声明的接收消息的订阅方法;
  3. 最后编写消息发布者(Publisher),调用EventBus的post(Object event)即可将消息发送出去。

注意:该该例子中,我使用了EventBus.getDefault()方法来获取EventBus的对象实例。实际上,该方法会获取一个单例,所以才可以随时使用。如果不是用这种单例模式,就需要想办法把订阅者(Subscriber)注册时用的EventBus的引用传给需要发送事件的模块中。简而言之,就是Subscriber用的EventBus和post方法需要的EventBus需要是同一个EventBus(即一个对象)。

换句话来说,EventBus也是支持跨模块的。


通过这三步,我们就完成了EventBus所提供的消息传递流程。当然,EventBus的功能不仅局限于这些,下面还会介绍一些它的拓展功能。

3.3 拓展功能

上面我们在编写订阅者中接收消息事件的方法时,提到过需要特别加上@Subscribe注解来声明方法。在这一节中,我们将要说明@Subscribe注解的一些其他用法。

@Subscribe是EventBus自定义的一种注解,它总共可以接收三个参数。ThreadModeboolean stickyint priority

所以上面的接收Event方法的代码,完整版的可以这样写:

@Subscribe(threadMode = ThreadMode.MAIN,sticky = true,priority = 1)
public void onMessageEvent(EventMessage message) {
        Log.i(TAG,"收到消息啦!" + "消息的类型为:" + message.getType() + "消息的内容为:" + message.getMessage());
    };

这三个参数代表对@Subscribe注解的方法中的一些配置,可以根据需要选择是否使用。

接下来,就专门介绍这三个参数的用法。

3.3.1 线程模型

@Subscribe的第一个参数——threadMode,顾名思义,代表着线程模型的意思。在EventBus中,定义了总共5种线程模型(原先看博文时只提到了4种,可能是新版本的缘故又出现了第5种线程模型,即MAIN_ORDERED),这5种线程模型如图所示:
每天学习一个Android中的常用框架——14.EventBus_第2张图片
EventBus为什么要定义线程模型?主要还是出于实际应用场景考虑。线程模型约束了post()(发布事件方法,Publisher)和onMessageEvent()(接收事件方法,Subscriber )分别在不同线程下的情况,以满足生产上的需要。

首先,我们先看看EventBus提供的五种线程模型:

  1. POSTING:默认,表示事件处理函数的线程跟发布事件的线程在同一个线程;
  2. MAIN:表示事件接收函数的线程在主线程(UI)线程,因此在这里不能进行耗时操作;
  3. MAIN_ORDERED:类似MAIN_ORDERED,但是无论事件发布者在主线程或者是子线程,都不会造成线程阻塞;
  4. BACKGROUND:表示事件处理函数的线程在后台线程,因此不能进行UI操作。如果发布事件的线程是主线程(UI线程),那么事件处理函数将会开启一个后台线程,如果果发布事件的线程是在后台线程,那么事件处理函数就使用该线程;
  5. ASYNC:表示无论事件发布的线程是哪一个,事件处理函数始终会新建一个子线程运行,同样不能进行UI操作。

最开始看到这5种线程模型的说明时,确实一下子不太好理解。事实上,线程模型就是为了约束当事件发布者处于什么线程时,事件订阅者就处于什么线程。为了便于描述,作者这里绘制了一个表格来总结一下这5种线程模型,它们所处的线程关系可以看作是一一对应的关系,表格如下:

线程模型 事件发布者所处线程(Publisher) 事件订阅者所处线程(Subscriber)
POSTING(默认) 主线程/子线程 主线程/子线程
MAIN 主线程(会阻塞)/子线程 主线程
MAIN_ORDERED 主线程(不会阻塞)/子线程 主线程
BACKGROUND 主线程/子线程 子线程/跟Publisher一样的子线程
ASYNC 主线程/子线程 子线程

一般来说,使用POSTING模式适合使用在执行简单任务的情况下,不需要复杂运算,因为这种模式不需要做线程切换的判断逻辑,直接分发至相同的线程环境,速度快,耗时少。我们使用的最多的,也是这个模式。

3.3.2 黏性事件

@Subscribe的第二个参数——sticky,是一个boolean类型的参数,默认值是false,表示不启用黏性特性,在这里也代表着黏性事件的意思。该特性代表什么意思呢?我们之前举的EventBus事件传递的例子时,都是先对订阅者(Subscriber)进行注册,然后再post事件的(顺序是:1.声明消息事件——>2.声明订阅者——>3.声明发布者)。那黏性事件的意义则是最后两步反过来:先post事件,后对订阅者注册(顺序是:1.声明消息事件——>2.声明发布者——>3.声明订阅者)。

沿用上面的代码,可以举一个简单的例子。这里想要使用黏性事件需要修改两处:

  1. 修改接收消息的方法,在@Subscribe上添加参数sticky = true,表示开启黏性事件,例如@Subscribe(threadMode = ThreadMode.MAIN,sticky = true)
  2. 修改发布事件的方法,使用postSticky()来替代post(),表示发出的是黏性事件。

代码如下:

	private View.OnClickListener mGoListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.e(TAG, "onClick: post");
            EventMessage message = new EventMessage("测试黏性消息","你好!");
            EventBus.getDefault().postSticky(message);
        }
    };

    private View.OnClickListener mRegisterListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.e(TAG, "onClick: start register" );
            EventBus.getDefault().register(SubscriberActivity.this);
        }
    };

	@Subscribe(threadMode = ThreadMode.MAIN,sticky = true)
    public void onMessageEvent(EventMessage message) {
        Log.i(TAG,"收到消息啦!" + "消息的类型为:" + message.getType() + "消息的内容为:" + message.getMessage());
    };

这里我们先执行mGoListener绑定的点击事件,将消息发布出去,然后再执行mRegisterListener绑定的点击事件,注册EventBus。接下来可以查看日志信息,就可以发现代码成功运行了。

3.3.3 优先级

@Subscribe的第三个参数——priority,是一个int类型的参数,默认值是0。这个参数比较好理解,就是指定订阅方法的优先级,值越大表示优先级越大。在某个事件被发布出来的时候,优先级较高的订阅方法会首先接收到事件。

这里有两个地方需要注意:

  1. 只有当两个订阅方法使用相同的ThreadMode参数的时候,它们的优先级才会与priority指定的值一致;
  2. 只有当某个订阅方法的ThreadMode参数为POSTING的时候,它才能停止该事件的继续分发。

4.源码地址

AFL——Android框架学习

你可能感兴趣的:(Android)