前言

  在前面,我们探讨了如何在自己的代码中引入EventBus,进行基本的事件分发/监听;对注册观察者与事件发送的过程进行了浅析。从之前的学习中,我们了解到,EventBus一共有4种onEvent方法,要根据实际需求的不同选用不同的事件处理方法。本篇blog中,我们集中研究一下这四种事件处理方法内部分别做了什么事情,是如何实现的。

  本篇会较多涉及java中的并发/多线程技术,这部分基础不够扎实的朋友,可以趁机黑练一下。(其实是说我自己 -_-!)

post的最后一步

  EventBus.post 是分发事件时调用的方法,post时,根据EventType找到对应的Subscription列表,遍历列表依次调用postToSubscription。方法如下

    private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
case PostThread:
invokeSubscriber(subscription, event);
break;
case MainThread:
if (isMainThread) {
invokeSubscriber(subscription, event);
} else {
mainThreadPoster.enqueue(subscription, event);
}
break;
case BackgroundThread:
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提供的4种消息处理方式(ThreadMode)进行switch/case,这4种ThreadMode的含义,在之前的文章中已经给出过说明。结合代码,能看出在这四个case语句里面,处理方式无非以下两种:

  1. 若当前线程是ThreadMode所指明的线程,直接调用 invokeSubscriber(subscription, event);
  2. 当前线程不是ThreadMode所指明的线程,将 subscription、event 加入到目标线程的队列中(enqueue)。

  接下来逐一分析这两种处理方式。

交由当前线程处理

  从方法名与方法所起的作用来看,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);
}
}

  如此简单,subscription中保存了之前解析出的以“onEvent”开头的方法,直接调用之。当然要catch住invoke可能会抛出的三个Exception:IllegalArgumentException, InvocationTargetException, IllegalAccessException,不知为何这里只catch了其中两个。我判断原因是,之前解析subscription时,已经就参数类型做过过滤了,可以保证传入的event一定是方法所需要的,所以就无需再去catch参数类型不匹配的Exception。

交由非当前线程处理

  在EventBus中,声明了如下三个Poster

    private final HandlerPoster mainThreadPoster;
private final BackgroundPoster backgroundPoster;
private final AsyncPoster asyncPoster;

  这三个Poster里,都有enqueue方法,用来将事件压入队列。但是这三种处理方式是略有不同的,依次来看。

HandlerPoster

final class HandlerPoster extends Handler {

    private final PendingPostQueue queue;
private final int maxMillisInsideHandleMessage;
private final EventBus eventBus;
private boolean handlerActive; HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {
super(looper);
this.eventBus = eventBus;
this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
queue = new PendingPostQueue();
} void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
queue.enqueue(pendingPost);
if (!handlerActive) {
handlerActive = true;
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
}
}
} @Override
public void handleMessage(Message msg) {
boolean rescheduled = false;
try {
long started = SystemClock.uptimeMillis();
while (true) {
PendingPost pendingPost = queue.poll();
if (pendingPost == null) {
synchronized (this) {
// Check again, this time in synchronized
pendingPost = queue.poll();
if (pendingPost == null) {
handlerActive = false;
return;
}
}
}
eventBus.invokeSubscriber(pendingPost);
long timeInMethod = SystemClock.uptimeMillis() - started;
if (timeInMethod >= maxMillisInsideHandleMessage) {
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
rescheduled = true;
return;
}
}
} finally {
handlerActive = rescheduled;
}
}
}

  HandlerPoster,人如其名,继承了Handler类,内部维护了一个PendingPostQueue,每当有时间enqueue时,判断当前Poster是否处于激活状态。对于未激活的将其激活。激活中的Poster,会通过sendMessage(obtainMessage())来依次处理PendingPostQueue中的事件,这是通过重载Handler中的handleMessage来做的。

BackgroundPoster

final class BackgroundPoster implements Runnable {

    private final PendingPostQueue queue;
private final EventBus eventBus; private volatile boolean executorRunning; BackgroundPoster(EventBus eventBus) {
this.eventBus = eventBus;
queue = new PendingPostQueue();
} public void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
queue.enqueue(pendingPost);
if (!executorRunning) {
executorRunning = true;
eventBus.getExecutorService().execute(this);
}
}
} @Override
public void run() {
try {
try {
while (true) {
PendingPost pendingPost = queue.poll(1000);
if (pendingPost == null) {
synchronized (this) {
// Check again, this time in synchronized
pendingPost = queue.poll();
if (pendingPost == null) {
executorRunning = false;
return;
}
}
}
eventBus.invokeSubscriber(pendingPost);
}
} catch (InterruptedException e) {
Log.w("Event", Thread.currentThread().getName() + " was interruppted", e);
}
} finally {
executorRunning = false;
}
} }

  与HandlerPoster不同的是,BackgroundPoster并没有继承自Handler,而是实现了Runnable接口。这么做的原因是,在用到BackgroudPoster的场合,必须是新建一个进程来处理事件,这里就使用了eventBus.getExecutorService().execute(this),从线程池里取出线程来执行。可以看到后面的AsyncPoster也是采用类似的方法。

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

  同样是用Runnable来实现,为啥AsyncPoster的代码比BackgroundPoster的简单这么多呢?仔细对比一下,就会发现BackgroudPoster中多出的代码,都是用来处理同步的。所以,原因在于,BackgroudPoster必须保证事件处理的顺序,先进先出。而AsyncQueue则没有这个顾虑。所以在需要按顺序处理事件的场合,就不要使用AsyncPoster啦!

小结

  本篇blog简单介绍了四种ThreadMode内部的机制,如果需要执行事件的线程是当前线程,则直接用反射调用方法;否则将事件压入对应Poster的队列中,依次(HandlerPoster,BackgroundPoster)或异步(AsyncPoster)执行。

下期预告

  从目录结构上,对EventBus项目进行整体解析。

[EventBus源码解析] 订阅者处理消息的四种ThreadMode的更多相关文章

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

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

  2. 【Android】EventBus 源码解析

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

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

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

  4. 时序数据库 Apache-IoTDB 源码解析之文件数据块(四)

    上一章聊到行式存储.列式存储的基本概念,并介绍了 TsFile 是如何存储数据以及基本概念.详情请见: 时序数据库 Apache-IoTDB 源码解析之文件格式简介(三) 打一波广告,欢迎大家访问Io ...

  5. springboot源码解析-管中窥豹系列之Initializer(四)

    一.前言 Springboot源码解析是一件大工程,逐行逐句的去研究代码,会很枯燥,也不容易坚持下去. 我们不追求大而全,而是试着每次去研究一个小知识点,最终聚沙成塔,这就是我们的springboot ...

  6. EventBus源码解析

    用例 本文主要按照如下例子展开: //1. 新建bus对象,默认仅能在主线程上对消息进行调度 Bus bus = new Bus(); // maybe singleton //2. 新建类A(sub ...

  7. Android EventBus源码解析

    项目地址 :https://github.com/greenrobot/EventBus 这个项目个人感觉就是为了解决回调事件过多的,比方说A函数在做完以后 要调用b类的c函数,那我们通常的做法就是 ...

  8. 76.Android之EventBus源码解析

    转载:http://p.codekk.com/blogs/detail/54cfab086c4761e5001b2538 1. 功能介绍 1.1 EventBus EventBus 是一个 Andro ...

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

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

随机推荐

  1. SunSonic 3.0 ORM开源框架的学习

    SubSonic 3.0简介 接触到SubSonic3.0 ORM框架是看了AllEmpty大神的从零开始编写自己的C#框架(链接在此)系列的随笔接触到的,本文章学习内容源于AllEmpty大神. S ...

  2. golang的第一个deadlock

    package main import (     "fmt"     "math/rand" ) func push(c chan []int) {      ...

  3. WCF 断点不会命中

    VS的调试模式改为Debug 工具—选项—调试—常规中的‘要求源文件和原始版本完全匹配’的勾去掉

  4. 拿到新机器,进行初始化和部署Nginx的过程

    1. 在/etc/ansbile/hosts中添加主机init 2. 在sysinit.yml中修改要初始化的机器:   hosts: init 3. 设置不检查key      export ANS ...

  5. PHP正则表达式的快速学习方法

    1.入门简介 简单的说,正则表达式是一种可以用于模式匹配和替换的强有力的工具.我们可以在几乎所有的基于UNIX系统的工具中找到正则表达式的身影,例如,vi编辑器,Perl或PHP脚本语言,以及awk或 ...

  6. How to browse the entire documentation using XCode 5 Documentation and API Reference ?

    file:///Users/yangiori/Library/Developer/Shared/Documentation/DocSets/com.apple.adc.documentation.Ap ...

  7. REDIS key notification

    Commands Clients Documentation Community Download Support License Join us in London October 19th for ...

  8. Centos7.2 Systemd 方式编译 Mysql5.7.11

    导读 MySQL 5.7 版本的发布,也就是说从现在开始5.7已经可以在生产环境中使用,有任何问题官方都将立刻修复. MySQL 5.7主要特性: 原生支持Systemd 更好的性能:对于多核CPU. ...

  9. 将Controller抛出的异常转到特定View

    <!-- 将Controller抛出的异常转到特定View --> <bean class="org.springframework.web.servlet.handler ...

  10. C#多线程网摘 1

    1.每个窗体都有自己的都在不同的线程上运行,如果需要在窗体之间交互,就需要在线程之间交互. 2.当线程Sleep时,系统就退出执行队列一段时间,当睡眠结束时,系统会产生一个时钟中断,从而使线程回到执行 ...