Eventbus异步框架源码解析(一)

Stella981
• 阅读 666

Eventbus异步框架源码解析大体流程。

EventBus 工作原理

Eventbus异步框架源码解析(一)

Eventbus异步框架源码解析(一)

Eventbus异步框架源码解析(一)

Eventbus异步框架源码解析(一)

消息发布和订阅模型

生产者:发布者发布消息
消费者:订阅者注册消息
调度中心:执行消息的分发到位

线程切换

主要就是两个切换,主线程切子线程,子线程切主线程
一个进程内通过Looper.getMainLooper可以顺利切换到主线程
子线程通过是通过主线程执行某个线程,自动切换,也可以借助ThreadLocal缓存对象来切换

参考:Android线程消息机制

EventBus 类图

  • EventBus:核心类,代表了一个事件总线。实现发布-订阅模式。

  • SubscriberMethodFinder:寻找订阅者的订阅方法

  • FindState:寻找订阅方法过程中的缓存Buffer,目的内存复用

  • SubscriberMethod:订阅者的方法封装类

  • Subscription:订阅者对象的封装

  • ThreadMode:线程模型,用来标识不同线程执行消息

  • PostingThreadState:消息发布状态封装

  • Poster:不同线程消息发布器

EventBus 订阅-发布时序图

EventBus 订阅-发布流程图

订阅流程源码分析

EventBus.getDefault

获取EventBus单例,

// 提供默认单例 EventBus

EventBus.register

  1. 获取订阅者Class

  2. 通过反射,获取Class的所有订阅方法集合

  3. 遍历订阅方法集合,存储两个Map,Map<订阅事件类型,订阅对象集合>, Map<订阅者,订阅事件集合>

  4. 如果订阅的是Stick事件类型,则直接寻找对应事件,然后分发到订阅方法

    public void register(Object subscriber) {

SubscriberMethodFinder.findSubscriberMethods

  1. 先从内存缓存 Map<订阅对象Class,订阅对象Class的订阅方法> 找

  2. 然后通过反射方法获取订阅者Class的所有订阅方法

  3. Map 存储

  4. 返回订阅者的订阅方法集合

    List findSubscriberMethods(Class<?> subscriberClass) {

SubscriberMethodFinder.findUsingInfo

  1. 通过FIND_STATE_POOL构建FindState,用做找寻订阅方法时的缓存对象

  2. 执行反射寻找订阅者的订阅方法,存储到FindState中

  3. 循环遍历找订阅者的父类的订阅方法

  4. 返回订阅者的订阅方法集合,并释放FindState

    private List findUsingInfo(Class<?> subscriberClass) {

SubscriberMethodFinder.findUsingReflectionInSingleClass
遍历类中所有方法,寻找目标订阅方法,封装成SubscriberMethod存储到findState的subscriberMethods集合中

private void findUsingReflectionInSingleClass(FindState findState) {

EventBus.subscribe

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {

发布流程源码分析

EventBus.post

参考:深入理解ThreadLocal

public void post(Object event) {

EventBus.postSingleEvent

根据eventInheritance确认是否需要分发父类Class,最终执行postSingleEventForEventType 进行事件分发

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {

EventBus.postSingleEventForEventType

  1. 获取订阅对象集合

  2. 遍历订阅对象,执行事件分发

    private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) { // 获取 订阅对象集合 CopyOnWriteArrayList subscriptions; synchronized (this) { 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;}

EventBus.postToSubscription
根据不同的 ThreadMode 进行处理

  1. 执行线程与目标线程匹配,则直接反射执行订阅方法

  2. 不匹配,则先将事件添加到PendingPostQueue,然后通过ExecutorService执行Runnable

    private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) { // 根据不同的 ThreadMode 进行处理 switch (subscription.subscriberMethod.threadMode) { case POSTING: invokeSubscriber(subscription, event); break; case MAIN: if (isMainThread) { invokeSubscriber(subscription, event); } else { mainThreadPoster.enqueue(subscription, event); } break; case MAIN_ORDERED: 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: asyncPoster.enqueue(subscription, event); break; default: throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode); }}// 执行订阅者的订阅方法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); }}

BackgroundPoster
以BackgroundPoster为例,进行代码分析

  1. 构建PendingPost,然后加入PendingPostQueue队列,接着调用线程执行器执行线程

  2. 线程执行,循环获取PendingPost,调用EventBus.invokeSubscriber反射执行订阅者的订阅方法

    final class BackgroundPoster implements Runnable, Poster { ... // 入队 public void enqueue(Subscription subscription, Object event) { // 将 订阅者和事件封装为一个 PendingPost 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); 轮训取 post,1s没有则停止执行 if (pendingPost == null) { synchronized (this) { // Check again, this time in synchronized pendingPost = queue.poll(); if (pendingPost == null) { executorRunning = false; return; } } } // 最后执行eventBus的invokeSubscriber方法 eventBus.invokeSubscriber(pendingPost); } } ... }}

本文分享自微信公众号 - Android架构师成长之路(gh_07f996f00d9b)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Easter79 Easter79
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这