[TOC]
EventBus 是 Android 平台非常优秀的事件总线开源库,来自于德国的 greenrobot 团队,该团队旗下还有大名鼎鼎的 greenDAO。EventBus 足够快速、轻量,安装量超过 100,000,000+!足以看出其热门程度。本文是我对 EventBus 3.0 的源码分析,源码分析经验不足,如有错误,还请指出。
# 观察者模式
本段引自[图说设计模式](http://design-patterns.readthedocs.io/zh_CN/latest/behavioral_patterns/observer.html#id6)
## 模式动机
建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。在此,发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展,这就是观察者模式的模式动机。
## 模式定义
观察者模式(Observer Pattern):定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。
观察者模式是一种对象行为型模式。
## 实现总结
具体代码实现可参考 [我的个人 wiki](http://wiki.xuchongyang.com/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%A1%8C%E4%B8%BA%E5%9E%8B%E6%A8%A1%E5%BC%8F.html)
* 观察目标会维护一个观察者的集合,并提供新增、删除接口
* 当观察目标需要通知观察者时,会循环调用观察者集合元素的更新方法,并可将部分参数或观察目标自身传递给观察者
* 观察者更新方法被调用后,可根据传递来的数据做相应的处理
## 优点
* 观察者模式可以实现表示层和数据逻辑层的分离,并定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不同的表示层作为具体观察者角色。
* 观察者模式在观察目标和观察者之间建立一个抽象的耦合。
* 观察者模式支持广播通信。
* 观察者模式符合“开闭原则”的要求。
## 缺点
* 如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
* 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
* 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
# EventBus 3.0源码分析
## 简单使用
EventBus 3.0 的使用非常简单,总共 4 步:
* 定义事件
```java
public static class MessageEvent { /* Additional fields if needed */ }
```
* 构建观察者,声明欲观察事件
```java
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {/* Do something */};
```
* 建立观察者、观察目标关系
```java
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
```
* 观察目标发送事件
```java
EventBus.getDefault().post(new MessageEvent());
```
## 整体设计
![](https://ws2.sinaimg.cn/large/006tKfTcgy1fkuqqvweilj30zk0db74s.jpg)
一目了然!接下来我们按使用步骤来一一分析。
## register 注册
```java
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
// 根据观察者类型找出该观察者的所有事件订阅方法
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
// 给观察者订阅每个事件
subscribe(subscriber, subscriberMethod);
}
}
}
```
register 方法的作用就很清楚了,一一来看:
1、首先来看 findSubscriberMethods 方法的实现
```java
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
// 先从方法缓存中查找是否有缓存
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
if (ignoreGeneratedIndex) {
// 通过反射来获取订阅方法
subscriberMethods = findUsingReflection(subscriberClass);
} else {
// 通过 Index 来获取订阅方法,这个是在 EventBus 3.0 中新添加的,后面再详细分析
subscriberMethods = findUsingInfo(subscriberClass);
}
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation");
} else {
// 对订阅方法进行缓存
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}
```
我们的观察者订阅目标事件,都是通过 Subscribe 注解来订阅的。看下 Subscribe 注解的声明:
```java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING;
boolean sticky() default false;
int priority() default 0;
}
```
可以看到该注解的保留时长为 RUNTIME,所以可以在运行时通过反射读取到注解中的信息。同时,EventBus 3.0 新增了一个 EventBusAnnotationProcessor 注解处理器,在编译期读取注解并解析,然后通过 java 类保存所有观察者订阅的信息,运行时直接使用,这样会比在运行时通过反射获取观察者的订阅信息来的快。
2、register 中的 subscribe 方法
先看 EventBus 类的三个成员变量,后面会用到。
```java
// 键:事件类型,值:订阅关系集合
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
// 键:观察者,值:订阅事件集合
private final Map<Object, List<Class<?>>> typesBySubscriber;
// 键:粘性事件的 class 对象,值:事件对象
private final Map<Class<?>, Object> stickyEvents;
```
```java
// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class<?> eventType = subscriberMethod.eventType;
// 建立订阅关系,便于后面取消订阅
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
// 根据事件类型获取订阅关系
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
int size = subscriptions.size();
// 根据优先级将当前订阅关系插入到 subscriptions 中
for (int i = 0; i <= size; i++) {
if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
subscriptions.add(i, newSubscription);
break;
}
}
// 根据观察者获取到其订阅事件类型集合
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
// 添加事件到观察者订阅事件集合
subscribedEvents.add(eventType);
//粘性事件,立即分发
if (subscriberMethod.sticky) {
// 是否分发订阅了响应事件类父类事件的方法
if (eventInheritance) {
// Existing sticky events of all subclasses of eventType have to be considered.
// Note: Iterating over all events may be inefficient with lots of sticky events,
// thus data structure should be changed to allow a more efficient lookup
// (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
for (Map.Entry<Class<?>, Object> entry : entries) {
Class<?> candidateEventType = entry.getKey();
if (eventType.isAssignableFrom(candidateEventType)) {
Object stickyEvent = entry.getValue();
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
} else {
Object stickyEvent = stickyEvents.get(eventType);
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
```
上面 checkPostStickyEventToSubscription 的实现如下:
```java
private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
if (stickyEvent != null) {
// If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)
// --> Strange corner case, which we don't take care of here.
postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());
}
}
```
postToSubscription 我们在后面事件分发中还会见到,再详细看。
## post 分发事件 event
```java
/** Posts the given event to the event bus. */
public void post(Object event) {
// 获得当前线程的 PostingThreadState
PostingThreadState postingState = currentPostingThreadState.get();
List<Object> eventQueue = postingState.eventQueue;
// 将当前事件添加到当前线程的事件队列中
eventQueue.add(event);
if (!postingState.isPosting) {
postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
postingState.isPosting = true;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset");
}
try {
while (!eventQueue.isEmpty()) {
// 发送单个事件
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
```
post 方法的关键代码在 postSingleEvent:
```java
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
boolean subscriptionFound = false;
if (eventInheritance) {
List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
for (int h = 0; h < countTypes; h++) {
Class<?> clazz = eventTypes.get(h);
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
} else {
// 发送单个事件
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
}
if (!subscriptionFound) {
if (logNoSubscriberMessages) {
Log.d(TAG, "No subscribers registered for event " + eventClass);
}
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
post(new NoSubscriberEvent(this, event));
}
}
}
```
其中 postSingleEventForEventType 方法如下:
```java
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
// 根据事件 class 类型来找出所有订阅该事件的订阅关系
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
// 将事件一一分发给每一个观察者
for (Subscription subscription : subscriptions) {
postingState.event = event;
postingState.subscription = subscription;
boolean aborted = false;
try {
// 分发给观察者
postToSubscription(subscription, event, postingState.isMainThread);
aborted = postingState.canceled;
} finally {
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
if (aborted) {
break;
}
}
return true;
}
return false;
}
```
通过源码以及注释可以看的很清晰了,其中 postToSubscription 方法如下:
```java
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
case POSTING:
invokeSubscriber(subscription, event);
break;
case MAIN:
if (isMainThread) {
invokeSubscriber(subscription, event);
} else {
mainThreadPoster.enqueue(subscription, event);
}
break;
case BACKGROUND:
if (isMainThread) {
backgroundPoster.enqueue(subscription, event);
} else {
invokeSubscriber(subscription, event);
}
break;
case ASYNC:
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
}
}
```
可以看到,会对观察者的 ThreadMode 进行判断,再根据发布线程情况 invoke 观察者的订阅方法。
ThreadMode 总共有四类:
本段引自[EventBus 源码解析](http://a.codekk.com/detail/Android/Trinea/EventBus%20%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90)
* PostThread:默认的 ThreadMode,表示在执行 Post 操作的线程直接调用订阅者的事件响应方法,不论该线程是否为主线程(UI 线程)。当该线程为主线程时,响应方法中不能有耗时操作,否则有卡主线程的风险。适用场景:对于是否在主线程执行无要求,但若 Post 线程为主线程,不能耗时的操作;
* MainThread:在主线程中执行响应方法。如果发布线程就是主线程,则直接调用订阅者的事件响应方法,否则通过主线程的 Handler 发送消息在主线程中处理——调用订阅者的事件响应函数。显然,MainThread 类的方法也不能有耗时操作,以避免卡主线程。适用场景:必须在主线程执行的操作;
* BackgroundThread:在后台线程中执行响应方法。如果发布线程不是主线程,则直接调用订阅者的事件响应函数,否则启动唯一的后台线程去处理。由于后台线程是唯一的,当事件超过一个的时候,它们会被放在队列中依次执行,因此该类响应方法虽然没有 PostThread 类和 MainThread 类方法对性能敏感,但最好不要有重度耗时的操作或太频繁的轻度耗时操作,以造成其他操作等待。适用场景:操作轻微耗时且不会过于频繁,即一般的耗时操作都可以放在这里;
* Async:不论发布线程是否为主线程,都使用一个空闲线程来处理。和 BackgroundThread 不同的是,Async 类的所有线程是相互独立的,因此不会出现卡线程的问题。适用场景:长耗时操作,例如网络访问。
接着来看下 invokeSubscriber 方法。
```java
void invokeSubscriber(Subscription subscription, Object event) {
try {
//通过反射调用了观察者的订阅函数,并把 event 对象作为参数传入
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
} catch (InvocationTargetException e) {
handleSubscriberException(subscription, event, e.getCause());
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unexpected exception", e);
}
}
```
## unregister 解除注册
```java
/** Unregisters the given subscriber from all event classes. */
public synchronized void unregister(Object subscriber) {
List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
if (subscribedTypes != null) {
// 一一解除订阅
for (Class<?> eventType : subscribedTypes) {
unsubscribeByEventType(subscriber, eventType);
}
typesBySubscriber.remove(subscriber);
} else {
Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}
```
看关键的 unsubscribeByEventType 方法:
```java
/** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. */
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions != null) {
int size = subscriptions.size();
for (int i = 0; i < size; i++) {
Subscription subscription = subscriptions.get(i);
if (subscription.subscriber == subscriber) {
subscription.active = false;
subscriptions.remove(i);
i--;
size--;
}
}
}
}
```
# EventBus 源码分析概览
1、register 方法注册:首先会根据观察者的类型找出它声明要订阅的所有事件(订阅方法),然后一一订阅
2、订阅过程:首先根据事件类型获取到订阅该事件类型的订阅关系集合,(为观察者和订阅方法生成订阅关系)并把这个订阅关系对象存入到该集合中;然后根据观察者获取到该观察者订阅的事件集合,并把当前订阅的事件放入到事件集合中。
EventBus 有两个 Map 类型的成员变量,分别为:
* Map1 用于根据事件类型通过反射调用观察者的方法 -- Key:事件类型,Value:订阅了该事件的订阅关系(观察者、订阅方法)集合
* Map2 用于取消订阅 -- Key:观察者,Value:该观察者的订阅事件集合
3、观察目标 post 事件:首先获得当前线程的待发送事件队列,并把当前事件对象添加进去;接着依次发送当前队列中的事件对象。
4、事件对象的发送:首先从刚才的 Map 中,根据事件类型取得订阅关系集合;然后遍历订阅关系集合,先进行线程判断,再分别通过反射调用观察者的订阅方法
5、unregister 解除注册:首先从 Map2 中根据观察者取得该观察者订阅事件集合,然后一一解除该观察者和每个事件的订阅关系,最后再把该观察者从 Map2 中删除
6、解除观察者和每个事件的订阅关系:从 Map1 中根据事件类型获得订阅了该事件的订阅关系集合,将该观察者相关的订阅关系进行删除
补充:
* 订阅方法是对订阅事件做的一层封装
* 订阅关系是对观察者和订阅方法做的一层封装
另分享一篇分析思路不错的文章
[浅析EventBus 3.0实现思想](http://alighters.com/blog/2016/05/22/eventbus3-dot-0-analyze/)
- 导读
- Java知识
- Java基本程序设计结构
- 【基础知识】Java基础
- 【源码分析】Okio
- 【源码分析】深入理解i++和++i
- 【专题分析】JVM与GC
- 【面试清单】Java基本程序设计结构
- 对象与类
- 【基础知识】对象与类
- 【专题分析】Java类加载过程
- 【面试清单】对象与类
- 泛型
- 【基础知识】泛型
- 【面试清单】泛型
- 集合
- 【基础知识】集合
- 【源码分析】SparseArray
- 【面试清单】集合
- 多线程
- 【基础知识】多线程
- 【源码分析】ThreadPoolExecutor源码分析
- 【专题分析】volatile关键字
- 【面试清单】多线程
- Java新特性
- 【专题分析】Lambda表达式
- 【专题分析】注解
- 【面试清单】Java新特性
- Effective Java笔记
- Android知识
- Activity
- 【基础知识】Activity
- 【专题分析】运行时权限
- 【专题分析】使用Intent打开三方应用
- 【源码分析】Activity的工作过程
- 【面试清单】Activity
- 架构组件
- 【专题分析】MVC、MVP与MVVM
- 【专题分析】数据绑定
- 【面试清单】架构组件
- 界面
- 【专题分析】自定义View
- 【专题分析】ImageView的ScaleType属性
- 【专题分析】ConstraintLayout 使用
- 【专题分析】搞懂点九图
- 【专题分析】Adapter
- 【源码分析】LayoutInflater
- 【源码分析】ViewStub
- 【源码分析】View三大流程
- 【源码分析】触摸事件分发机制
- 【源码分析】按键事件分发机制
- 【源码分析】Android窗口机制
- 【面试清单】界面
- 动画和过渡
- 【基础知识】动画和过渡
- 【面试清单】动画和过渡
- 图片和图形
- 【专题分析】图片加载
- 【面试清单】图片和图形
- 后台任务
- 应用数据和文件
- 基于网络的内容
- 多线程与多进程
- 【基础知识】多线程与多进程
- 【源码分析】Handler
- 【源码分析】AsyncTask
- 【专题分析】Service
- 【源码分析】Parcelable
- 【专题分析】Binder
- 【源码分析】Messenger
- 【面试清单】多线程与多进程
- 应用优化
- 【专题分析】布局优化
- 【专题分析】绘制优化
- 【专题分析】内存优化
- 【专题分析】启动优化
- 【专题分析】电池优化
- 【专题分析】包大小优化
- 【面试清单】应用优化
- Android新特性
- 【专题分析】状态栏、ActionBar和导航栏
- 【专题分析】应用图标、通知栏适配
- 【专题分析】Android新版本重要变更
- 【专题分析】唯一标识符的最佳做法
- 开源库源码分析
- 【源码分析】BaseRecyclerViewAdapterHelper
- 【源码分析】ButterKnife
- 【源码分析】Dagger2
- 【源码分析】EventBus3(一)
- 【源码分析】EventBus3(二)
- 【源码分析】Glide
- 【源码分析】OkHttp
- 【源码分析】Retrofit
- 其他知识
- Flutter
- 原生开发与跨平台开发
- 整体归纳
- 状态及状态管理
- 零碎知识点
- 添加Flutter到现有应用
- Git知识
- Git命令
- .gitignore文件
- 设计模式
- 创建型模式
- 结构型模式
- 行为型模式
- RxJava
- 基础
- Linux知识
- 环境变量
- Linux命令
- ADB命令
- 算法
- 常见数据结构及实现
- 数组
- 排序算法
- 链表
- 二叉树
- 栈和队列
- 算法时间复杂度
- 常见算法思想
- 其他技术
- 正则表达式
- 编码格式
- HTTP与HTTPS
- 【面试清单】其他知识
- 开发归纳
- Android零碎问题
- 其他零碎问题
- 开发思路