(转载)http://liuling123.com/2016/01/EventBus-explain.html

概述

EventBus是针一款对Android的发布/订阅事件总线。它可以让我们很轻松的实现在Android各个组件之间传递消息,并且代码的可读性更好,耦合度更低。

如何使用

(1)首先需要定义一个消息类,该类可以不继承任何基类也不需要实现任何接口。如:

  1. public class MessageEvent {
  2. ......
  3. }

(2)在需要订阅事件的地方注册事件

  1. EventBus.getDefault().register(this);

(3)产生事件,即发送消息

  1. EventBus.getDefault().post(messageEvent);

(4)处理消息

  1. @Subscribe(threadMode = ThreadMode.PostThread)
  2. public void XXX(MessageEvent messageEvent) {
  3. ...
  4. }

在3.0之前,EventBus还没有使用注解方式。消息处理的方法也只能限定于onEvent、onEventMainThread、onEventBackgroundThread和onEventAsync,分别代表四种线程模型。而在3.0之后,消息处理的方法可以随便取名,但是需要添加一个注解@Subscribe,并且要指定线程模型(默认为PostThread),四种线程模型,下面会讲到。
注意,事件处理函数的访问权限必须为public,否则会报异常。

(5)取消消息订阅

  1. EventBus.getDefault().unregister(this);

有何优点

采用消息发布/订阅的一个很大的优点就是代码的简洁性,并且能够有效地降低消息发布者和订阅者之间的耦合度。
举个例子,比如有两个界面,ActivityA和ActivityB,从ActivityA界面跳转到ActivityB界面后,ActivityB要给ActivityA发送一个消息,ActivityA收到消息后在界面上显示出来。我们最先想到的方法就是使用广播,使用广播实现此需求的代码如下:
首先需要在ActivityA中定义一个广播接收器:

  1. public class MessageBroadcastReceiver extends BroadcastReceiver {
  2. @Override
  3. public void onReceive(Context context, Intent intent) {
  4. mMessageView.setText("Message from SecondActivity:" + intent.getStringExtra("message"));
  5. }
  6. }

还需要在onCreate()方法中注册广播接收器:

  1. @Override
  2. protected void onCreate(Bundle savedInstanceState) {
  3. super.onCreate(savedInstanceState);
  4. setContentView(R.layout.activity_main);
  5. //注册事件
  6. EventBus.getDefault().register(this);
  7. //注册广播
  8. IntentFilter intentFilter = new IntentFilter("message_broadcast");
  9. mBroadcastReceiver = new MessageBroadcastReceiver();
  10. registerReceiver(mBroadcastReceiver, intentFilter);
  11. ......
  12. }

然后在onDestory()方法中取消注册广播接收器:

  1. @Override
  2. protected void onDestroy() {
  3. super.onDestroy();
  4. ......
  5. //取消广播注册
  6. unregisterReceiver(mBroadcastReceiver);
  7. }

最后我们需要在ActivityB界面中发送广播消息:

  1. findViewById(R.id.send_broadcast).setOnClickListener(new View.OnClickListener() {
  2. @Override
  3. public void onClick(View v) {
  4. String message = mMessageET.getText().toString();
  5. if(TextUtils.isEmpty(message)) {
  6. message = "defaule message";
  7. }
  8. Intent intent = new Intent();
  9. intent.setAction("message_broadcast");
  10. intent.putExtra("message", message);
  11. sendBroadcast(intent);
  12. }
  13. });

看着上面的实现代码,感觉也没什么不妥,挺好的!下面对比看下使用EventBus如何实现。
根据文章最前面所讲的EventBus使用步骤,首先我们需要定义一个消息事件类:

  1. public class MessageEvent {
  2. private String message;
  3. public MessageEvent(String message) {
  4. this.message = message;
  5. }
  6. public String getMessage() {
  7. return message;
  8. }
  9. public void setMessage(String message) {
  10. this.message = message;
  11. }
  12. }

在ActivityA界面中我们首先需要注册订阅事件:

  1. @Override
  2. protected void onCreate(Bundle savedInstanceState) {
  3. super.onCreate(savedInstanceState);
  4. setContentView(R.layout.activity_main);
  5. //注册事件
  6. EventBus.getDefault().register(this);
  7. ......
  8. }

然后在onDestory()方法中取消订阅:

  1. @Override
  2. protected void onDestroy() {
  3. super.onDestroy();
  4. //取消事件注册
  5. EventBus.getDefault().unregister(this);
  6. }

当然还要定义一个消息处理的方法:

  1. @Subscribe(threadMode = ThreadMode.MainThread)
  2. public void onShowMessageEvent(MessageEvent messageEvent) {
  3. mMessageView.setText("Message from SecondActivity:" + messageEvent.getMessage());
  4. }

至此,消息订阅者我们已经定义好了,我们还需要在ActivityB中发布消息:

  1. findViewById(R.id.send).setOnClickListener(new View.OnClickListener() {
  2. @Override
  3. public void onClick(View v) {
  4. String message = mMessageET.getText().toString();
  5. if(TextUtils.isEmpty(message)) {
  6. message = "defaule message";
  7. }
  8. EventBus.getDefault().post(new MessageEvent(message));
  9. }
  10. });

对比代码一看,有人会说了,这尼玛有什么区别嘛!说好的简洁呢?哥们,别着急嘛!我这里只是举了个简单的例子,仅仅从该例子来看,EventBus的优势没有体现出来。现在我将需求稍微改一下,ActivityA收到消息后,需要从网络服务器获取数据并将数据展示出来。如果使用广播,ActivityA中广播接收器代码应该这么写:

  1. public class MessageBroadcastReceiver extends BroadcastReceiver {
  2. @Override
  3. public void onReceive(Context context, Intent intent) {
  4. new Thread(new Runnable() {
  5. @Override
  6. public void run() {
  7. //从服务器上获取数据
  8. ......
  9. runOnUiThread(new Runnable() {
  10. @Override
  11. public void run() {
  12. //将获取的数据展示在界面上
  13. ......
  14. }
  15. });
  16. }
  17. }).start();
  18. }
  19. }

看到这段代码,不知道你何感想,反正我是看着很不爽,嵌套层次太多,完全违反了Clean Code的原则。那使用EventBus来实现又是什么样呢?我们看一下。

  1. @Subscribe(threadMode = ThreadMode.BackgroundThread)
  2. public void onGetDataEvent(MessageEvent messageEvent) {
  3. //从服务器上获取数据
  4. ......
  5. EventBus.getDefault().post(new ShowMessageEvent());
  6. }
  7. @Subscribe(threadMode = ThreadMode.MainThread)
  8. public void onShowDataEvent(ShowMessageEvent showMessageEvent) {
  9. //将获取的数据展示在界面上
  10. ......
  11. }

对比一下以上两段代码就能很明显的感觉到EventBus的优势,代码简洁、层次清晰,大大提高了代码的可读性和可维护性。我这只是简单的加了一个小需求而已,随着业务越来越复杂,使用EventBus的优势愈加明显。

常用API介绍

线程模型

在EventBus的事件处理函数中需要指定线程模型,即指定事件处理函数运行所在的想线程。在上面我们已经接触到了EventBus的四种线程模型。那他们有什么区别呢?
在EventBus中的观察者通常有四种线程模型,分别是PostThread(默认)、MainThread、BackgroundThread与Async。

  • PostThread:如果使用事件处理函数指定了线程模型为PostThread,那么该事件在哪个线程发布出来的,事件处理函数就会在这个线程中运行,也就是说发布事件和接收事件在同一个线程。在线程模型为PostThread的事件处理函数中尽量避免执行耗时操作,因为它会阻塞事件的传递,甚至有可能会引起ANR。
  • MainThread:如果使用事件处理函数指定了线程模型为MainThread,那么不论事件是在哪个线程中发布出来的,该事件处理函数都会在UI线程中执行。该方法可以用来更新UI,但是不能处理耗时操作。
  • BackgroundThread:如果使用事件处理函数指定了线程模型为BackgroundThread,那么如果事件是在UI线程中发布出来的,那么该事件处理函数就会在新的线程中运行,如果事件本来就是子线程中发布出来的,那么该事件处理函数直接在发布事件的线程中执行。在此事件处理函数中禁止进行UI更新操作。
  • Async:如果使用事件处理函数指定了线程模型为Async,那么无论事件在哪个线程发布,该事件处理函数都会在新建的子线程中执行。同样,此事件处理函数中禁止进行UI更新操作。

为了验证以上四个方法,我写了个小例子。

  1. @Subscribe(threadMode = ThreadMode.PostThread)
  2. public void onMessageEventPostThread(MessageEvent messageEvent) {
  3. Log.e("PostThread", Thread.currentThread().getName());
  4. }
  5. @Subscribe(threadMode = ThreadMode.MainThread)
  6. public void onMessageEventMainThread(MessageEvent messageEvent) {
  7. Log.e("MainThread", Thread.currentThread().getName());
  8. }
  9. @Subscribe(threadMode = ThreadMode.BackgroundThread)
  10. public void onMessageEventBackgroundThread(MessageEvent messageEvent) {
  11. Log.e("BackgroundThread", Thread.currentThread().getName());
  12. }
  13. @Subscribe(threadMode = ThreadMode.Async)
  14. public void onMessageEventAsync(MessageEvent messageEvent) {
  15. Log.e("Async", Thread.currentThread().getName());
  16. }

分别使用上面四个方法订阅同一事件,打印他们运行所在的线程。首先我们在UI线程中发布一条MessageEvent的消息,看下日志打印结果是什么。

  1. findViewById(R.id.send).setOnClickListener(new View.OnClickListener() {
  2. @Override
  3. public void onClick(View v) {
  4. Log.e("postEvent", Thread.currentThread().getName());
  5. EventBus.getDefault().post(new MessageEvent());
  6. }
  7. });

打印结果如下:

  1. 2689-2689/com.lling.eventbusdemo E/postEvent main
  2. 2689-2689/com.lling.eventbusdemo E/PostThread main
  3. 2689-3064/com.lling.eventbusdemo E/Async pool-1-thread-1
  4. 2689-2689/com.lling.eventbusdemo E/MainThread main
  5. 2689-3065/com.lling.eventbusdemo E/BackgroundThread pool-1-thread-2

从日志打印结果可以看出,如果在UI线程中发布事件,则线程模型为PostThread的事件处理函数也执行在UI线程,与发布事件的线程一致。线程模型为Async的事件处理函数执行在名字叫做pool-1-thread-1的新的线程中。而MainThread的事件处理函数执行在UI线程,BackgroundThread的时间处理函数执行在名字叫做pool-1-thread-2的新的线程中。

我们再看看在子线程中发布一条MessageEvent的消息时,会有什么样的结果。

  1. findViewById(R.id.send).setOnClickListener(new View.OnClickListener() {
  2. @Override
  3. public void onClick(View v) {
  4. new Thread(new Runnable() {
  5. @Override
  6. public void run() {
  7. Log.e("postEvent", Thread.currentThread().getName());
  8. EventBus.getDefault().post(new MessageEvent());
  9. }
  10. }).start();
  11. }
  12. });

打印结果如下:

  1. 3468-3945/com.lling.eventbusdemo E/postEvent Thread-125
  2. 3468-3945/com.lling.eventbusdemo E/PostThread Thread-125
  3. 3468-3945/com.lling.eventbusdemo E/BackgroundThread Thread-125
  4. 3468-3946/com.lling.eventbusdemo E/Async pool-1-thread-1
  5. 3468-3468/com.lling.eventbusdemo E/MainThread main

从日志打印结果可以看出,如果在子线程中发布事件,则线程模型为PostThread的事件处理函数也执行在子线程,与发布事件的线程一致(都是Thread-125)。BackgroundThread事件模型也与发布事件在同一线程执行。Async则在一个名叫pool-1-thread-1的新线程中执行。MainThread还是在UI线程中执行。

上面一个例子充分验证了指定不同线程模型的事件处理方法执行所在的线程。

黏性事件

除了上面讲的普通事件外,EventBus还支持发送黏性事件。何为黏性事件呢?简单讲,就是在发送事件之后再订阅该事件也能收到该事件,跟黏性广播类似。具体用法如下:

订阅黏性事件:

  1. EventBus.getDefault().register(StickyModeActivity.this);

黏性事件处理函数:

  1. @Subscribe(sticky = true)
  2. public void XXX(MessageEvent messageEvent) {
  3. ......
  4. }

发送黏性事件:

  1. EventBus.getDefault().postSticky(new MessageEvent("test"));

处理消息事件以及取消订阅和上面方式相同。

看个简单的黏性事件的例子,为了简单起见我这里就在一个Activity里演示了。

Activity代码:

  1. public class StickyModeActivity extends AppCompatActivity {
  2. int index = 0;
  3. @Override
  4. protected void onCreate(Bundle savedInstanceState) {
  5. super.onCreate(savedInstanceState);
  6. setContentView(R.layout.activity_sticky_mode);
  7. findViewById(R.id.post).setOnClickListener(new View.OnClickListener() {
  8. @Override
  9. public void onClick(View v) {
  10. EventBus.getDefault().postSticky(new MessageEvent("test" + index++));
  11. }
  12. });
  13. findViewById(R.id.regist).setOnClickListener(new View.OnClickListener() {
  14. @Override
  15. public void onClick(View v) {
  16. EventBus.getDefault().registerSticky(StickyModeActivity.this);
  17. }
  18. });
  19. findViewById(R.id.unregist).setOnClickListener(new View.OnClickListener() {
  20. @Override
  21. public void onClick(View v) {
  22. EventBus.getDefault().unregister(StickyModeActivity.this);
  23. }
  24. });
  25. }
  26. @Subscribe(threadMode = ThreadMode.PostThread, sticky = true)
  27. public void onMessageEventPostThread(MessageEvent messageEvent) {
  28. Log.e("PostThread", messageEvent.getMessage());
  29. }
  30. @Subscribe(threadMode = ThreadMode.MainThread, sticky = true)
  31. public void onMessageEventMainThread(MessageEvent messageEvent) {
  32. Log.e("MainThread", messageEvent.getMessage());
  33. }
  34. @Subscribe(threadMode = ThreadMode.BackgroundThread, sticky = true)
  35. public void onMessageEventBackgroundThread(MessageEvent messageEvent) {
  36. Log.e("BackgroundThread", messageEvent.getMessage());
  37. }
  38. @Subscribe(threadMode = ThreadMode.Async, sticky = true)
  39. public void onMessageEventAsync(MessageEvent messageEvent) {
  40. Log.e("Async", messageEvent.getMessage());
  41. }
  42. }

布局代码activity_sticky_mode.xml:

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
  3. android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
  4. android:paddingRight="@dimen/activity_horizontal_margin"
  5. android:paddingTop="@dimen/activity_vertical_margin"
  6. android:paddingBottom="@dimen/activity_vertical_margin"
  7. android:orientation="vertical"
  8. tools:context="com.lling.eventbusdemo.StickyModeActivity">
  9. <Button
  10. android:id="@+id/post"
  11. android:layout_width="wrap_content"
  12. android:layout_height="wrap_content"
  13. android:text="Post"/>
  14. <Button
  15. android:id="@+id/regist"
  16. android:layout_width="wrap_content"
  17. android:layout_height="wrap_content"
  18. android:text="Regist"/>
  19. <Button
  20. android:id="@+id/unregist"
  21. android:layout_width="wrap_content"
  22. android:layout_height="wrap_content"
  23. android:text="UnRegist"/>
  24. </LinearLayout>

代码很简单,界面上三个按钮,一个用来发送黏性事件,一个用来订阅事件,还有一个用来取消订阅的。首先在未订阅的情况下点击发送按钮发送一个黏性事件,然后点击订阅,会看到日志打印结果如下:

  1. 15246-15246/com.lling.eventbusdemo E/PostThread test0
  2. 15246-15391/com.lling.eventbusdemo E/Async test0
  3. 15246-15246/com.lling.eventbusdemo E/MainThread test0
  4. 15246-15393/com.lling.eventbusdemo E/BackgroundThread test0

这就是粘性事件,能够收到订阅之前发送的消息。但是它只能收到最新的一次消息,比如说在未订阅之前已经发送了多条黏性消息了,然后再订阅只能收到最近的一条消息。这个我们可以验证一下,我们连续点击5次POST按钮发送5条黏性事件,然后再点击REGIST按钮订阅,打印结果如下:

  1. 6980-6980/com.lling.eventbusdemo E/PostThread test4
  2. 6980-6980/com.lling.eventbusdemo E/MainThread test4
  3. 6980-7049/com.lling.eventbusdemo E/Async test4
  4. 6980-7048/com.lling.eventbusdemo E/BackgroundThread test4

由打印结果可以看出,确实是只收到最近的一条黏性事件。

好了,EventBus的使用暂时分析到这里,例子代码从这里获取。下一讲将讲解EventBus的源码。

本文首发:http://liuling123.com/2016/01/EventBus-explain.html

文/Lauren_Liuling(简书作者)
原文链接:http://www.jianshu.com/p/da9e193e8b03
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

(转载)EventBus使用详解的更多相关文章

  1. 安卓高级EventBus使用详解

    我本来想写但是在网上看了下感觉写得不如此作者写得好:http://www.jianshu.com/p/da9e193e8b03 前言:EventBus出来已经有一段时间了,github上面也有很多开源 ...

  2. [转载]Linux 命令详解:./configure、make、make install 命令

    [转载]Linux 命令详解:./configure.make.make install 命令 来源:https://www.cnblogs.com/tinywan/p/7230039.html 这些 ...

  3. [转载] 多图详解Spring框架的设计理念与设计模式

    转载自http://developer.51cto.com/art/201006/205212_all.htm Spring作为现在最优秀的框架之一,已被广泛的使用,51CTO也曾经针对Spring框 ...

  4. 【转载】log4j详解使用

    log4j详解 日志论    在应用程序中输出日志有有三个目的:(1)监视代码中变量的变化情况,把数据周期性地记录到文件中供其他应用进行统计分析工作. (2)跟踪代码运行进轨迹,作为日后审计的依据.  ...

  5. 【转载】GitHub详解

    原文:GitHub详解 GitHub详解 GitHub 是一个共享虚拟主机服务,用于存放使用Git版本控制的软件代码和内容项目.它由GitHub公司(曾称Logical Awesome)的开发者Chr ...

  6. [转载]ssget 用法详解 by yxp

    总结得很好的ssget用法.....如此好文,必须转载. 原文地址: http://blog.csdn.net/yxp_xa/article/details/72229202 ssget 用法详解 b ...

  7. (转载)实例详解Android快速开发工具类总结

    实例详解Android快速开发工具类总结 作者:LiJinlun 字体:[增加 减小] 类型:转载 时间:2016-01-24我要评论 这篇文章主要介绍了实例详解Android快速开发工具类总结的相关 ...

  8. [转载]Fiddler界面详解

    转载地址:http://www.cnblogs.com/chengchengla1990/p/5681775.html Statistics 页签 完整页签如下图: Statistics 页签显示当前 ...

  9. (转载)UITableView使用详解

    在开发iphone的应用时基本上都要用到UITableView,这里讲解一下UITableView的使用方法及代理的调用情况 UITableView使用详解 - (void)viewDidLoad { ...

  10. [转载]App.Config详解及读写操作

    App.Config详解 应用程序配置文件是标准的 XML 文件,XML 标记和属性是区分大小写的.它是可以按需要更改的,开发人员可以使用配置文件来更改设置,而不必重编译应用程序.配置文件的根节点是c ...

随机推荐

  1. ubuntu bcompare 安装

    Terminal Install wget http://www.scootersoftware.com/bcompare-4.2.8.23479_amd64.deb sudo apt-get upd ...

  2. setData 设置某个数组或者数组对象的值

    demo:list是一个对象数组,设置list数组某个对象的值 下标是动态index的value值 let item='list['+index+'].value'; this.setData({ [ ...

  3. CodeForces 1110H. Modest Substrings

    题目简述:给定$1 \leq l \leq r \leq 10^{800}$,求一个长度为$n \leq 2000$的数字串$s$,其含有最多的[好]子串.一个串$s$是[好]的,如果将其看做数字时无 ...

  4. Flutter实战视频-移动电商-06.Dio基础_Get请求和动态组件协作

    博客地址: https://jspang.com/post/FlutterShop.html#toc-0ee 编写页面代码 创建动态组件HomePage,原来的代码是静态的我们这里就去掉就可以了. 然 ...

  5. 访问web-inf下jsp资源的几种方式

    转自:http://blog.csdn.NET/eidolon8/article/details/7050114 方法一: 本来WEB-INF中的jsp就是无法通过地址栏访问的,所以安全. 如果说你要 ...

  6. hihocoder #1608 : Jerry的奶酪(状压DP)

    传送门 题意 分析 设dp[i][j]为在i状态下当前在第j个奶酪的最小费用 转移方程:dp[(1<<k)|i][k]=dp[i][j]+d[j][k] 预处理出每个奶酪之间的距离,加入起 ...

  7. Lightoj1028 【数学-乘法原理】

    题意: 给你一个数,问你有多少种进制对n的表示,存在后导零: 比如30:用3进制表示: 1010 思路: 我们发现,就是一个数的约数就能对n表示最后存在后导零: 计算[2 ,n]之间的n的约数个数. ...

  8. hdu1847(sg函数&yy)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1847 题意:中文题诶- 思路:直接sg函数打表即可,观察打表的结果发现是有规律的,sg函数的值只为0, ...

  9. Codevs 3112 二叉树计数

    3112 二叉树计数 题目描述 Description 一个有n个结点的二叉树总共有多少种形态 输入描述 Input Description 读入一个正整数n 输出描述 Output Descript ...

  10. Java 定时任务(转)

    转自 http://www.cnblogs.com/chenssy/p/3788407.html 在我们编程过程中如果需要执行一些简单的定时任务,无须做复杂的控制,我们可以考虑使用JDK中的Timer ...