转载自:http://blog.csdn.net/u011240877/article/details/73015939
前面总结了几篇基础,在这过程中看着别人分享自定义 View、架构或者源码分析,看起来比我写的“高大上”多了,内心也有点小波动。
但是自己的水平自己清楚,基础不扎实画再多源码流程图也没什么意思,还是老老实实打好基础吧,技术这东西不能心急。
在复习了 Android 跨进程、多线程通信的几种方式的基础上,为了调节下心情,我们接下来一起来学以致用,分析分析一些有名的 android 事件总线框架。
首先拿 EventBus
开刀!
读完本文你将了解:
EventBus 3.0 简介
首先打开官方文档:http://greenrobot.org/eventbus/,一张嚣张的大图吸引了我的目光:
“Android 第一的事件库”,看起来很牛逼的样子啊,是不是真的这么牛呢?
首先看看介绍:
EventBus 是一个使用“观察者模式”的、松耦合的开源框架。它使用少量的几句代码就可以实现核心类之间的通讯,帮助我们简化代码、松依赖、加速开发。
观察者模式的确符合这个事件订阅、发布的场景,不了解这个模式的同学可以看看我之前写的两篇文章:
在 EventBus 之前,有什么事件通信的方法:
startActivityForResult()
发出请求 ,onActivityResult()
接收回调
- Activity 多层嵌套调用,多次 startActivityForResult,繁琐 = 易出错
- 嵌套 Fragement 调用,依赖外层 Activity 进行,还是繁琐
- 回调
- 子线程运行,生命周期不同步
- 线程间通信
EventBus 提出是为了解决什么问题呢:
- 简化关键组件间的通讯(Activity, Fragment, 线程通信)
- 不管什么组件,都可以通讯
- 避免复杂的依赖其他类,以及生命周期问题
- 内存 泄漏?
- 效率很高,优化了性能(重点关注)
- 体积小
- 被 一亿 多 app 使用,很吓人
- 线程间通信,也可以设置订阅者优先级
EventBus 3.0 关键介绍
- 方便的注解
- 使用
@Subscribe
注解描述方法,可以在编译时获取信息,不需要在运行反射
- 使用
- 事件执行线程多种选择
- 主线程
- 子线程
- 事件的继承
- 发送给订阅事件 A 的消息,也会发给订阅了 A 的子类的方法
- 简化事件
- 不需要在 Application 或者其他部分配置,直接使用
EventBus.getDefault()
进行操作即可
EventBus.getDefault()
做了什么呢?
- 可配置的
- 可以通过
Builder
配置 EventBus
- 可以通过
http://greenrobot.org/eventbus/documentation/
@Subscribe 注解
EventBus 3.0 之后使用 @Subscribe
注解来描述一个注册的方法
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
四种 ThreadMode
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
EventBus 提供了四种 ThreadMode 值:
- POSTING
- 订阅方法将和发送者执行在同一线程,默认的值
- 订阅方法最好不要执行耗时操作,因为它可能会影响发送者的线程
- 尤其是发送者在主线程的时候
- MAIN
- 订阅方法执行在主线程
- 用于更新 UI
- 也要注意不执行耗时操作
- BACKGROUND
- 订阅方法执行在单一的一个子线程
- 如果发送者不要主线程,那订阅方法就会执行在发送者线程
- 否则,使用一个单线程发送所有消息,所有消息串行执行
- 也要注意避免耗时操作,影响到在同一线程的其他订阅方法
- ASYNC
- 订阅方法会在一个新开的子线程(不是主线程、也不是发送者所在线程)执行(类似每次都新建一个线程)
- 在执行耗时操作时需要使用这个,不会影响其他线程
- 但是要控制数量,避免创建大量线程导致的开销
- EventBus 使用线程池控制
粘性事件 Sticky Event
EventBus 还支持 粘性事件。
- 普通事件是说,先注册,然后发送事件才能收到
- 而粘性事件,在发送事件之后再订阅该事件也能收到
- 此外,粘性事件在发送后会被保存在内存中,每次进入都会去内存中获取最新的粘性事件数据,除非你手动解除注册
可以看到,粘性事件实现了对一些关键信息的缓存与更新,一般用于保存那些经常变化的信息,比如定位信息、传感器信息等等。
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
优先级
我们可以设置一个注册方法的优先级:
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
默认优先级是 0,同一 线程(ThreadMode) 中优先级高的订阅者会先于低的接收到消息。
注意,只有在同一线程中的订阅者优先级才有作用。
有优先级后 ,高优先级的订阅者就可以取消消息往后的传播,这也符合生活和一些场景的需求。
这通过调用 cancelEventDelivery(event)
方法:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
注意,只有 ThreadMode 为 POSTING 的订阅方法可以拦截消息。
优先级设置后,界面上显示不明显,因为 EventBus 的消息发送效率很高,但是如果打断点的话就可以看到,的确是高优先级的方法先被调用。
配置
我们一般调用 EventBus 直接使用 EventBus.getDefault()
方法即可。如果你想要自己配置 EventBus,它也支持。
通过 EventBus.builder()
方法我们得到 EventBusBuilder
对象,然后配置 EventBus 的各种属性,比如这样:
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
EventBusBuilder
中可以配置的内容如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
可以看到,默认 EventBus 中以下属性是设为 true 的:
- logSubscriberExceptions: 记录订阅异常
- logNoSubscriberMessages :记录没有目标订阅者的消息
- sendSubscriberExceptionEvent :订阅方法异常时发送
SubscriberExceptionEvent
事件 - sendNoSubscriberEvent :发送的消息没有订阅者时发送
NoSubscriberEvent
事件 - eventInheritance :事件允许继承
AsyncExecutor
EventBus 还提供了一个异步线程池 AsyncExecutor
,使用它创建的线程,如果抛出异常,它会自动捕获,然后将异常信息包裹成一个 Event 发送出去。
AsyncExecutor 只是一个帮我们省去处理子线程抛出异常的工具类,不是 EventBus 的核心类。
我们可以在全局范围内调用 AsyncExector.create()
方法创建一个实例,然后调用 execute
方法执行异步任务,它的参数是 AsyncExecutor.RunnableEx
:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
如果 RunnableEx
中抛出异常,AsyncExecutor
会捕获这个异常,然后包裹成 ThrowableFailureEvent
发送出去,注册了这个事件的方法将会得到调用。
订阅这个异常的例子如下:
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
EventBus 的使用
下面我们演示一下 EventBus 的基本使用。
首先 gradle 引入依赖 EventBus 以及它的注解处理器:
根目录下的 gradle 的 dependencies 中添加 apt 依赖:
- 1
- 1
app 目录下的 gradle 中添加:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
其中 eventBusIndex
你可以随意设置包名和文件名。
然后就可以发车了!
创建要订阅的事件:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
然后创建一个注册 EventBus 页面:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
这个页面的功能如图所示:
- 有两个优先级不同的订阅方法,有两个按钮用于注册和解除注册订阅
- 一个用于高优先级订阅方法拦截事件向后传递的按钮
- 还有一个按钮用于跳转到发送事件页面中,另一个按钮用于跳转到粘性事件订阅页面。
先看下粘性事件订阅页面:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
功能如图所示:
- 一个显示订阅方法接收信息的文字
- 一个点击后跳转到发送事件页面的按钮
- 两个用于注册和解除注册粘性事件的按钮
好,接着再看一下发送事件页面:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
这个页面很简单,两个发送普通事件和粘性事件的按钮。
运行效果
演示下普通事件的注册、解除注册、以及高优先级拦截事件的运行效果:
从上面的动图可以看到:
- 注册事件监听后,订阅的方法就可在 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