EventBus,作为我学习的继Android官方推出的Handler和AysncTask之后的第三个消息机制框架,在很多项目的开发中都能听说过这个框架,可见该框架的热门程度。作为一个相当有人气的框架,自然成为了需要学习的对象。
EventBus是一种用于Android的事件发布——订阅总线,由GreenRobot开发,Gihub地址是:EventBus。它简化了应用程序内各个组件之间进行通信的复杂度,尤其是碎片之间进行通信的问题,可以避免由于使用广播通信而带来的诸多不便。
关于 EventBus在开发中经常会选择使用它来进行模块间通信、解耦。平常使用这个库只是很浅显的操作三部曲:register
,post
,unregister
,来达到基础的开发目的。因此这篇博客中除了演示EventBus的基本功能之外,还会再进阶地讲解一些其拓展功能,包括:线程模型、黏性事件、优先级,让读者可以更好地了解到EventBus的具体功能。
在演示EventBus之前,我们先在下一节简单介绍一下EventBus的特性,即发布——订阅模型。
2.特性EventBus是一个开源库,它利用发布——订阅模式来对项目进行解耦。它可以利用很少的代码,来实现多组件间通信。Android的组件间通信,我们不由得会想到Handler消息机制和广播机制等方式,通过它们也可以进行通信。但是使用它们进行通信,代码量多,组件间容易产生耦合引用。关于EventBus的工作模式,这里引用一张官方图帮助理解:
这里按照从左到右的顺序,简单介绍一下图中出现的三个方框(角色)所代表的含义:
EventBus.getDefault()
就可以得到一个EventBus对象,然后再调用post(Object)
方法发送消息(Event)即可。onEvent
开头的那几个方法,分别是onEvent
、onEventMainThread
、onEventBackgroundThread
和onEventAsync
,而在3.0之后事件处理的方法名可以随意取,不过需要加上注解@Subscribe
,并且指定线程模型,默认是POSTING
。从图中可以看出,Publisher(发布者)通过post()
方法,把Event(事件)发布出去。之后Subscriber(订阅者)在onEvent()
方法(方法名可以自定义)中接收事件,就完成了一次消息传递的过程。当然,在提及Subscriber时,还提到了关于线程模型的概念,这里我们后面再详细说明,接下来会首先演示一下EventBus的基本使用方法。
如果不能理解发布——订阅模型的读者,可以通俗地把这个模型理解成类似于小说网站中订阅小说的场景:
一般作者(Publisher,发布者)在推送小说中的最新章节(Event,事件)时,订阅了该小说的读者(Subscriber,订阅者)就会接收到这条消息(即“小说的最新章节更新了”)。这种模型的好处就是:作者可以随时随地将新章节推送出去(即发送消息),而订阅了小说的读者不需要每分每秒都去关注作者,只需要准时了解到小说的最新章节已经发布的消息(即接收消息),然后再去看小说即可。
发布——订阅模型的大概意思就是如上面的例子所说,如果还是不能理解的话,可以参看CSDN上详细讲解了该模型的博文,这里就不再赘述了。
最后,我们再看看使用EventBus的几条理由:
现在,我们就来体验EventBus的强大之处吧。
3.演示老样子,在我们使用某个框架之前,都是需要先集成的。我们去EventBus的GItHub官网上获取最新的依赖,然后修改moudle的build.gradle,代码如下:
implementation 'org.greenrobot:eventbus:3.2.0'
想要使用EventBus进行消息传递,首先需要创建一个自定义的消息事件类,消息事件类型可以是String
,int
等常见类,也可以是自己自定义一个事件类,方便管理。为了更好地演示,这里自定义了一个名为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;}@Overridepublic 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;}
}
创建好消息事件后,接下来就是要声明订阅者。为了便于演示,这里将订阅者设定到一个名为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;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_subscriber);// 初始化UIinitUI();// 注册点击事件btn_next_activity.setOnClickListener(new View.OnClickListener() {@Overridepublic 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);}@Overrideprotected void onStart() {super.onStart();// 订阅者——Subscriber需要绑定EventBusEventBus.getDefault().register(this);}// @Override
// protected void onStop() {
// super.onStop();
// // 订阅者——Subscriber需要解绑EventBus
// EventBus.getDefault().unregister(this);
// }@Overrideprotected void onDestroy() {super.onDestroy();// 订阅者——Subscriber需要解绑EventBusEventBus.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还有两个问题需要留意一下:
另外:在类中我们用@Subscribe
声明了onMessageEvent()
这句接受事件消息的方法。在创建这个方法时,同样需要注意几个问题:
public
修饰符修饰,不能用static
关键字修饰,不能是抽象的(abstract
)@Subscribe
注解进行修饰。关于@Subscribe
注解的详细介绍,我们放在拓展功能再介绍,现在就按照这样写即可。
<LinearLayout xmlns:android&#61;"http://schemas.android.com/apk/res/android"xmlns:app&#61;"http://schemas.android.com/apk/res-auto"xmlns:tools&#61;"http://schemas.android.com/tools"android:layout_width&#61;"match_parent"android:layout_height&#61;"match_parent"tools:context&#61;".SubscriberActivity"><Buttonandroid:id&#61;"&#64;&#43;id/btn_next_activity"android:layout_width&#61;"match_parent"android:layout_height&#61;"wrap_content"android:text&#61;"跳转到发布者Activity"/>LinearLayout>
创建好消息的订阅者后&#xff0c;接下来就是要声明发布者。为了便于演示&#xff0c;这里将订阅者设定到一个名为PublisherActivity的Activity中。在该Activity中&#xff0c;我们同样设置一个按钮&#xff0c;然后在按钮的点击事件中调用EventBus的post(Object event)
方法来发送消息&#xff0c;这样就可以在日志中看到SubscriberActivity接收到了该条消息&#xff0c;PublisherActivity的Java代码和布局文件代码如下&#xff1a;
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;&#64;Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_publisher);// 初始化UIinitUI();// 为按钮注册点击事件&#xff0c;发送消息btn_send_message.setOnClickListener(new View.OnClickListener() {&#64;Overridepublic void onClick(View v) {EventMessage message &#61; new EventMessage("测试消息","你好&#xff01;");EventBus.getDefault().post(message);}});}/*** 初始化控件*/private void initUI() {btn_send_message &#61; findViewById(R.id.btn_send_message);}
}
<LinearLayout xmlns:android&#61;"http://schemas.android.com/apk/res/android"xmlns:tools&#61;"http://schemas.android.com/tools"android:layout_width&#61;"match_parent"android:layout_height&#61;"match_parent"tools:context&#61;".PublisherActivity"android:orientation&#61;"vertical"><Buttonandroid:id&#61;"&#64;&#43;id/btn_send_message"android:layout_width&#61;"match_parent"android:layout_height&#61;"wrap_content"android:text&#61;"发送消息"/>LinearLayout>
当以上的三个步骤都完成之后&#xff0c;我们首先从SubscriberActivity点击按钮进入PublisherActivity&#xff0c;然后在PublisherActivity中点击按钮后&#xff0c;可以看到SubscriberActivity确实收到了消息&#xff0c;并且打印了日志&#xff0c;如图所示&#xff1a;
完成了EventBus基础功能的演示后&#xff0c;我们在这一小节里简单地总结一下EventBus传递消息的整个过程&#xff1a;
String
、int
等常见的数据类型&#xff1b;&#64;Subscribe
声明的接收消息的订阅方法&#xff1b;post(Object event)
即可将消息发送出去。注意&#xff1a;该该例子中&#xff0c;我使用了EventBus.getDefault()
方法来获取EventBus的对象实例。实际上&#xff0c;该方法会获取一个单例&#xff0c;所以才可以随时使用。如果不是用这种单例模式&#xff0c;就需要想办法把订阅者&#xff08;Subscriber&#xff09;注册时用的EventBus的引用传给需要发送事件的模块中。简而言之&#xff0c;就是Subscriber用的EventBus和post方法需要的EventBus需要是同一个EventBus&#xff08;即一个对象&#xff09;。
换句话来说&#xff0c;EventBus也是支持跨模块的。
通过这三步&#xff0c;我们就完成了EventBus所提供的消息传递流程。当然&#xff0c;EventBus的功能不仅局限于这些&#xff0c;下面还会介绍一些它的拓展功能。
上面我们在编写订阅者中接收消息事件的方法时&#xff0c;提到过需要特别加上&#64;Subscribe
注解来声明方法。在这一节中&#xff0c;我们将要说明&#64;Subscribe
注解的一些其他用法。
&#64;Subscribe
是EventBus自定义的一种注解&#xff0c;它总共可以接收三个参数。ThreadMode
、boolean sticky
、int priority
。
所以上面的接收Event方法的代码&#xff0c;完整版的可以这样写&#xff1a;
&#64;Subscribe(threadMode &#61; ThreadMode.MAIN,sticky &#61; true,priority &#61; 1)
public void onMessageEvent(EventMessage message) {Log.i(TAG,"收到消息啦&#xff01;" &#43; "消息的类型为&#xff1a;" &#43; message.getType() &#43; "消息的内容为&#xff1a;" &#43; message.getMessage());};
这三个参数代表对&#64;Subscribe
注解的方法中的一些配置&#xff0c;可以根据需要选择是否使用。
接下来&#xff0c;就专门介绍这三个参数的用法。
&#64;Subscribe
的第一个参数——threadMode
&#xff0c;顾名思义&#xff0c;代表着线程模型的意思。在EventBus中&#xff0c;定义了总共5种线程模型&#xff08;原先看博文时只提到了4种&#xff0c;可能是新版本的缘故又出现了第5种线程模型&#xff0c;即MAIN_ORDERED
&#xff09;&#xff0c;这5种线程模型如图所示&#xff1a;
EventBus为什么要定义线程模型&#xff1f;主要还是出于实际应用场景考虑。线程模型约束了post()
&#xff08;发布事件方法&#xff0c;Publisher&#xff09;和onMessageEvent()
&#xff08;接收事件方法&#xff0c;Subscriber &#xff09;分别在不同线程下的情况&#xff0c;以满足生产上的需要。
首先&#xff0c;我们先看看EventBus提供的五种线程模型&#xff1a;
POSTING
&#xff1a;默认&#xff0c;表示事件处理函数的线程跟发布事件的线程在同一个线程&#xff1b;MAIN
&#xff1a;表示事件接收函数的线程在主线程(UI)线程&#xff0c;因此在这里不能进行耗时操作&#xff1b;MAIN_ORDERED
&#xff1a;类似MAIN_ORDERED
&#xff0c;但是无论事件发布者在主线程或者是子线程&#xff0c;都不会造成线程阻塞&#xff1b;BACKGROUND
&#xff1a;表示事件处理函数的线程在后台线程&#xff0c;因此不能进行UI操作。如果发布事件的线程是主线程(UI线程)&#xff0c;那么事件处理函数将会开启一个后台线程&#xff0c;如果果发布事件的线程是在后台线程&#xff0c;那么事件处理函数就使用该线程&#xff1b;ASYNC
&#xff1a;表示无论事件发布的线程是哪一个&#xff0c;事件处理函数始终会新建一个子线程运行&#xff0c;同样不能进行UI操作。最开始看到这5种线程模型的说明时&#xff0c;确实一下子不太好理解。事实上&#xff0c;线程模型就是为了约束当事件发布者处于什么线程时&#xff0c;事件订阅者就处于什么线程。为了便于描述&#xff0c;作者这里绘制了一个表格来总结一下这5种线程模型&#xff0c;它们所处的线程关系可以看作是一一对应的关系&#xff0c;表格如下&#xff1a;
线程模型 | 事件发布者所处线程&#xff08;Publisher&#xff09; | 事件订阅者所处线程&#xff08;Subscriber&#xff09; |
---|---|---|
POSTING &#xff08;默认&#xff09; | 主线程/子线程 | 主线程/子线程 |
MAIN | 主线程&#xff08;会阻塞&#xff09;/子线程 | 主线程 |
MAIN_ORDERED | 主线程&#xff08;不会阻塞&#xff09;/子线程 | 主线程 |
BACKGROUND | 主线程/子线程 | 子线程/跟Publisher一样的子线程 |
ASYNC | 主线程/子线程 | 子线程 |
一般来说&#xff0c;使用POSTING
模式适合使用在执行简单任务的情况下&#xff0c;不需要复杂运算&#xff0c;因为这种模式不需要做线程切换的判断逻辑&#xff0c;直接分发至相同的线程环境&#xff0c;速度快&#xff0c;耗时少。我们使用的最多的&#xff0c;也是这个模式。
&#64;Subscribe
的第二个参数——sticky
&#xff0c;是一个boolean
类型的参数&#xff0c;默认值是false
&#xff0c;表示不启用黏性特性&#xff0c;在这里也代表着黏性事件的意思。该特性代表什么意思呢&#xff1f;我们之前举的EventBus事件传递的例子时&#xff0c;都是先对订阅者&#xff08;Subscriber&#xff09;进行注册&#xff0c;然后再post事件的&#xff08;顺序是&#xff1a;1.声明消息事件——>2.声明订阅者——>3.声明发布者&#xff09;。那黏性事件的意义则是最后两步反过来&#xff1a;先post事件&#xff0c;后对订阅者注册&#xff08;顺序是&#xff1a;1.声明消息事件——>2.声明发布者——>3.声明订阅者&#xff09;。
沿用上面的代码&#xff0c;可以举一个简单的例子。这里想要使用黏性事件需要修改两处&#xff1a;
&#64;Subscribe
上添加参数sticky &#61; true
&#xff0c;表示开启黏性事件&#xff0c;例如&#64;Subscribe(threadMode &#61; ThreadMode.MAIN,sticky &#61; true)
&#xff1b;postSticky()
来替代post()
&#xff0c;表示发出的是黏性事件。代码如下&#xff1a;
private View.OnClickListener mGoListener &#61; new View.OnClickListener() {&#64;Overridepublic void onClick(View v) {Log.e(TAG, "onClick: post");EventMessage message &#61; new EventMessage("测试黏性消息","你好&#xff01;");EventBus.getDefault().postSticky(message);}};private View.OnClickListener mRegisterListener &#61; new View.OnClickListener() {&#64;Overridepublic void onClick(View v) {Log.e(TAG, "onClick: start register" );EventBus.getDefault().register(SubscriberActivity.this);}};&#64;Subscribe(threadMode &#61; ThreadMode.MAIN,sticky &#61; true)public void onMessageEvent(EventMessage message) {Log.i(TAG,"收到消息啦&#xff01;" &#43; "消息的类型为&#xff1a;" &#43; message.getType() &#43; "消息的内容为&#xff1a;" &#43; message.getMessage());};
这里我们先执行mGoListener
绑定的点击事件&#xff0c;将消息发布出去&#xff0c;然后再执行mRegisterListener
绑定的点击事件&#xff0c;注册EventBus。接下来可以查看日志信息&#xff0c;就可以发现代码成功运行了。
&#64;Subscribe
的第三个参数——priority
&#xff0c;是一个int
类型的参数&#xff0c;默认值是0
。这个参数比较好理解&#xff0c;就是指定订阅方法的优先级&#xff0c;值越大表示优先级越大。在某个事件被发布出来的时候&#xff0c;优先级较高的订阅方法会首先接收到事件。
这里有两个地方需要注意&#xff1a;
ThreadMode
参数的时候&#xff0c;它们的优先级才会与priority
指定的值一致&#xff1b;ThreadMode
参数为POSTING
的时候&#xff0c;它才能停止该事件的继续分发。AFL——Android框架学习