很久没有更新过源码解析类文章,以下内容作为源码分析类的笔记。分析方法适用于其它源码分析。

分析工具说明

许久以来,阅读源代码最得力的工具就非Source Insight莫属了。然,后来微软出了一款轻量级但功能强大的IDE就没Source Insight什么事了。微软的这款IDE就是大名鼎鼎的VSCODE,全名叫:Visual Studio Code。它的强大之处这里就不过多描述了。我们转回到正题。

获取源码并导入IDE

我们在Github.com上搜索到EventBus的项目地址,然后进行克隆即可。

    git clone https://github.com/greenrobot/EventBus.git

克隆完成之后,导入IDE。打开VS CODE,在菜单栏中选择File -> Open,选择克隆后文件所在的目录,然后点击确认。这就完成了代码导入工作。

由于我们只是分析EventBus的骨架脉络,所以我们要选择EventBus的早期版本。这里我们将要选择V1.0.1版。这同样适用于Android源代码分析,适宜选择1.5版或1.6版。

切换版本的步骤为:先进入EventBus刚才的克隆目录,然后执行以下步骤:

    //列出EventBus项目的所有tag。sahadevs-MacBook-Pro:EventBus sahadev$ git tagV1.0.1V2.2.0V2.3.0V2.4.0V3.0.0//上面可以看出EventBus共发布了5个版本。我们切换到V1.0.1版。sahadevs-MacBook-Pro:EventBus sahadev$ git checkout V1.0.1

经过以上步骤,我们的代码就切换到了V1.0.1版本。VS CODE中的内容也会同步更新。接下来开始我们的代码分析。

事件监听注册

我们熟知EventBus的用法,其中最有代表性的入口为:

     EventBus.getDefault().register(this);

为了方便分析,上面这行代码放在Activity中执行。所以上面的this就是Activity的实例。EventBus采用了单例模式,在这个版本中,采用了最简便的实现方式:

public class EventBus {private static final EventBus defaultInstance = new EventBus();public static EventBus getDefault() {return defaultInstance;}
}

好,以上并不是我们的重点。我们要从register方法说起。

    public void register(Object subscriber) {register(subscriber, defaultMethodName, ThreadMode.PostThread);}public void register(Object subscriber, String methodName, ThreadMode threadMode) {List<Method> subscriberMethods = findSubscriberMethods(subscriber.getClass(), methodName);for (Method method : subscriberMethods) {Class<?> eventType = method.getParameterTypes()[0];subscribe(subscriber, method, eventType, threadMode);}}

register(this)方法执行时调用了register(Object subscriber, String methodName, ThreadMode threadMode),subscriber是Activity,methodName采用了defaultMethodName的值,这个值在EventBus中定义为:’onEvent’,threadMode为ThreadMode.PostThread。其中ThreadMode为一个枚举,它有两个值:

    public enum ThreadMode {/** Subscriber will be called in the same thread, which is posting the event. */PostThread,/** Subscriber will be called in Android's main thread (sometimes referred to as UI thread). */MainThread,/* BackgroundThread */}

PostThread用于标识发送线程,MainThread用于标识主线程。它们在后期执行的区别为:如果在注册时为PostThread,则事件发送后,会直接调起监听方法。而如果注册时为MainThread,则会经过主线程的消息队列调起。

好,回到刚才的地方,在register(Object subscriber, String methodName, ThreadMode threadMode)方法内先执行了findSubscriberMethods,我们移步看看这个方法内执行了什么工作:

    private List<Method> findSubscriberMethods(Class<?> subscriberClass, String methodName) {...List<Method> subscriberMethods;...subscriberMethods = new ArrayList<Method>();Class<?> clazz = subscriberClass;HashSet<Class<?>> eventTypesFound = new HashSet<Class<?>>();while (clazz != null) {String name = clazz.getName();...Method[] methods = clazz.getDeclaredMethods();for (Method method : methods) {if (method.getName().equals(methodName)) {Class<?>[] parameterTypes = method.getParameterTypes();if (parameterTypes.length == 1) {if (eventTypesFound.add(parameterTypes[0])) {// Only add if not already found in a sub classsubscriberMethods.add(method);}}}}clazz = clazz.getSuperclass();}...}

这里面的代码比较长,将不必要的略去。这段代码主要做了以下工作:

根据给定的类,遍历该类及其父类所有的方法,如果遍历时发现与给定的方法名相等,并且该方法的参数只有一个,那么就将该方法加入到subscriberMethods中,最后返回。这里被找到的方法是同名的,但是参数类型不同,这里的参数类型决定了将来要监听的事件类型。

好,我们回到findSubscriberMethods之前被调用的地方,继续往下走。接下来的动作是将findSubscriberMethods方法返回的Method列表进行遍历。而这次遍历时,将Method的第一个参数类型作为参数传给了subscribe方法:

    private void subscribe(Object subscriber, Method subscriberMethod, Class<?> eventType, ThreadMode threadMode) {CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);if (subscriptions == null) {subscriptions = new CopyOnWriteArrayList<Subscription>();subscriptionsByEventType.put(eventType, subscriptions);} ...subscriberMethod.setAccessible(true);Subscription subscription = new Subscription(subscriber, subscriberMethod, threadMode);subscriptions.add(subscription);...}

该方法首先做的就是根据eventType从subscriptionsByEventType对象中取出对应的subscriptions,然这里第一次肯定是null。所以先进行实例化,然后存放于subscriptionsByEventType中。而subscriptionsByEventType对象存放的就是根据eventType所对应的subscriptions。而subscriptions中存放的就是所有的观察者,待会有时间发生时,根据事件的类型,从subscriptionsByEventType中取出所有的观察者subscriptions逐一通知。

在创建了观察者列表subscriptions之后开始创建具体的观察者。Subscription是一个基本的类,它的示例只是持有了subscriber(Activity),subscriberMethod(onEvent),threadMode(ThreadMode.PostThread)三者的引用而已。

到这里,观察者Activity监听某一事件的注册工作就算完成了。

事件发送

接下来,我们将从事件发送入口post方法开始分析:

    public void post(Object event) {//这里的两个属性很重要,下面这个用于缓存事件,待下次事件执行时,一并执行。List<Object> eventQueue = currentThreadEventQueue.get();eventQueue.add(event);//这个用于标识是否正在发送事件,保证事件的顺序发送。防止多线程情况下的交叉执行。BooleanWrapper isPosting = currentThreadIsPosting.get();if (isPosting.value) {return;} else {isPosting.value = true;try {while (!eventQueue.isEmpty()) {postSingleEvent(eventQueue.remove(0));}} finally {isPosting.value = false;}}}

这里最开始有两个EventBus的成员属性,这里很重要:currentThreadEventQueue与currentThreadIsPosting。这两个属性本身是和线程有关系的。关于这两个属性内部的作用机制这里就不再赘述了。上面post方法的第一行currentThreadEventQueue.get()返回的是一个空的ArrayList()对象。而currentThreadIsPosting.get()方法返回的是一个BooleanWrapper对象,它的默认属性value默认是false。如果对这块的返回值有疑问,可以在EventBus.java文件内找到这两个对象的定义,它们在new时,通过initialValue方法返回了get获取到的初始值。

通过判断,最终进入postSingleEvent方法内,而该方法则是事件分发的核心:

    private void postSingleEvent(Object event) throws Error {List<Class<?>> eventTypes = findEventTypes(event.getClass());...int countTypes = eventTypes.size();for (int h = 0; h < countTypes; h++) {Class<?> clazz = eventTypes.get(h);CopyOnWriteArrayList<Subscription> subscriptions;synchronized (this) {subscriptions = subscriptionsByEventType.get(clazz);}if (subscriptions != null) {for (Subscription subscription : subscriptions) {if (subscription.threadMode == ThreadMode.PostThread) {postToSubscribtion(subscription, event);} else if (subscription.threadMode == ThreadMode.MainThread) {mainThreadPoster.enqueue(event, subscription);} else {throw new IllegalStateException("Unknown thread mode: " + subscription.threadMode);}}...}}...}

这个方法内首先调用了findEventTypes方法,我们先移步看看它做了什么工作:

    private List<Class<?>> findEventTypes(Class<?> eventClass) {...Class<?> clazz = eventClass;while (clazz != null) {eventTypes.add(clazz);addInterfaces(eventTypes, clazz.getInterfaces());clazz = clazz.getSuperclass();}return eventTypes;}

这部分的主要工作为:将指定类以及父类所实现的接口及其本身的类型加入到eventTypes中,最后将eventTypes返回。如果我们在定义事件类型时,没有继承任何类、实现任何接口,那么就会省去这个步骤,节省时间。

回到postSingleEvent方法继续往下看:根据刚刚返回的事件类型集合开始遍历,每次遍历时,根据给定的事件类型从subscriptionsByEventType中寻找对应的监听者。我们在上面分析注册部分时候已经见过,subscriptionsByEventType中存放的就是事件类型与监听者的键值对。找到监听者列表subscriptions之后,开始对subscriptions进行遍历,进行消息下发。

消息的发送主要两种模式:

  • PostThread
  • MainThread

我们在开头的时候就提到过,PostThread会通过反射直接调起对应的方法,而MainThread则会通过主线程的消息队列进行调起。我们来看看究竟是不是这样:

    //PostThread的执行方式static void postToSubscribtion(Subscription subscription, Object event) throws Error {...subscription.method.invoke(subscription.subscriber, event);...}//MainThread的执行方式void enqueue(Object event, Subscription subscription) {PendingPost pendingPost = PendingPost.obtainPendingPost(event, subscription);Message message = obtainMessage();message.obj = pendingPost;if (!sendMessage(message)) {throw new RuntimeException("Could not send handler message");}}

可以看到:对于PostThread类型,直接通过反射将事件传给了观察者。而对于MainThread类型,则是通过Handler将信息传给了主线程。最后消息到达主线程后,通过与PostThread方式一样经过postToSubscribtion方法执行。


以上就是关于EventBus事件监听注册、事件广播的解析过程。

EventBus1.0.1源码解析相关推荐

  1. Android8.0 bindService源码解析

    1.AMS的bindService 我们从调用bindService方法开始来看 bindService(intent,serviceConnection, Context.BIND_AUTO_CRE ...

  2. Android7.0 bindService源码解析

    看到标题的时候,有些同学可能会有些质疑:现在都Android12了,你讲Android7,是不是太过时了.这里有两个原因: (1)Android8.0和Android7.0的源码有些不同,但是Andr ...

  3. YYModel V1.0.4源码解析

    YYKit出现了很长时间了,一直想要详细解析一下它的源码,都是各种缘由推迟了. 最近稍微闲了一点,决定先从最简单的YYModel开始吧. 首先,我也先去搜索了一下YYModel相关的文章,解析主要AP ...

  4. 部署测试fabric1.0及源码解析

    开发环境 UBUNTU 16.04 LTS docker docker-compose git go 1.8以上 docker,docker-compose以及go的安装这里不再描述. 部署测试 新建 ...

  5. android9.0 UsbService源码解析

    文章目录 前言 一.服务启动 二.服务创建 三.系统就绪 四.系统启动完毕 前言 USBManager作为一接口类,客户端,当然要有一个服务端来支持工作,这个服务就是UsbService.我这里先从他 ...

  6. android9.0 UsbManager源码解析

    文章目录 前言 一.UsbManager是什么? 二.每个类的简介 总结 前言 安卓手机可以通过USB连接外设,比如键盘,鼠标,摄像头.还可以与电脑互联进行数据传输,加强了对外扩展的能力. 这功能无疑 ...

  7. react native 0.50 源码解析 再出发 持续更新

    1.核心类 1.1 RCTRootView 一个RCTRootView持有一个RCTBridge成员变量 RCTRootView : UIViewRCTBridge *bridge;UIViewCon ...

  8. [Dubbo3.0.8源码解析系列]-12-全局视野来看Dubbo3.0.8的服务启动生命周期

    目录 12 全局视野来看Dubbo3的服务启动生命周期 12.1 启动方法简介 12.2 启动器启动方法的调用逻辑start() 12.3 应用程序发布器DefaultApplicationDeplo ...

  9. semver 源码解析(Npm library)

    semver 源码解析(Npm library) 文章目录 semver 源码解析(Npm library) 正文 0. 基本信息 1. 源码解析 1.1 核心类型 1.2 SemVer 类型实现核心 ...

最新文章

  1. cursor的moveToNext()与moveToFirst()
  2. Long Number
  3. java knn分类_返回2个或更多最近邻居的KNN算法
  4. 信息学奥赛一本通 2028:【例4.14】百钱买百鸡
  5. Day27:threading模块
  6. BZOJ 2120: 数颜色
  7. spring扩展点五:factoryBean的使用
  8. SRIO的介绍和IP核解析
  9. 百科知识:呼叫转移与呼叫前转
  10. web前端之HTML5 入门(9):脚本、速查列表、URL、字符实体
  11. html5怎么做相册影集,手机怎么做相册影集
  12. CLIP Learning Transferable Visual Models From Natural Language Supervision
  13. 计算机网络专业以后装网线,一种便于安装的计算机网络用网线安装盒的制作方法...
  14. 万维网互联网计算机网络的区别,姜多多:万维网,互联网与因特网有什么区别?...
  15. opencv 锐化 java_Java Opencv 实现锐化
  16. 二叉树之前序遍历、中序遍历、后续遍历
  17. 电化学传感器原理回顾
  18. 啊,CET6 2020年12月
  19. 编码器—解码器和注意力机制
  20. [附源码]SSM计算机毕业设计宠物医院管理系统JAVA

热门文章

  1. Redis之简单动态字符串sds
  2. boost::function的用法(二)
  3. 电子工程学院的师兄弟姐们们,老师叫你们回家
  4. 笔记本电脑锁_2020年双11有哪些值得选购的笔记本电脑?(全能本/便携高性能笔记本电脑/设计本)...
  5. python循环套循环_零基础学python 14 循环套循环:循环的嵌套
  6. mft按钮设计_哈汽机组660MW超临界空冷机组ETS设计及逻辑说明
  7. 智慧交通day01-算法库01:numba
  8. python长沙_长沙python
  9. Mongodb 账户权限配置
  10. LeetCode MySQL 597. 好友申请 I :总体通过率