Android 框架学习3:从 EventBus 中学到的精华
关联文章:
学习的目的是为了超越,经过前面对 EventBus 3.0 的学习,我们已经对它相当熟悉了,现在来总结下,从这个框架里我们可以学到些什么。
读完本文你将了解:
首先看看 EventBus 解决了什么问题。
EventBus 解决了什么问题
在日常开发中,回调的使用场景非常多,比如按钮的点击事件,网络的请求结果等等,它表示的是对某一将来可能发生事件的监听,具体使用步骤为以下 3 步:
- 创建一个回调接口,在接口中定义监听到事件发生时要进行的操作
- 需要监听的地方创建一个回调的具体实现,然后传递给事件触发者
- 事件触发者持有回调接口的引用,在事件发生时,调用回调接口的具体实现
非常简单的 3 步就实现了对未来事件的监听。
如果对某一个事件有多个监听,就需要在事件触发者里创建监听者列表,然后在事件发生时挨个通知注册过的监听者。这就是“观察者模式”。
观察者模式又称“发布-订阅模式”,它用于一个被观察者持有多个观察者对象的引用,当被观察者状态发生改变时,通知所有观察者进行更新。是一种一对多的依赖关系。
不熟悉的同学欢迎查看我的 观察者模式 : 一支穿云箭,千军万马来相见 了解观察者模式。
在观察者模式中,订阅者需要实现同样的接口,也就是只能监听同样的事件。如果想要监听不同的事件就需要创建不同的接口,在事件多了以后难免有些繁琐。
最好有一种方法,订阅者实现一个接口就可以监听不同事件,哦不,干脆不实现接口,只创建事件发生时要进行的操作就好了。
EventBus 所代表的思想,就是一种解决方案。
EventBus 的思想
在 EventBus 中,我们无需实现接口,只要在订阅者中创建监听不同事件的方法,然后使用注解标识。
EventBus 会在编译时和运行时(取决于你是否添加索引)通过处理注解和反射的方式拿到订阅方法和所在的类,然后将订阅者、订阅方法、订阅的事件分别保存在两个属性中。
在有发送者发送事件时,EventBus 根据事件去前面保存的属性里找到订阅者和订阅方法,然后以反射的方式调用它。
EventBus 的编译时注解
源码分析 EventBus 3.0 如何实现事件总线 中我们了解到,在编译时 EventBus 的注解处理器会读取注解,然后生成索引文件。
之前 EventBus 是纯反射,经常有人说性能差什么的,这下好了,编译时注解可以加快很多查找效率。
除了 ButterKnife 使用编译时注解生成重复代码外,EventBus 对编译时注解的使用为我们提供了新的思路:
- 将运行时需要进行的查找工作转移到编译时
- 使用哈希表保存查找到的信息
- 生成的类实现约定的接口,便于运行时调用
关于编译时注解如何使用可以查看这篇:使用编译时注解简单实现类似 ButterKnife 的效果
EventBus 用到的设计模式
EventBus 作为比较成熟的框架,还是使用了很多设计模式的,这里复习一下。
①单例模式
static volatile EventBus defaultInstance;
public static EventBus getDefault() {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}
经典的 double-check 单例,使用 volatile
修饰避免重排序,完美。
②Builder 模式
public EventBus() {
this(DEFAULT_BUILDER);
}
EventBus(EventBusBuilder builder) {
subscriptionsByEventType = new HashMap<>();
typesBySubscriber = new HashMap<>();
stickyEvents = new ConcurrentHashMap<>();
mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
backgroundPoster = new BackgroundPoster(this);
asyncPoster = new AsyncPoster(this);
indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
builder.strictMethodVerification, builder.ignoreGeneratedIndex);
logSubscriberExceptions = builder.logSubscriberExceptions;
logNoSubscriberMessages = builder.logNoSubscriberMessages;
sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
throwSubscriberException = builder.throwSubscriberException;
eventInheritance = builder.eventInheritance;
executorService = builder.executorService;
}
类中的字段太多时,每次都调用 setXXX()
太累了,不如提供一个 Builder,提供些默认值,剩下的就让用户自己设置吧。
③外观模式
EventBus 将不同事件的保存、分发封装在内部,向外部提供了简单的注册、发送、解除注册方法。
把散落在多处的重复操作收到一个入口,这不就是外观模式么。
④策略模式
private final HandlerPoster mainThreadPoster;
private final BackgroundPoster backgroundPoster;
private final AsyncPoster asyncPoster;
EventBus 的几种消息 Poster 虽然没有严格的符合策略模式,但思想其实是一样的。都是完成相同的任务,只不过具体实现方式不同。
其实完全可以制定一个统一 Poster 接口,方法为 enqueue()
,然后 EventBus 持有 Poster 的三个引用,每个引用的实现不同。
⑤观察者模式
这个就不用多说了,EventBus 事件订阅、发布的过程就是观察者模式的改进版。
想了解设计模式的文章,可以查看: 设计模式专栏
值得学习的细节
如果现在让我去写一个 EventBus ,恐怕是难以胜任的。
即使整体思想(事件注册、收集、发送、解除注册)有了,分成不同的模块,具体怎么实现,想想就头疼哦。
来看看 EventBus 有什么值得学习的细节:
- 提供默认 Builder 和默认实例
- 选用合适的线程池
Executors.newCachedThreadPool()
适合并发执行大量短期的小任务
- 使用
ThreadLocal
实现事件队列的线程无关性 - 并发控制
- 数据有可能在操作的同时添加,使用
CopyOnWriteArrayList
与synchronized
- 数据有可能在操作的同时添加,使用
- 职责分离
- 查找类中的订阅方法有专门的类
SubscriberMethodFinder
- 保存类与其中的订阅方法关系有专门的类
Subscription
- 不同线程发送事件也有专门的类
- 查找类中的订阅方法有专门的类
- 使用 map 缓存一些可能会重复使用的信息
- 事件与其的父类和接口的映射
Map<Class<?>, List<Class<?>>> eventTypesCache = new HashMap<>()
- 事件与对应的订阅者关联列表
Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType
- 订阅者与订阅的事件关联列表
Map<Object, List<Class<?>>> typesBySubscriber
- 保存粘性事件
Map<Class<?>, Object> stickyEvents
- 事件与其的父类和接口的映射
- 如果需要创建大量相同类型的对象,考虑使用对象池,对象使用完毕回收,只要把引用其他对象置为 null 即可
FindState[] FIND_STATE_POOL = new FindState[POOL_SIZE]
List<PendingPost> pendingPostPool = new ArrayList<PendingPost>()
- 将重复传递的一些参数封到一个对象里传递
PostingThreadState
- 创建了自己的事件队列
PendingPostQueue
- 双向链表
- 生产者-消费者模型, 出队 wait - 入队 nofityAll
不足之处
①EventBus 虽然提供了编译时生成的索引,使用起来却不方便,不能直接给 EventBus.getDefault()
添加索引,必须自己创建一个 EventBusBuilder
实例,然后才能添加:
EventBus mInstance = EventBus.builder()
.addIndex(new MyEventBusIndex())
.build();
②使用 EventBus 进行解耦其实是把双刃剑,大量的业务逻辑散布在各处,有点类似 C 语言里的 goto
的弊端:
- 在程序比较简单时是比较灵活,但是当程序比较复杂时很容易造成程序流程的混乱
- 非编写本人,其他人看程序不那么容易理解
- 调试程序的过程也会变得很困难。
③Poster 没有创建基类/接口
其实也不是什么大问题,就是感觉创建统一接口会更优美啊 0.0
感悟
EventBus 还是挺优秀的框架,主要是它出现的比较早,思想比较优秀。
在写这篇文章之前我去总结了几篇 EventBus 源码中设计的知识点,比如线程池、阻塞队列、编译时注解。
基础很重要啊,基础不好怎么可能写出这样的代码呢?!还是要再努力点才行啊。
Android 框架学习3:从 EventBus 中学到的精华的更多相关文章
- Android 框架学习1:EventBus 3.0 的特点与如何使用
前面总结了几篇基础,在这过程中看着别人分享自定义 View.架构或者源码分析,看起来比我写的"高大上"多了,内心也有点小波动. 但是自己的水平自己清楚,基础不扎实画再多源码流程图也 ...
- Android 框架学习2:源码分析 EventBus 3.0 如何实现事件总线
Go beyond yourself rather than beyond others. 上篇文章 深入理解 EventBus 3.0 之使用篇 我们了解了 EventBus 的特性以及如何使用,这 ...
- Android 框架学习之 第一天 okhttp & Retrofit
最近面试,一直被问道新技术新框架,这块是短板,慢慢补吧. 关于框架的学习,分几个步骤 I.框架的使用 II.框架主流使用的版本和Android对应的版本 III.框架的衍生使用比如okhttp就会有R ...
- Android框架式编程之EventBus
一.EventBus 简介 EventBus是一种用于Android的事件发布-订阅总线,由GreenRobot开发,Gihub地址是:EventBus. 它简化了应用程序内各个组件之间进行通信的复杂 ...
- Android开发学习之路-EventBus使用
EventBus是一个通过发布.订阅事件实现组件间消息传递的工具. 它存在的目的,就是为了优化组件之间传递消息的过程.传统组件之间传递消息的方法有使用广播,回调等,而这些方法使用都比较复杂. 工作原理 ...
- Android接口和框架学习
Android接口和框架学习 缩写: HAL:HardwareAbstraction Layer.硬件抽象层 CTS:CompatibilityTest Suite,兼容性測试套件 Android让你 ...
- Android驱动学习-app调用内核驱动过程(驱动框架回顾)
考研已经过去了,android驱动的学习也断了半年多了,现在重新捡起来学习,回顾一下Android驱动的大体框架. Android系统的核心是java,其有一个David虚拟机.Android-app ...
- 【Android - 框架】之GreenDao的使用
上一篇博客([Android - 框架]之ORMLite的使用)中介绍了ORMLite的基本使用,今天我们来研究以下GreenDao的使用. GreenDao和ORMLite一样,都是基于ORM(Ob ...
- Android开发学习之路-记一次CSDN公开课
今天的CSDN公开课Android事件处理重难点快速掌握中老师讲到一个概念我觉得不正确. 原话是这样的:点击事件可以通过事件监听和回调两种方法实现. 我一听到之后我的表情是这样的: 这跟我学的看的都不 ...
随机推荐
- 20145211 《网络渗透》MS08_067安全漏洞
20145211 <网络渗透>MS08_067安全漏洞 一.实验原理 ms08_067是服务器服务中一个秘密报告的漏洞,于2008年被发现.攻击者利用靶机默认开放的SMB服务的445端口, ...
- 4.9版本linux内核的ina220电流检测芯片源码在哪里
答:在drivers/hwmon/ina2xx.c中,内核配置项为CONFIG_SENSORS_INA2XX Location: -> Device Drivers -> Hardware ...
- 【bzoj2563】阿狸和桃子的游戏(贪心+构造)
题目传送门:bzoj2563 先膜拜一波PoPoQQQ的题解:BZOJ 2563 阿狸和桃子的游戏 贪心 其实我们可以这样看:把一条边的权值均分到两个端点,那么取到两个端点就能得到这条边的边权,如果只 ...
- windchill系统安装大概步骤
1.安装VMware Workstation虚拟机 2.win7的64位操作系统(为什么不用32位?因为32位的内存最大只能设置4G) 3.安装Oracle数据库(映射iso文件[上面栏的虚拟机-&g ...
- Python学习札记(二十) 函数式编程1 介绍 高阶函数介绍
参考: 函数式编程 高阶函数 Note A.函数式编程(Functional Programming)介绍 1.函数是Python内建支持的一种封装,我们通过一层一层的函数调用把复杂任务分解成简单的任 ...
- java如何实现Socket的长连接和短连接
讨论Socket必讨论长连接和短连接 一.长连接和短连接的概念 1.长连接与短连接的概念:前者是整个通讯过程,客户端和服务端只用一个Socket对象,长期保持Socket的连接:后者是每次请求,都新建 ...
- 06_zookeeper_原生API使用2
1. 设置znode节点数据(同步) import org.apache.zookeeper.*; import org.apache.zookeeper.data.Stat; import java ...
- 转 : JBoss Web和 Tomcat的区别
JBoss Web和 Tomcat的区别 在Web2.0的浪潮中,各种页面技术和框架不断涌现,为服务器端的基础架构提出了更高的稳定性和可扩展性的要求.近年来,作为开源中间件的全 球领导者,JBoss在 ...
- php获取经纬度
<?php header("content-type:text/html;charset=utf-8"); function ipjwd() { $getIp=$_SERVE ...
- Java虚拟机组成详解
导读:详细而深入的总结,是对知识“豁然开朗”之后的“刻骨铭心”,想忘记都难. Java虚拟机(Java Virtual Machine)下文简称jvm,上一篇我们对jvm有了大体的认识,进入本文之后我 ...