EventBus,作为我学习的继Android官方推出的Handler和AysncTask之后的第三个消息机制框架,在很多项目的开发中都能听说过这个框架,可见该框架的热门程度。作为一个相当有人气的框架,自然成为了需要学习的对象。
EventBus是一种用于Android的事件发布——订阅总线,由GreenRobot开发,Gihub地址是:EventBus。它简化了应用程序内各个组件之间进行通信的复杂度,尤其是碎片之间进行通信的问题,可以避免由于使用广播通信而带来的诸多不便。
关于 EventBus在开发中经常会选择使用它来进行模块间通信、解耦。平常使用这个库只是很浅显的操作三部曲:register
,post
,unregister
,来达到基础的开发目的。因此这篇博客中除了演示EventBus的基本功能之外,还会再进阶地讲解一些其拓展功能,包括:线程模型、黏性事件、优先级,让读者可以更好地了解到EventBus的具体功能。
在演示EventBus之前,我们先在下一节简单介绍一下EventBus的特性,即发布——订阅模型。
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的强大之处吧。
老样子,在我们使用某个框架之前,都是需要先集成的。我们去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;
}
@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;
}
}
创建好消息事件后,接下来就是要声明订阅者。为了便于演示,这里将订阅者设定到一个名为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还有两个问题需要留意一下:
另外:在类中我们用@Subscribe
声明了onMessageEvent()
这句接受事件消息的方法。在创建这个方法时,同样需要注意几个问题:
public
修饰符修饰,不能用static
关键字修饰,不能是抽象的(abstract
)@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>
创建好消息的订阅者后,接下来就是要声明发布者。为了便于演示,这里将订阅者设定到一个名为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确实收到了消息,并且打印了日志,如图所示:
完成了EventBus基础功能的演示后,我们在这一小节里简单地总结一下EventBus传递消息的整个过程:
String
、int
等常见的数据类型;@Subscribe
声明的接收消息的订阅方法;post(Object event)
即可将消息发送出去。注意:该该例子中,我使用了EventBus.getDefault()
方法来获取EventBus的对象实例。实际上,该方法会获取一个单例,所以才可以随时使用。如果不是用这种单例模式,就需要想办法把订阅者(Subscriber)注册时用的EventBus的引用传给需要发送事件的模块中。简而言之,就是Subscriber用的EventBus和post方法需要的EventBus需要是同一个EventBus(即一个对象)。
换句话来说,EventBus也是支持跨模块的。
通过这三步,我们就完成了EventBus所提供的消息传递流程。当然,EventBus的功能不仅局限于这些,下面还会介绍一些它的拓展功能。
上面我们在编写订阅者中接收消息事件的方法时,提到过需要特别加上@Subscribe
注解来声明方法。在这一节中,我们将要说明@Subscribe
注解的一些其他用法。
@Subscribe
是EventBus自定义的一种注解,它总共可以接收三个参数。ThreadMode
、boolean sticky
、int priority
。
所以上面的接收Event方法的代码,完整版的可以这样写:
@Subscribe(threadMode = ThreadMode.MAIN,sticky = true,priority = 1)
public void onMessageEvent(EventMessage message) {
Log.i(TAG,"收到消息啦!" + "消息的类型为:" + message.getType() + "消息的内容为:" + message.getMessage());
};
这三个参数代表对@Subscribe
注解的方法中的一些配置,可以根据需要选择是否使用。
接下来,就专门介绍这三个参数的用法。
@Subscribe
的第一个参数——threadMode
,顾名思义,代表着线程模型的意思。在EventBus中,定义了总共5种线程模型(原先看博文时只提到了4种,可能是新版本的缘故又出现了第5种线程模型,即MAIN_ORDERED
),这5种线程模型如图所示:
EventBus为什么要定义线程模型?主要还是出于实际应用场景考虑。线程模型约束了post()
(发布事件方法,Publisher)和onMessageEvent()
(接收事件方法,Subscriber )分别在不同线程下的情况,以满足生产上的需要。
首先,我们先看看EventBus提供的五种线程模型:
POSTING
:默认,表示事件处理函数的线程跟发布事件的线程在同一个线程;MAIN
:表示事件接收函数的线程在主线程(UI)线程,因此在这里不能进行耗时操作;MAIN_ORDERED
:类似MAIN_ORDERED
,但是无论事件发布者在主线程或者是子线程,都不会造成线程阻塞;BACKGROUND
:表示事件处理函数的线程在后台线程,因此不能进行UI操作。如果发布事件的线程是主线程(UI线程),那么事件处理函数将会开启一个后台线程,如果果发布事件的线程是在后台线程,那么事件处理函数就使用该线程;ASYNC
:表示无论事件发布的线程是哪一个,事件处理函数始终会新建一个子线程运行,同样不能进行UI操作。最开始看到这5种线程模型的说明时,确实一下子不太好理解。事实上,线程模型就是为了约束当事件发布者处于什么线程时,事件订阅者就处于什么线程。为了便于描述,作者这里绘制了一个表格来总结一下这5种线程模型,它们所处的线程关系可以看作是一一对应的关系,表格如下:
线程模型 | 事件发布者所处线程(Publisher) | 事件订阅者所处线程(Subscriber) |
---|---|---|
POSTING (默认) |
主线程/子线程 | 主线程/子线程 |
MAIN |
主线程(会阻塞)/子线程 | 主线程 |
MAIN_ORDERED |
主线程(不会阻塞)/子线程 | 主线程 |
BACKGROUND |
主线程/子线程 | 子线程/跟Publisher一样的子线程 |
ASYNC |
主线程/子线程 | 子线程 |
一般来说,使用POSTING
模式适合使用在执行简单任务的情况下,不需要复杂运算,因为这种模式不需要做线程切换的判断逻辑,直接分发至相同的线程环境,速度快,耗时少。我们使用的最多的,也是这个模式。
@Subscribe
的第二个参数——sticky
,是一个boolean
类型的参数,默认值是false
,表示不启用黏性特性,在这里也代表着黏性事件的意思。该特性代表什么意思呢?我们之前举的EventBus事件传递的例子时,都是先对订阅者(Subscriber)进行注册,然后再post事件的(顺序是:1.声明消息事件——>2.声明订阅者——>3.声明发布者)。那黏性事件的意义则是最后两步反过来:先post事件,后对订阅者注册(顺序是:1.声明消息事件——>2.声明发布者——>3.声明订阅者)。
沿用上面的代码,可以举一个简单的例子。这里想要使用黏性事件需要修改两处:
@Subscribe
上添加参数sticky = true
,表示开启黏性事件,例如@Subscribe(threadMode = ThreadMode.MAIN,sticky = true)
;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。接下来可以查看日志信息,就可以发现代码成功运行了。
@Subscribe
的第三个参数——priority
,是一个int
类型的参数,默认值是0
。这个参数比较好理解,就是指定订阅方法的优先级,值越大表示优先级越大。在某个事件被发布出来的时候,优先级较高的订阅方法会首先接收到事件。
这里有两个地方需要注意:
ThreadMode
参数的时候,它们的优先级才会与priority
指定的值一致;ThreadMode
参数为POSTING
的时候,它才能停止该事件的继续分发。AFL——Android框架学习