前面总结了几篇基础,在这过程中看着别人分享自定义 View、架构或者源码分析,看起来比我写的“高大上”多了,内心也有点小波动。

但是自己的水平自己清楚,基础不扎实画再多源码流程图也没什么意思,还是老老实实打好基础吧,技术这东西不能心急。

在复习了 Android 跨进程、多线程通信的几种方式的基础上,为了调节下心情,我们接下来一起来学以致用,分析分析一些有名的 Android 事件总线框架。

首先拿 EventBus 开刀!

读完本文你将了解:

  • EventBus 30 简介
  • EventBus 30 关键介绍
    • Subscribe 注解
    • 四种 ThreadMode
    • 粘性事件 Sticky Event
    • 优先级
    • 配置
    • AsyncExecutor
  • EventBus 的使用
  • 运行效果
  • 总结
  • 代码地址
  • Thanks

EventBus 3.0 简介

首先打开官方文档:http://greenrobot.org/eventbus/,一张嚣张的大图吸引了我的目光:

“Android 第一的事件库”,看起来很牛逼的样子啊,是不是真的这么牛呢?

首先看看介绍:

EventBus 是一个使用“观察者模式”的、松耦合的开源框架。它使用少量的几句代码就可以实现核心类之间的通讯,帮助我们简化代码、松依赖、加速开发。

观察者模式的确符合这个事件订阅、发布的场景,不了解这个模式的同学可以看看我之前写的两篇文章:

  • 观察者模式 : 一支穿云箭,千军万马来相见
  • 最熟悉的陌生人:ListView 中的观察者模式

在 EventBus 之前,有什么事件通信的方法:

  • startActivityForResult() 发出请求 , onActivityResult() 接收回调

    • Activity 多层嵌套调用,多次 startActivityForResult,繁琐 = 易出错
    • 嵌套 Fragement 调用,依赖外层 Activity 进行,还是繁琐
  • 回调
    • 子线程运行,生命周期不同步
  • 线程间通信

EventBus 提出是为了解决什么问题呢:

  • 简化关键组件间的通讯(Activity, Fragment, 线程通信)

    • 不管什么组件,都可以通讯
  • 避免复杂的依赖其他类,以及生命周期问题
    • 内存 泄漏?
  • 效率很高,优化了性能(重点关注)
  • 体积小
  • 被 一亿 多 app 使用,很吓人
  • 线程间通信,也可以设置订阅者优先级

EventBus 3.0 关键介绍

  1. 方便的注解

    • 使用 @Subscribe 注解描述方法,可以在编译时获取信息,不需要在运行反射
  2. 事件执行线程多种选择
    • 主线程
    • 子线程
  3. 事件的继承
    • 发送给订阅事件 A 的消息,也会发给订阅了 A 的子类的方法
    • 简化事件
  4. 不需要在 Application 或者其他部分配置,直接使用 EventBus.getDefault() 进行操作即可
    • EventBus.getDefault() 做了什么呢?
  5. 可配置的
    • 可以通过 Builder 配置 EventBus

http://greenrobot.org/eventbus/documentation/

@Subscribe 注解

EventBus 3.0 之后使用 @Subscribe 注解来描述一个注册的方法

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {//注册方法所在线程ThreadMode threadMode() default ThreadMode.POSTING;    //默认与发生方法在同一线程//是否为粘性事件boolean sticky() default false;//注册方法接收消息的优先级,当同一线程有多个注册方法时有效int priority() default 0;
}

四种 ThreadMode

@Subscribe(threadMode = ThreadMode.POSTING)
public void onMessage(MessageEvent event) {log(event.message);
}

EventBus 提供了四种 ThreadMode 值:

  1. POSTING

    • 订阅方法将和发送者执行在同一线程,默认的值
    • 订阅方法最好不要执行耗时操作,因为它可能会影响发送者的线程
    • 尤其是发送者在主线程的时候
  2. MAIN
    • 订阅方法执行在主线程
    • 用于更新 UI
    • 也要注意不执行耗时操作
  3. BACKGROUND
    • 订阅方法执行在单一的一个子线程
    • 如果发送者不要主线程,那订阅方法就会执行在发送者线程
    • 否则,使用一个单线程发送所有消息,所有消息串行执行
    • 也要注意避免耗时操作,影响到在同一线程的其他订阅方法
  4. ASYNC
    • 订阅方法会在一个新开的子线程(不是主线程、也不是发送者所在线程)执行(类似每次都新建一个线程)
    • 在执行耗时操作时需要使用这个,不会影响其他线程
    • 但是要控制数量,避免创建大量线程导致的开销
    • EventBus 使用线程池控制

粘性事件 Sticky Event

EventBus 还支持 粘性事件

  • 普通事件是说,先注册,然后发送事件才能收到
  • 而粘性事件,在发送事件之后再订阅该事件也能收到
  • 此外,粘性事件在发送后会被保存在内存中,每次进入都会去内存中获取最新的粘性事件数据,除非你手动解除注册

可以看到,粘性事件实现了对一些关键信息的缓存与更新,一般用于保存那些经常变化的信息,比如定位信息、传感器信息等等。

@Subscribe(sticky = true)
public void readStickyMsg(MessageEvent event){//...
}

优先级

我们可以设置一个注册方法的优先级:

@Subscribe(priority = 1);
public void onEvent(MessageEvent event) {...
}

默认优先级是 0,同一 线程(ThreadMode) 中优先级高的订阅者会先于低的接收到消息。

注意,只有在同一线程中的订阅者优先级才有作用。

有优先级后 ,高优先级的订阅者就可以取消消息往后的传播,这也符合生活和一些场景的需求。

这通过调用 cancelEventDelivery(event) 方法:

@Subscribe
public void onEvent(MessageEvent event){// Process the event...// Prevent delivery to other subscribersEventBus.getDefault().cancelEventDelivery(event) ;
}
Pasted from: http://greenrobot.org/eventbus/documentation/priorities-and-event-cancellation/

注意,只有 ThreadMode 为 POSTING 的订阅方法可以拦截消息。

优先级设置后,界面上显示不明显,因为 EventBus 的消息发送效率很高,但是如果打断点的话就可以看到,的确是高优先级的方法先被调用。

配置

我们一般调用 EventBus 直接使用 EventBus.getDefault() 方法即可。如果你想要自己配置 EventBus,它也支持。

通过 EventBus.builder() 方法我们得到 EventBusBuilder 对象,然后配置 EventBus 的各种属性,比如这样:

EventBus eventBus = EventBus.builder().logNoSubscriberMessages(false).sendNoSubscriberEvent(false).build();

EventBusBuilder 中可以配置的内容如下:

boolean logSubscriberExceptions = true;
boolean logNoSubscriberMessages = true;
boolean sendSubscriberExceptionEvent = true;
boolean sendNoSubscriberEvent = true;
boolean throwSubscriberException;
boolean eventInheritance = true;
boolean ignoreGeneratedIndex;
boolean strictMethodVerification;
ExecutorService executorService = DEFAULT_EXECUTOR_SERVICE;
List<Class<?>> skipMethodVerificationForClasses;
List<SubscriberInfoIndex> subscriberInfoIndexes;

可以看到,默认 EventBus 中以下属性是设为 true 的:

  • logSubscriberExceptions: 记录订阅异常
  • logNoSubscriberMessages :记录没有目标订阅者的消息
  • sendSubscriberExceptionEvent :订阅方法异常时发送 SubscriberExceptionEvent 事件
  • sendNoSubscriberEvent :发送的消息没有订阅者时发送 NoSubscriberEvent 事件
  • eventInheritance :事件允许继承

AsyncExecutor

EventBus 还提供了一个异步线程池 AsyncExecutor,使用它创建的线程,如果抛出异常,它会自动捕获,然后将异常信息包裹成一个 Event 发送出去。

AsyncExecutor 只是一个帮我们省去处理子线程抛出异常的工具类,不是 EventBus 的核心类。

我们可以在全局范围内调用 AsyncExector.create() 方法创建一个实例,然后调用 execute 方法执行异步任务,它的参数是 AsyncExecutor.RunnableEx

AsyncExecutor.create().execute(new AsyncExecutor.RunnableEx() {@Overridepublic void run() throws LoginException {// No need to catch any Exception (here: LoginException)remote.login();EventBus.getDefault().postSticky(new LoggedInEvent());}}
);
Pasted from: http://greenrobot.org/eventbus/documentation/asyncexecutor/

如果 RunnableEx 中抛出异常,AsyncExecutor 会捕获这个异常,然后包裹成 ThrowableFailureEvent 发送出去,注册了这个事件的方法将会得到调用。

订阅这个异常的例子如下:

@Subscribe(threadMode = ThreadMode.MAIN)
public void handleFailureEvent(ThrowableFailureEvent event) {// do something
}

EventBus 的使用

原文地址:http://blog.csdn.net/u011240877

下面我们演示一下 EventBus 的基本使用。

首先 gradle 引入依赖 EventBus 以及它的注解处理器:

根目录下的 gradle 的 dependencies 中添加 apt 依赖:

classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

app 目录下的 gradle 中添加:

apply plugin: 'android-apt'
apt {arguments {eventBusIndex "top.shixinzhang.MyEventBusIndex"}
}
dependencies {compile 'org.greenrobot:eventbus:3.0.0'apt 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}

其中 eventBusIndex 你可以随意设置包名和文件名。

然后就可以发车了!

创建要订阅的事件:

public class MessageEvent implements Serializable {private static final long serialVersionUID = -1371779234999786464L;private String message;private String time;public MessageEvent(String message, String time) {this.message = message;this.time = time;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}public String getTime() {return time;}public void setTime(String time) {this.time = time;}
}

然后创建一个注册 EventBus 页面:

public class EventBusRegisterActivity extends AppCompatActivity {@BindView(R.id.tv_event_info)TextView mTvEventInfo;@BindView(R.id.tv_event_info_priority)TextView mTvEventInfoPriority;@BindView(R.id.btn_cancel_delivery)Button mBtnCancelDelivery;private boolean mCancelDelivery;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_event_bus_test);ButterKnife.bind(this);}/*** 订阅方法,设置线程为 POSTING,优先级为 5* @param event*/@Subscribe(threadMode = ThreadMode.POSTING, priority = 5)public void readMessageFirst(MessageEvent event) {mTvEventInfoPriority.setText("\n" + event.getMessage() + ", " + event.getTime());if (mCancelDelivery) {EventBus.getDefault().cancelEventDelivery(event);}}@Subscribe(threadMode = ThreadMode.POSTING, priority = 1)public void readMessage(MessageEvent event) {mTvEventInfo.setText("\n" + event.getMessage() + ", " + event.getTime());}/*** 为了演示效果,我们在按钮点击事件中注册事件*/@OnClick(R.id.btn_register_normal)public void registerNormal() {if (!EventBus.getDefault().isRegistered(this)) {EventBus.getDefault().register(this);}}@OnClick(R.id.btn_unregister_normal)public void unregisterNormal() {EventBus.getDefault().unregister(this);}@OnClick(R.id.btn_cancel_delivery)public void cancelDelivery() {mCancelDelivery = !mCancelDelivery;mBtnCancelDelivery.setText(mCancelDelivery ? "已拦截事件传递" : "点击拦截事件传递");}@OnClick(R.id.btn_jump_next)public void jumpToNext() {startActivity(new Intent(this, EventBusPosterActivity.class));}@OnClick(R.id.btn_jump_sticky)public void jumpToSticky() {startActivity(new Intent(this, EventBusStickyActivity.class));}@Overrideprotected void onDestroy() {super.onDestroy();}
}

这个页面的功能如图所示:

  • 有两个优先级不同的订阅方法,有两个按钮用于注册和解除注册订阅
  • 一个用于高优先级订阅方法拦截事件向后传递的按钮
  • 还有一个按钮用于跳转到发送事件页面中,另一个按钮用于跳转到粘性事件订阅页面。

先看下粘性事件订阅页面:

public class EventBusStickyActivity extends AppCompatActivity {@BindView(R.id.tv_event_info)TextView mTvEventInfo;@BindView(R.id.btn_register_sticky_event)Button mBtnRegisterStickyEvent;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_event_bus_sticky);ButterKnife.bind(this);}@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)public void readStickyMsg(MessageEvent event){mTvEventInfo.setText(mTvEventInfo.getText() + "\n" + event.getMessage() + ", " + event.getTime());}@OnClick(R.id.btn_jump_next)public void jumpToNext(){startActivity(new Intent(this, EventBusPosterActivity.class));}@OnClick(R.id.btn_register_sticky_event)public void registerSticky(){EventBus.getDefault().register(this);}@OnClick(R.id.btn_unregister_sticky_event)public void unregisterSticky(){EventBus.getDefault().removeStickyEvent(MessageEvent.class);}@Overrideprotected void onDestroy() {super.onDestroy();}
}

功能如图所示:

  • 一个显示订阅方法接收信息的文字
  • 一个点击后跳转到发送事件页面的按钮
  • 两个用于注册和解除注册粘性事件的按钮

好,接着再看一下发送事件页面:

public class EventBusPosterActivity extends AppCompatActivity {@BindView(R.id.tv_event_info)TextView mTvEventInfo;@BindView(R.id.btn_post_normal)Button mBtnPostNormal;@BindView(R.id.btn_post_sticky_event)Button mBtnPostStickyEvent;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_event_bus_post);ButterKnife.bind(this);}@OnClick(R.id.btn_post_normal)public void postNormalEvent(){EventBus.getDefault().post(new MessageEvent("另外界面的主线程,普通消息", DateUtils.getCurrentTime()));finish();}@OnClick(R.id.btn_post_sticky_event)public void postStickyEvent(){EventBus.getDefault().postSticky(new MessageEvent("另外界面的主线程,粘性消息", DateUtils.getCurrentTime()));finish();}
}

这个页面很简单,两个发送普通事件和粘性事件的按钮。

运行效果

演示下普通事件的注册、解除注册、以及高优先级拦截事件的运行效果

从上面的动图可以看到:

  • 注册事件监听后,订阅的方法就可在 EventBus 发送事件后收到调用
  • 优先级高的会比低的先收到调用,界面上显示不明显,但是打断点就可以看到先后的调用顺序
  • 优先级高的拦截事件后,低优先级的就不会收到新的事件
  • 解除注册后,也不会收到新的事件

接着看一下 粘性事件的注册、解除注册的效果

从上面的动图可以看到:

  • 在发送粘性事件之后注册粘性监听,也可以得到消息
  • 发送的粘性事件会被缓存起来,以后只要注册这个事件就会得到消息
  • 当发送新的粘性事件后,订阅粘性事件的方法会更新到最新的值
  • 解除粘性事件的注册后,就不会再去获取值

可以看到,粘性事件的确适用于获取一些需要缓存的关键信息,比如城市、天气等等。

总结

这篇文章介绍了 EventBus 3.0 的主要特点和使用,可以发现,它的确很容易使用,目前能想到的事件通讯基本都可以满足,代码耦合也不严重。

而 EventBus 的缺点就是,会导致业务逻辑比较分散,不直观。尤其是在运行时触发多种事件、多个订阅方法时。不过这应该是解耦的双刃剑吧。

下一篇文章我们分析下 EventBus 的核心功能是如何实现的。

有些之前不了解的内容,在写了 Sample 之后才发现错在哪儿,知行合一,知行合一啊!

代码地址

Thanks

http://greenrobot.org/eventbus/
http://greenrobot.org/eventbus/documentation/
https://github.com/greenrobot/EventBus
https://segmentfault.com/a/1190000005089229
http://www.jianshu.com/p/f057c460c77e
http://liuwangshu.cn/application/eventbus/1-eventbus.html

Android 框架学习1:EventBus 3.0 的特点与如何使用相关推荐

  1. Android 框架学习4:一次读懂热门图片框架 Picasso 源码及流程

    Android 框架学习1:EventBus 3.0 的特点与如何使用 Android 框架学习2:源码分析 EventBus 3.0 如何实现事件总线 Android 框架学习3:我从 EventB ...

  2. Android 框架学习2:源码分析 EventBus 3.0 如何实现事件总线

    Go beyond yourself rather than beyond others. 上篇文章 深入理解 EventBus 3.0 之使用篇 我们了解了 EventBus 的特性以及如何使用,这 ...

  3. Android框架之路——EventBus的使用

    一.简介 EventBus是由greenrobot 组织贡献的一个Android事件发布/订阅轻量级框架.EventBus是一个Android端优化的publish/subscribe消息总线,简化了 ...

  4. android http最新框架,Android框架学习笔记02AndroidAsycHttp框架

    上一篇中我们介绍了OkHttp3.0框架的基本使用方法,这一篇我们学习一下Android的另外一个网络请求框架--AsyncHttpClient框架.Asynchttpclient框架是一个开源的异步 ...

  5. 从零开始--系统深入学习android(实践-让我们开始写代码-Android框架学习-2.service)...

    第2章 Services Service是一个长期运行在后台,并不提供用户界面的应用程序组件.其他应用程序的组件可以启动一个service,并且即使用户切换到另一个应用程序,service也可以在后台 ...

  6. 从零开始--系统深入学习android(实践-让我们开始写代码-Android框架学习-7.App Widgets)...

    第7章 App Widgets App Widgets是一个应用程序的微型视图,可以嵌入到其他应用程序(如主屏幕)并且能够定期更新.你可以发布一个应用程序的App Widget,而这些视图称为窗口的用 ...

  7. Android 框架学习5:微信热修复框架 Tinker 从使用到 patch 加载、生成、合成原理分析

    这篇文章是基于内部分享的逐字稿内容整理的,现在比较喜欢写逐字稿,方便整理成文章. 文章目录 目录 Tinker 介绍 使用 TinkerApplicaition ``SampleApplicaitio ...

  8. 【转】EventBus 3.0使用详解

    原文:https://www.jianshu.com/p/f9ae5691e1bb 01 前言 当我们进行项目开发的时候,往往是需要应用程序的各组件.组件与后台线程间进行通信,比如在子线程中进行请求数 ...

  9. Java日志框架学习笔记

    Java日志框架学习笔记 文章目录 0 主流Java日志框架 1 log4j 1.1 理论知识 1.1.1 Loggers日志记录器 1.1.2 Appenders输出端 1.1.3 Layout日志 ...

最新文章

  1. java都市男人心痒痒_说的男人心痒痒的情话 让男人心痒痒的话,谁能帮我弄几句呀?...
  2. python好找工作么-python和django掌握到什么水平才好找工作?
  3. 关于物理像素/逻辑像素
  4. python—多线程之数据混乱问题
  5. 怎样查看rpm安装包的安装路径
  6. linux-3.4.2移植到FL2440上(一)--只是基本移植
  7. python处理时间和日期_python时间和日期的处理
  8. 视觉设计基础知识整理
  9. 上市前夕再陷版权纠纷,快手面临的风险却不止于此
  10. 面试技巧——程序员简历模板
  11. 从矩阵谱分解到矩形的最少正方形剖分
  12. 六、容器(高琪java300集+java从入门到精通笔记)
  13. coursera “the media could not be loaded either because the server or network falled...“解决办法
  14. 北京物资学院计算机考研资料汇总
  15. 打通现实世界,工程师通过DeFi一天内完成抵押借贷偿还房屋贷款
  16. @PersistenceContext和@Autowired在EntityManager上应用的区别。
  17. 极客爱情 2.0.1| 从你的编程世界路过
  18. Python-__getattr__与__getattribute__
  19. Android Studio update失败问题 some conflicts were found in the installation area
  20. (二)、Apache doris编译

热门文章

  1. 通过股票代码识别所属板块(20190730)
  2. 2022-2028年中国水玻璃行业竞争格局分析及投资发展研究报告
  3. 第8章第17节:制作企业宣传册的公司团队第四页面 [PowerPoint精美幻灯片实战教程]
  4. 大数据分析练习-第八届泰迪杯A题-基于数据挖掘的上市公司高送转预测
  5. 基础IT技术学习资料300篇,欢迎一键收藏
  6. java.io.IOException: Prepare failed.: status=0x1
  7. 蓝牙 韦东山_韦东山生活实例演绎法讲解蓝牙
  8. 电话号码的正则表达式
  9. 20180508----01:15
  10. 币泳金:理安全的储存数字货币,冷钱包与热钱包的管理