android EventBus详解(二)

简介: 上一节讲了EventBus的使用方法和实现的原理,下面说一下EventBus的Poster只对粘滞事件和invokeSubscriber()方法是怎么发送的。 Subscribe流程 我们继续来看EventBus类,分析完了包含的属性,接下来我们看入口方法register() 通过查看源码我们发现,所有的register()方法,最后都会直接或者间接的调用regi

上一节讲了EventBus的使用方法和实现的原理,下面说一下EventBus的Poster只对粘滞事件和invokeSubscriber()方法是怎么发送的。

Subscribe流程

我们继续来看EventBus类,分析完了包含的属性,接下来我们看入口方法register()

通过查看源码我们发现,所有的register()方法,最后都会直接或者间接的调用register()方法

/**
 * @param subscriber 订阅者对象
 * @param sticky     是否粘滞
 * @param priority   优先级
 */
private synchronized void register(Object subscriber, boolean sticky, int priority) {
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods
            (subscriber.getClass());
    for (SubscriberMethod subscriberMethod : subscriberMethods) {
        subscribe(subscriber, subscriberMethod, sticky, priority);
    }
}

SubscriberMethod类

出现了一个SubscriberMethod类,看看它是干嘛的:

看字面意思是订阅者方法,看看类中的内容,除了复写的equals()和hashCode()就只有这些了。

final Method method; //方法名
final ThreadMode threadMode; //工作在哪个线程
final Class<?> eventType; //参数类型
/** Used for efficient comparison */
String methodString;

private synchronized void checkMethodString() {
    if (methodString == null) {
        // Method.toString has more overhead, just take relevant parts of the method
        StringBuilder builder = new StringBuilder(64);
        builder.append(method.getDeclaringClass().getName());
        builder.append('#').append(method.getName());
        builder.append('(').append(eventType.getName());
        methodString = builder.toString();
    }
}

ThreadMode是一个枚举类,是不是应该换成 int 更好呢。 checkMethodString()方法就是为了设置变量 methodString 的值,这里new了一个StringBuilder,然后又调用了toString()返回,是不是应该改成直接new String(format...)更好呢? 
OK,不管那些细节,看到这里就知道,其实这个类也就是一个封装了的方法名而已。

回到EventBus#register()咱们继续. 噢,又遇到了SubscriberMethodFinder这又是啥,继续去看。

SubscriberMethodFinder类

从字面理解,就是订阅者方法发现者。
回想一下,我们之前用 EventBus 的时候,需要在注册方法传的那个 this 对象里面写一个 onEvent() 方法。没错,SubscriberMethodFinder类就是查看传进去的那个 this 对象里面有没有onEvent()方法的。怎么做到的?当然是反射。而且这个类用了大量的反射去查找类中方法名。

先看他的变量声明

private static final String ON_EVENT_METHOD_NAME = "onEvent";

/**
 * 在较新的类文件,编译器可能会添加方法。那些被称为BRIDGE或SYNTHETIC方法。
 * EventBus必须忽略两者。有修饰符没有公开,但在Java类文件中有格式定义
 */
private static final int BRIDGE = 0x40;
private static final int SYNTHETIC = 0x1000;
//需要忽略的修饰符
private static final int MODIFIERS_IGNORE = Modifier.ABSTRACT | Modifier.STATIC | BRIDGE |
        SYNTHETIC;

//key:类名,value:该类中需要相应的方法集合
private static final Map<String, List<SubscriberMethod>> methodCache = new HashMap<String,
        List<SubscriberMethod>>();

//跳过校验方法的类(即通过构造函数传入的集合)
private final Map<Class<?>, Class<?>> skipMethodVerificationForClasses;

有一句注释

In newer class files, compilers may add methods. Those are called bridge or synthetic methods. EventBus must ignore both. There modifiers are not public but defined in the Java class file format: http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.6-200-A.1

翻译过来大概就是说java编译器在编译的时候,会额外添加一些修饰符,然后这些修饰符为了效率应该是被忽略的。

还有一个skipMethodVerificationForClasses,看到注释是需要跳过被校验方法的类,校验方法是什么?看看他是干什么的。findSubscriberMethods()方法有点长,咱们抽一点看。 跳过上面的那些临时变量,从while循环里开始看:

Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
    String methodName = method.getName();
    if (methodName.startsWith(ON_EVENT_METHOD_NAME)) {
        int modifiers = method.getModifiers();//方法的修饰符
        //如果是public,且 不是之前定义要忽略的类型
        if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
            //。。。先不看
        }
    }
}
clazz = clazz.getSuperclass();

首先是反射获取到 clazz 的全部方法 methods。
通过对全部的方法遍历,为了效率首先做一次筛选,只关注我们的以 “onEvent” 开头的方法。(现在知道之前在基础用法中我说:其实命名不一定必须是onEvent()的原因了吧,因为只要是onEvent开头的就可以了。) 
忽略private类型的,最后如果是公有,并且不是 java编译器 生成的方法名,那么就是我们要的了。

再来看拿到要的方法后是怎么处理的

Class<?>[] parameterTypes = method.getParameterTypes();
//如果只有一个参数
if (parameterTypes.length == 1) {
    String modifierString = methodName.substring(ON_EVENT_METHOD_NAME
            .length());
    ThreadMode threadMode;
    if (modifierString.length() == 0) {
        threadMode = ThreadMode.PostThread;
    } else if (modifierString.equals("MainThread")) {
        threadMode = ThreadMode.MainThread;
    } else if (modifierString.equals("BackgroundThread")) {
        threadMode = ThreadMode.BackgroundThread;
    } else if (modifierString.equals("Async")) {
        threadMode = ThreadMode.Async;
    } else {
        if (skipMethodVerificationForClasses.containsKey(clazz)) {
            continue;
        } else {
            throw new EventBusException("Illegal onEvent method, check " +
                    "for typos: " + method);
        }
    }
    Class<?> eventType = parameterTypes[0];
    methodKeyBuilder.setLength(0);
    methodKeyBuilder.append(methodName);
    methodKeyBuilder.append('>').append(eventType.getName());
    String methodKey = methodKeyBuilder.toString();
    if (eventTypesFound.add(methodKey)) {
        // 方法名,工作在哪个线程,事件类型
        subscriberMethods.add(new SubscriberMethod(method, threadMode,
                eventType));
    }
}

还是反射,拿到这个方法的全部参数集合,如果是只有一个参数,再去根据不同的方法名赋予不同的线程模式(其实也就是最后响应的方法是工作在哪个线程)。
这里我们看到,其实EventBus不仅仅支持onEvent()的回调,它还支持onEventMainThread()onEventBackgroundThread()onEventAsync()这三个方法的回调。
一直到最后,我们看到这个方法把所有的方法名集合作为value,类名作为key存入了 methodCache 这个全局静态变量中。意味着,整个库在运行期间所有遍历的方法都会存在这个 map 中,而不必每次都去做耗时的反射取方法了。

synchronized (methodCache) {
    methodCache.put(key, subscriberMethods);
}
return subscriberMethods;

看了这么久,我们再回到 EventBus#register() 方法。这回可以看懂了,就是拿到指定类名的全部订阅方法(以 onEvent 开头的方法),并对每一个方法调用subscribe()。那么再看subscribe()方法。

事件的处理与发送subscribe()

subscribe()方法接受四个参数,分别为:订阅者封装的对象、响应方法名封装的对象、是否为粘滞事件(可理解为广播)、这条事件的优先级。

//根据传入的响应方法名获取到响应事件(参数类型)
Class<?> eventType = subscriberMethod.eventType;
Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);
//通过响应事件作为key,并取得这个事件类型将会响应的全部订阅者
//没个订阅者至少会订阅一个事件,多个订阅者可能订阅同一个事件(多对多)
//key:订阅的事件,value:订阅这个事件的所有订阅者集合
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);

//根据优先级插入到订阅者集合中
int size = subscriptions.size();
for (int i = 0; i <= size; i++) {
    if (i == size || newSubscription.priority > subscriptions.get(i).priority) {
        subscriptions.add(i, newSubscription);
        break;
    }
}

//当前订阅者订阅了哪些事件
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
    subscribedEvents = new ArrayList<Class<?>>();
    typesBySubscriber.put(subscriber, subscribedEvents);
}
//key:订阅者对象,value:这个订阅者订阅的事件集合
subscribedEvents.add(eventType);

跳过一些初始化的局部变量(逻辑看注释就够了)

如果传入的事件是有优先级之分的,则会根据优先级,将事件插入所有订阅了事件eventType的类的集合subscriptions中去。看逻辑我们发现,这里并没有对优先级的大小做限制,默认的优先级是0,priority越大,优先级越高。
每个订阅者是可以有多个重载的onEvent()方法的,所以这里多做了一步,将所有订阅者的响应方法保存到subscribedEvents中。
至此,我们就知道了 EventBus 中那几个map的全部含义。同时也回答了上一篇中问的为什么如果EventBus.defaultInstance不为null以后程序要抛出异常,就是因为这几个 map 不同了。 map 变了以后,订阅的事件就全部变为另一个 EventBus 对象的了,就没办法响应之前那个 EventBus 对象的订阅方法了。

最后又是一个感叹:子事件也可以让响应父事件的 onEvent() 。这个有点绕,举个例子,订阅者的onEvent(CharSequence),如果传一个String类型的值进去,默认情况下是不会响应的,但如果我们在构建的时候设置了 eventInheritance 为 true ,那么它就会响应了。

最后又是一个感叹:子事件也可以让响应父事件的 onEvent() 。这个有点绕,举个例子,订阅者的onEvent(CharSequence),如果传一个String类型的值进去,默认情况下是不会响应的,但如果我们在构建的时候设置了 eventInheritance 为 true ,那么它就会响应了。

if(sticky)
if (eventInheritance) {
    Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
    for (Map.Entry<Class<?>, Object> entry : entries) {
        Class<?> candidateEventType = entry.getKey();
        //如果eventtype是candidateEventType同一个类或是其子类
        if (eventType.isAssignableFrom(candidateEventType)) {
            Object stickyEvent = entry.getValue();
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }
    }
} else {
    Object stickyEvent = stickyEvents.get(eventType);
    checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}

最后是调用checkPostStickyEventToSubscription()做一次安全判断,就调用postToSubscription()发送事件了。
这里就关联到了我们之前讲的Poster类的作用了。
回答之前的问题:Poster只负责粘滞事件的代码。这里可以回答一部分:如果不是 sticky 事件都直接不执行了,还怎么响应。

private void postToSubscription(...) {
    switch (threadMode) {
        case PostThread:
            //直接调用响应方法
            invokeSubscriber(subscription, event);
            break;
        case MainThread:
            //如果是主线程则直接调用响应事件,否则使用handle去在主线程响应事件
            if (isMainThread) {
                invokeSubscriber(subscription, event);
            } else {
                mainThreadPoster.enqueue(subscription, event);
            }
            break;
            //。。。
    }
}

最后,还记得我们之前没有讲的那个invokeSubscriber(subscription, event);方法吗? 之前我们不知道subscriberMethod是什么,现在我们能看懂了,就是通过反射调用订阅者类subscriber的订阅方法onEventXXX(),并将event作为参数传递进去

subscription.subscriberMethod.method.invoke(subscription.subscriber, event);

Register与Poster工作图

原理图

开源实验室:图6

流程图

完整的注册流程
开源实验室:图7

至此,整个EventBus从注册订阅到事件的处理到响应的过程我们都分析完了,最后就只剩下发送流程和取消注册了。


目录
相关文章
|
Android开发
android EventBus详解(一)
EventBus 是一款针对Android优化的发布/订阅事件总线。主要功能是替代Intent, Handler, BroadCast 在 Fragment,Activity,Service,线程之间传递消息.优点是开销小,使用方便,可以很大程度上降低它们之间的耦合,使得我们的代码更加简洁,耦合性更低,提升我们的代码质量。 类似的库还有 Otto ,今天就带大家一起研读 EventB
1189 0
|
Android开发 存储 缓存
android EventBus详解(三)
post()方法调用流程 我们继续来看EventBus类,的另一个入口方法post() //已省略部分代码 public void post(Object event) { PostingThreadState postingState = currentPostingThreadState.get(); List&lt;Object&gt; eventQu
1133 0
|
3天前
|
JavaScript Linux 网络安全
Termux安卓终端美化与开发实战:从下载到插件优化,小白也能玩转Linux
Termux是一款安卓平台上的开源终端模拟器,支持apt包管理、SSH连接及Python/Node.js/C++开发环境搭建,被誉为“手机上的Linux系统”。其特点包括零ROOT权限、跨平台开发和强大扩展性。本文详细介绍其安装准备、基础与高级环境配置、必备插件推荐、常见问题解决方法以及延伸学习资源,帮助用户充分利用Termux进行开发与学习。适用于Android 7+设备,原创内容转载请注明来源。
50 18
|
1月前
|
安全 Android开发 iOS开发
escrcpy:【技术党必看】Android开发,Escrcpy 让你无线投屏新体验!图形界面掌控 Android,30-120fps 超流畅!🔥
escrcpy 是一款基于 Scrcpy 的开源项目,使用 Electron 构建,提供图形化界面来显示和控制 Android 设备。它支持 USB 和 Wi-Fi 连接,帧率可达 30-120fps,延迟低至 35-70ms,启动迅速且画质清晰。escrcpy 拥有丰富的功能,包括自动化任务、多设备管理、反向网络共享、批量操作等,无需注册账号或广告干扰。适用于游戏直播、办公协作和教育演示等多种场景,是一款轻量级、高性能的 Android 控制工具。
|
1月前
|
JavaScript 搜索推荐 Android开发
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
69 8
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
|
1月前
|
Dart 前端开发 Android开发
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
57 4
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
1月前
|
前端开发 Java Shell
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
182 20
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
2月前
|
缓存 前端开发 Android开发
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
117 12
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
|
2月前
|
Dart 前端开发 Android开发
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
44 1
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
3月前
|
开发框架 Android开发 iOS开发
安卓与iOS开发中的跨平台策略:一次编码,多平台部署
在移动应用开发的广阔天地中,安卓和iOS两大阵营各占一方。随着技术的发展,跨平台开发框架应运而生,它们承诺着“一次编码,到处运行”的便捷。本文将深入探讨跨平台开发的现状、挑战以及未来趋势,同时通过代码示例揭示跨平台工具的实际运用。
194 3

热门文章

最新文章