EventBus是针对Android优化的发布-订阅事件总线,简化了Android组件间的通信。EventBus以其简单易懂、优雅、开销小等优点而备受欢迎。

github 地址:https://github.com/greenrobot/EventBus

1. 使用

1.1 gradle中引入

        api 'org.greenrobot:eventbus:3.0.0'

1.2 定义事件

定义一个类作为事件,可以在类中定义不同的参数,发布者赋值,订阅者取值。
public class TestEvent {

    private String mName;

    public TestEvent(String name) {
mName = name;
} public String getEventName() {
return mName;
}
}

1.3 注册事件

首先需要将当前对象(Activity/Fragment等)与EventBus绑定(一般在onCreate函数中进行注册)

EventBus.getDefault().register(this);

接收事件的函数:

@Subscribe (threadMode = ThreadMode.MAIN, sticky = true)
public void onTestKeyEvent(TestEvent event) {
Log.d(TAG, "onTestKeyEvent | eventName=" + event.getEventName());
Toast.makeText(this, "test event, name=" + event.getEventName(), Toast.LENGTH_SHORT).show();
}

这里通过注解的方式,定义事件的类型,和回调的线程等信息。

查看EventBus jar包中Subscribe定义:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {

ThreadMode

 threadMode() default ThreadMode.POSTING;

    /**
* If true, delivers the most recent sticky event (posted with
* {@link EventBus#postSticky(Object)}) to this subscriber (if event available).
*/
boolean

sticky

() default false;

    /** Subscriber priority to influence the order of event delivery.
* Within the same delivery thread ({@link ThreadMode}), higher priority subscribers will receive events before
* others with a lower priority. The default priority is 0. Note: the priority does *NOT* affect the order of
* delivery among subscribers with different {@link ThreadMode}s! */
int

priority

() default 0;
}

查看EventBus jar包中ThreadMode定义:

a) POSTING : 回调在发布者线程

b) MAIN : 回调在主线程

c) BACKGROUND : 回调在子线程(如果发布在子线程者回调直接运行在该线程)

d) ASYNC : 异步回调(不回回调在发布线程也不会回调在主线程)

1.4 发送事件

发布者不需要进行注册,只需要将事件post出去。

a)  普通事件:EventBus.getDefault().post(new TestEvent("normalEvent"));

b) 粘性事件:EventBus.getDefault().postSticky(new TestEvent("stickEvent"));

普通事件和粘性事件区别:

如果发布的是普通事件,当前如果没有Subscriber,则后续注册的Subscriber也不会收到该事件。

如果发布的是粘性事件,当前如果没有Subscriber,内部会暂存该事件,当注册Subscriber时,该Subscriber会立刻收到该事件。

2. 结构

采用了典型的订阅发布设计模式。

3. 源码分析

// 这里只分析其原理和结构不会细细推敲每一行代码

订阅者信息封装(Subscription):

定义了两个成员变量,

final Object subscriber;  // 订阅一个事件的对象
final SubscriberMethod subscriberMethod; // 订阅的具体信息(方法名/ThreadMode/isStrick/priority)

EventBus主要成员变量:

private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
private final Map<Object, List<Class<?>>> typesBySubscriber;
private final Map<Class<?>, Object> stickyEvents;

subscriptionsByEventType:以event(即事件类)为key,以订阅列表(Subscription)为value,事件发送之后,在这里寻找订阅者,而Subscription又是一个CopyOnWriteArrayList,这是一个线程安全的容器。你们看会好奇,Subscription到底是什么,其实看下去就会了解的,现在先提前说下:Subscription是一个封装类,封装了订阅者、订阅方法这两个类。
typesBySubscriber:以订阅者类为key,以event事件类为value,在进行register或unregister操作的时候,会操作这个map。
stickyEvents:保存的是粘性事件

3.1 注册Subscriber

注册过程,也就是调用regester函数的执行过程(主要是通过反射将注册者信息添加到上述讲的两个map中:typesBySubscriber、subscriptionsByEventType

a) SubscriberMethodFinder 是专门用来查找目标对象中所有订阅函数(带缓存,避免同一个类多次反射查找)。反射可以获取函数的注解内容及每个函数的返回值/修饰符,具体查看findUsingReflectionInSingleClass函数。
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
// This is faster than getMethods, especially when subscribers are fat classes like Activities
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
// Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
methods = findState.clazz.getMethods();
findState.skipSuperClasses = true;
}
for (Method method : methods) {
int modifiers = method.getModifiers();
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 1) {
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
Class<?> eventType = parameterTypes[0];
if (findState.checkAdd(method, eventType)) {
ThreadMode threadMode = subscribeAnnotation.threadMode();
findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
}
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException("@Subscribe method " + methodName +
"must have exactly 1 parameter but has " + parameterTypes.length);
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException(methodName +
" is a illegal @Subscribe method: must be public, non-static, and non-abstract");
}
}
}

查找订阅函数

b) 将订阅函数添加到两个缓存map中
c) 如果订阅函数接收的是粘性事件,则将缓存中的粘性事件回调给该订阅函数。

上述b) c) 两个步骤的具体代码如下:
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();
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);
}
}
}

缓存订阅信息


3.2 分发消息到每个Subscriber

分发过程是从subscriptionsByEventType中取Subscriber并在指定的线程中回调接收函数的过程。

如何实现在不同线程中执行回调函数?

a)从订阅信息中获取订阅函数回调线程。

b) 在指定线程中回调订阅函数。

分发消息过程

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);
}
}

不同的的消息分发器在EventBus构造的时候初始化,下面看一下AsyncPoster的源码如下:

class AsyncPoster implements Runnable {

    private final PendingPostQueue queue;
private final EventBus eventBus; AsyncPoster(EventBus eventBus) {
this.eventBus = eventBus;
queue = new PendingPostQueue();
} public void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
queue.enqueue(pendingPost);
eventBus.getExecutorService().execute(this);
} @Override
public void run() {
PendingPost pendingPost = queue.poll();
if(pendingPost == null) {
throw new IllegalStateException("No pending post available");
}
eventBus.invokeSubscriber(pendingPost);
} }

AsyncPoster分发器继承自Runable,核心是通过自定义的阻塞队列维护消息,然后在EventBus定义的线程池中执行runable接口中的代码。

EventBus中还定义了BackgroundPoster/HandlerPoster这里不赘述。

3.3 物理类图

其它细节:

上述分析只是讲解了EventBus大概原理,并没有细细分析。如,代码中很多考虑了并发,事件优先级等

EventBus 使用/架构/源码分析的更多相关文章

  1. pouch架构源码分析

    // daemon/daemon.go 1.func NewDaemon(cfg config.Config) *Daemon 调用containerStore, err := meta.NewSto ...

  2. MyBatis 源码分析

    MyBatis 运行过程 传统的 JDBC 编程查询数据库的代码和过程总结. 加载驱动. 创建连接,Connection 对象. 根据 Connection 创建 Statement 或者 Prepa ...

  3. 11.源码分析---SOFARPC数据透传是实现的?

    先把栗子放上,让大家方便测试用: Service端 public static void main(String[] args) { ServerConfig serverConfig = new S ...

  4. 12.源码分析—如何为SOFARPC写一个序列化?

    SOFARPC源码解析系列: 1. 源码分析---SOFARPC可扩展的机制SPI 2. 源码分析---SOFARPC客户端服务引用 3. 源码分析---SOFARPC客户端服务调用 4. 源码分析- ...

  5. 10.源码分析---SOFARPC内置链路追踪SOFATRACER是怎么做的?

    SOFARPC源码解析系列: 1. 源码分析---SOFARPC可扩展的机制SPI 2. 源码分析---SOFARPC客户端服务引用 3. 源码分析---SOFARPC客户端服务调用 4. 源码分析- ...

  6. ABP源码分析二十五:EventBus

    IEventData/EventData: 封装了EventData信息,触发event的源对象和时间 IEventBus/EventBus: 定义和实现了了一系列注册,注销和触发事件处理函数的方法. ...

  7. Orchard源码分析(1):Orchard架构

      本文主要参考官方文档"How Orchard works"以及Orchardch上的翻译.   源码分析应该做到庖丁解牛,而不是以管窥豹或瞎子摸象.所以先对Orchard架构有 ...

  8. Caching-缓存架构与源码分析

    Caching-缓存架构与源码分析 首先奉献caching的开源地址[微软源码] 1.工程架构 为了提高程序效率,我们经常将一些不频繁修改,但是使用了还很大的数据进行缓存.尤其是互联网产品,缓存可以说 ...

  9. Hessian源码分析--总体架构

    Hessian是一个轻量级的remoting onhttp工具,使用简单的方法提供了RMI的功能. 相比WebService,Hessian更简单.快捷.采用的是二进制RPC协议,因为采用的是二进制协 ...

随机推荐

  1. 池化技术(一)Druid是如何管理数据库连接的?

    基于依赖程序的版本信息:druid:1.1.16               驱动程序mysql-connector-java:8.0.17 下一篇:HikariCP是如何管理数据库连接的 零.类图& ...

  2. java 主动信任证书

    java 主动信任证书 SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mid.toCharArray() ...

  3. ETCD:gRPC代理

    原文地址:gRPC proxy gRPC代理是在gRPC层(L7)运行的无状态etcd反向代理.代理旨在减少核心etcd群集上的总处理负载.对于水平可伸缩性,它合并了监视和租约API请求. 为了保护集 ...

  4. vue 客户端渲染和服务端渲染

    参考链接 https://www.cnblogs.com/tiedaweishao/p/6644267.html

  5. 从七个方面,面试BAT大厂高级工程师,纯干货!

    转载注明:https://blog.csdn.net/WantFlyDaCheng/article/details/100078782 一.框架是重点,但别让人感觉你只会山寨别人的代码 二.别单纯看单 ...

  6. 【带着canvas去流浪(10)】文字烟花

    目录 一. 文字烟花 二. 动画原理 2.1 像素操作 2.2 烟花生成算法 2.3 计时器 示例代码托管在:http://www.github.com/dashnowords/blogs 博客园地址 ...

  7. javaWeb核心技术第十一篇之Listener

    监听器:所谓的监听器是指对整个WEB环境的监听,当被监视的对象发生改变时,立即调用相应的方法进行处理. 监听术语: 事件源:被监听的对象. 监听器对象:监听事件源的对象 注册或绑定:1和2结合的过程 ...

  8. element-ui组件中的input等的change事件中传递自定义参数

    以select为例,如果select写在循环里,触发change事件时可能不只需要传递被选中项的值,还要传递index过去,来改变同一循环中的其他标签的状态. 下面这样写是无效的: @change=& ...

  9. Dynamics CRM - js中用webapi基于fetchxml查询遇到的问题 -- Invalid URI: The Uri scheme is too long.

    最近用WebApi做基于Fetchxml的查询的时候,遇到一个很蛋疼的报错:Invalid URI: The Uri scheme is too long. 检查了整个URL,也没发现有什么问题. - ...

  10. 服务器安装neo4j

    neo4j是目前最流行的图形数据库,支持完整的事务.在树形图中,图是有顶点.边和属性组成的,顶点和边都可以设置属性,顶点又称节点,边也叫做关系.每个节点和关系都可以有一个或多个属性,neo4j创建的图 ...