• 示例使用
  • 时序图
  • 源码解读

EventBus 使用

  • 官网定义:EventBus 是一个使用 Java 写的观察者模式,解耦的 Android 开源库。EventBus 只需要几行代码即可解耦简化代码,加快开发速度。

使用:

  1. 定义 event

    Event 就是个标准 POJO
    public class MessageEvent {

        public final String message;

        public MessageEvent(String message) {
this.message = message;
}
}
  1. 准备订阅者。

    在需要接收消息的 Activity 或者 Fragemnt 生命周期方法中注册,同时在对应的生命周期方法中进行反注册。不解绑的话会造成内存泄漏。
    @Override
public void onStart() {
super.onStart();
// 注册
EventBus.getDefault().register(this);
} @Override
public void onStop() {
// 反注册
EventBus.getDefault().unregister(this);
super.onStop();
}

写一个方法使用注解 Subscribe 修饰,同时设置 threadMode = ThreadMode.MAIN 表示在 UI 线程接收该事件。也可以使用其他的 ThreadMode 来修饰,比如 POSTING , ASYNC 等,不过在 Android 中一般都是用 MAIN 的。

        // 收到消息,弹出 Toast
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show();
}
  1. 发送 evnet

    在需要发送事件的地方通过调用 post(event) 方法发送自定义的 event
    EventBus.getDefault().post(new MessageEvent("Hello everyone!"));

官网

Github

时序图

源码解析

  • 思路:通过 EventBus.gerDefault().register(this),把当前的 Activity 或者 Fragment 和通过反射拿到在其中用 @Subscribe 修饰的方法,通过 Mapevent 和 订阅者绑定,通过 post() 方法携带 Event ,从容器中取出相关的 subscribe 和方法,通过反射调用该方法,把消息分发到目标 subscribe 。在 ActivityFragment 生命周期销毁的时候,通过 unRegister()subscribe 从容器中移除,去掉订阅者模式,防止内存泄漏。

  • 相关类

    • Poster:接口,唯一方法 enqueue(), 加入队列的。
    • HandlerPoster: 切换到 UI 线程的 Poster 实现类,内部通过一个链表存储消息,通过 Handler 把消息切换到 UI 线程,从而实现更新 UI 线程
    • BackgroundPoster: 后台执行 EventPoster 实现类
    • AsyncPoster:与 BackgroudPoster 类似
    • PendingPostQueue: 存储 PendingPost 的链表
    • Subscription: 订阅对象,有 subscribersubscribeMethod 构成
    • SubscriberMethod: subscriber 中的接收方法,有 方法名称 ,接收信息线程, 优先级, 是否是粘性, event类型
    • PostingThreadState:保存在 ThreadLocal 中,主要存储 event 的相关信息, 是否在主线程,订阅者信息,是否取消,是否已发送。
    • MainThreadSupport: 切换到 UI 线程用的,配合 HandlerPoster 一起使用。
    • SubscriberInfo:Base class for generated index classes created by annotation processing
  • 从使用的入口开始

注册:EventBus.getDefault().register(this)

发送消息:EventBus.getDefault().post(event)

  • 发送消息一般调用 post() 方法,然后把定义的 Event 发送出去,看下 post() 方法源码
    public void post(Object event) {
// ThreadLocal 中获取变量 postingState.
PostingThreadState postingState = currentPostingThreadState.get();
// 从 postingState 中拿到 ArrayList
List<Object> eventQueue = postingState.eventQueue;
// 把当前 event 加入到 ArrayList 中
eventQueue.add(event);
// 是否已发送,在主线程
if (!postingState.isPosting) {
postingState.isMainThread = isMainThread();
postingState.isPosting = true;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset");
}
try {
// ArrayList 不为 Null , 循环取出来数据,通过 postSingleEvent() 发送。
while (!eventQueue.isEmpty()) {
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
// Arraylist 中所有的消息都发送完成,设置 isPosting 和 isMainThread 为 false。
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}

ThreadLocal 中拿到 PostingThreadState ,然后把消息放入的到变量 ArrayList 中,然后判断 postingState 的状态,是否处于发送状态,不在发送状态的话,进入条件判断然后遍历 ArrayList 调用 postSingleEvent() 方法把 event 发送。看下 postSingleEvent() 源码

    private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
// 订阅者信息是否能找到
boolean subscriptionFound = false;
// 通过 EventBusBilder 传入的, 默认为 true
if (eventInheritance) {
List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
for (int h = 0; h < countTypes; h++) {
Class<?> clazz = eventTypes.get(h);
// 进入到了 postSingleEventForEventType() 方法
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
} else {
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
}
if (!subscriptionFound) {
if (logNoSubscriberMessages) {
logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
}
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
post(new NoSubscriberEvent(this, event));
}
}
}

在方法 postSingleEvent() 中,通过成员变量 eventInheritance (表示是否要向上查找事件的父类) 来判断,默认为 true ,所以进入 if 中,lookupAllEventTypes() 方法会通过递归的方式进行查找所有父类事件并存到 List 中。然后通过遍历调用 postSingleEventForEventType() 方法进行处理

看下 lookupAllEventTypes() 源码,其中 addInterfaces() 通过递归拿到全部的父类事件。

    private static List<Class<?>> lookupAllEventTypes(Class<?> eventClass) {
synchronized (eventTypesCache) {
List<Class<?>> eventTypes = eventTypesCache.get(eventClass);
if (eventTypes == null) {
eventTypes = new ArrayList<>();
Class<?> clazz = eventClass;
while (clazz != null) {
eventTypes.add(clazz);
addInterfaces(eventTypes, clazz.getInterfaces());
clazz = clazz.getSuperclass();
}
eventTypesCache.put(eventClass, eventTypes);
}
return eventTypes;
}
}

addInterfaces() 源码,通过递归调用方法,查找所有的父类事件并加入到 List

    static void addInterfaces(List<Class<?>> eventTypes, Class<?>[] interfaces) {
for (Class<?> interfaceClass : interfaces) {
if (!eventTypes.contains(interfaceClass)) {
eventTypes.add(interfaceClass);
addInterfaces(eventTypes, interfaceClass.getInterfaces());
}
}
}

看下 postSingleEventForEventType() 源码。成员变量 subscriptionsByEventType 是一个 Map<Class<?>, CopyOnWriteArrayList<Subscription>>,在注册的时候,eventType 作为 key ,subscriptions 作为 value 存入了 map 中。 通过成员变量拿到 subscriptions ,然后遍历该列表,调用 postToSubscription() 方法

    private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
// 线程安全的ArrayList 保存订阅者信息
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
// 根据 clz 获取该类的订阅者信息列表, subscriptionsByEventType 这个 Map 在注册的时候,把 eventType 和 subscriptions 绑定了。
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
//遍历订阅者信息列表
for (Subscription subscription : subscriptions) {
//设置 postingState 的状态
postingState.event = event;
postingState.subscription = subscription;
// 是否可以取消,默认不可取消
boolean aborted = false;
try {
// 传入订阅者信息,event, 是否在主线程
postToSubscription(subscription, event, postingState.isMainThread);
// postingState.canceled 默认是 false 。表示不可取消
aborted = postingState.canceled;
} finally {
//postingState 恢复成默认状态。
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
if (aborted) {
break;
}
}
return true;
}
return false;
}

进入 postToSubscription() 源码,根据订阅者在注册的时候存入的 threadModeevent 分发到不同的线程中。根据 threadMode 不同,可以分为 POSTING, MAIN, MAIN_ORDERED, BACKGROUND, ASYNC。 变量 mainThreadPoster 其实是 HandlerPoster ,把消息发送到 UI 线程的。

    private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
case POSTING:// 订阅者与 event 在相同线程
invokeSubscriber(subscription, event);
break;
case MAIN:// UI 线程
// event 是在 UI 线程,则直接通过反射调用。
if (isMainThread) {
invokeSubscriber(subscription, event);
} else {
// event 不在 UI 线程,则加入队列,通过 Handler 切换到 UI 线程
mainThreadPoster.enqueue(subscription, event);
}
break;
case MAIN_ORDERED://UI线程,按照顺序发送消息
if (mainThreadPoster != null) {
mainThreadPoster.enqueue(subscription, event);
} else {
// temporary: technically not correct as poster not decoupled from subscriber
invokeSubscriber(subscription, event);
}
break;
case BACKGROUND://
if (isMainThread) {
backgroundPoster.enqueue(subscription, event);
} else {
invokeSubscriber(subscription, event);
}
break;
case ASYNC://订阅者单独一个线程,不同于发送线程和UI线程。
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
}
}

invokeSubscriber() 方法,通过反射执行订阅者的订阅方法

    void invokeSubscriber(Subscription subscription, Object event) {
try {
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
} catch (InvocationTargetException e) {
handleSubscriberException(subscription, event, e.getCause());
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unexpected exception", e);
}
}

反注册:EventBus.getDefault().unregister(this);

源码解析-EventBus的更多相关文章

  1. [EventBus源码解析] EventBus.register 方法详述

    前情概要 在上一篇中,介绍了EventBus的基本使用方法,以及一部分进阶技巧.本篇及以后的几篇blog将会集中解析EventBus.java,看看作者是如何优雅地实现这个看似简单的事件分发/接收机制 ...

  2. [EventBus源码解析] EventBus.post 方法详述

    前情概要 上一篇blog我们了解了EventBus中register/unregister的过程,对EventBus如何实现观察者模式有了基本的认识.今天我们来看一下它是如何分发一个特定事件的,即po ...

  3. andorid jar/库源码解析之EventBus

    目录:andorid jar/库源码解析 EventBus: 作用: 用于不同Activity,Service等之间传递消息(数据). 栗子: A页面:onCreate定义   EventBus.ge ...

  4. Android 开源项目源码解析(第二期)

    Android 开源项目源码解析(第二期) 阅读目录 android-Ultra-Pull-To-Refresh 源码解析 DynamicLoadApk 源码解析 NineOldAnimations ...

  5. Android事件总线(二)EventBus3.0源码解析

    1.构造函数 当我们要调用EventBus的功能时,比如注册或者发送事件,总会调用EventBus.getDefault()来获取EventBus实例: public static EventBus ...

  6. EventBus源码解析 源码阅读记录

    EventBus源码阅读记录 repo地址: greenrobot/EventBus EventBus的构造 双重加锁的单例. static volatile EventBus defaultInst ...

  7. 【Android】EventBus 源码解析

    EventBus 源码解析 本文为 Android 开源项目实现原理解析 中 EventBus 部分项目地址:EventBus,分析的版本:ccc2771,Demo 地址:EventBus Demo分 ...

  8. EventBus (三) 源码解析 带你深入理解EventBus

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/40920453,本文出自:[张鸿洋的博客] 上一篇带大家初步了解了EventBus ...

  9. Android EventBus源码解析 带你深入理解EventBus

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/40920453,本文出自:[张鸿洋的博客] 上一篇带大家初步了解了EventBus ...

随机推荐

  1. 让服务器可以下载apk和ipa文件

    选中指定网站→在右侧找到 MIME类型 → 双击进入已有类型页 → 点击最右侧添加 apk    application/vnd.android.package-archive MRP文件(国内普遍的 ...

  2. cocos2d-js 粒子系统使用自定义图片,还原原来的图片宽高

    粒子系统使用自定义图片很简单只需要在plist最后一行设置png的名称即可.但是,在实际使用中,发现自定义图片无法使用原来的形状,例如设置了一长条的图片,结果出来确实一个个圆球. 翻了plist和cc ...

  3. Windows下MySQL 5.7安装记录

    软件下载 环境:Windows 7 旗舰版 64位 MySQL版本:mysql-5.7.3.0-winx64 MySQL下载地址:http://dev.mysql.com/downloads/inst ...

  4. 【Linux】文件权限

    Linux的每一个文件都跟多种类型相关联.在这些权限中,我们通常需要和三类权限打交道(用户.用户组以及其他实体). 1.文件权限查看ls –l Linux:/qinys # ls -l total 6 ...

  5. sql如何通过当前日期获取上周,上上周,上上上周的起始日期(周一_周七)

    当前时间周的起始日期(以周一为例)select DATEADD(week,DATEDIFF(week,0,getdate()),0)上周起始:select dateadd(week,-1,DATEAD ...

  6. Android开发之使用Intent进行自定义类型数据传输

    一.引言 我相信一定有人想通过Intent传送自定义类型的数据,但是苦于无法找到putExtra(String name,Object value)这个方法,最后都会妥协,采用字典或者全局变量来 解决 ...

  7. javascript库之Mustache库使用说明

    一.简单示例 代码: function show(t) { $("#content").html(t); } var view = { title: 'YZF', cacl: fu ...

  8. 不同类型的磁盘存储在Ubuntu下的性能测试

    Ubuntu下通过lsusb判断USB存储是否是USB3.0: # 要查看Seagate这个移动硬盘 lsusb 或者 lsusb -t $ lsusb Bus Device : ID : Intel ...

  9. 如何解决input file 选取相同文件后,change事件不起作用解决方法

    两种方法 1.在你的input所属的form表单reset()就可以了! $("#avatorForm")[0].reset(); 2.设置你的input file value值为 ...

  10. MessageListActivity has leaked IntentReceiver

    1. 在MessagelistActivity中出现has leaked IntentReceiver的异常.异常日志如下. 07-15 08:09:53.211: E/ActivityThread( ...