EventBus索引加速探究,事件分发机制面试题

注释2:将 订阅者对象 和注释1处获取到的FindState对象绑定,代码是这样的:

void initForSubscriber(Class<?> subscriberClass) {

this.subscriberClass = clazz = subscriberClass;

skipSuperClasses = false;

subscriberInfo = null; // 这里 subscriberInfo置null

}

注释3:通过 getSubscriberInfo(findState)来获取这个 订阅者的subscriberInfo

注释4: 如果subscriberInfo存在,就从 subscriberInfo中获取 @Subscribe方法

注释5:如果subscriberInfo不存在,就调用 findUsingReflectionInSingleClass(findState) 反射+暴力的方法寻找,就是一开始的findUsingReflection()

显然,注释3的获取的东西,可以验证我们有没有开启 索引加速。我们来看看这个方法:

// SubscriberMethodFiner.java

private SubscriberInfo getSubscriberInfo(FindState findState) {

if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) { // 1

SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();

if (findState.clazz == superclassInfo.getSubscriberClass()) {

return superclassInfo;

}

}

if (subscriberInfoIndexes != null) { // 2

for (SubscriberInfoIndex index : subscriberInfoIndexes) {

SubscriberInfo info = index.getSubscriberInfo(findState.clazz);

if (info != null) {

return info;

}

}

}

return null;

}

注释1: 如果 findState.subcriberInfo 不为null,且其内容也不为null,就把那些东西取出来。

在上面 FindState的初始化,我们看到了会把它的 subcriberInfo置为null,所以如果是第一次执行,注释1的条件是会判断会false。就会往注释2走。

注释2:判断 subscriberInfoIndexes是否为空,如果不为空,则遍历所有,找到一个和 订阅者相匹配的 SubscriberInfo返回回去。

subscriberInfoIndexes是什么?怎么来的呢?

首先它是一个 List<SubscriberInfoIndex>类型,它是一个列表,里面放的是 “订阅者索引”。

它在 EventBus的构造方法中被创建:

EventBus(EventBusBuilder builder) {

subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,

builder.strictMethodVerification, builder.ignoreGeneratedIndex);

}

可以看出它是来自于 EventBusBuilder,而在EventBusBuilder中, 这个对象又是怎么产生的呢,我通过源码,发现它只有一处地方用来操作这个列表:

// EventBusBuidler.java

/** Adds an index generated by EventBus’ annotation preprocessor. */

public EventBusBuilder addIndex(SubscriberInfoIndex index) {

if (subscriberInfoIndexes == null) {

subscriberInfoIndexes = new ArrayList<>();

}

subscriberInfoIndexes.add(index);

return this;

}

通过 addIndex()SubscriberInfoIndex 往这个列表里面加。再往上找,没有了,addIndex()没有出现在任意一处被调用的地方。

也就是说,这个方法,是外部调用的。

它的英文注释我翻译一下:通过EventBus的注解处理器添加一个索引

这就说明了这个方法,是由注解器产生的Java文件调用。这也说明了,如果我们不使用注解处理器,EventBus寻找 @Subcriber的做法,永远是 反射 + 遍历。

2. 通过EventBusAnnotationProcessor生成Java文件

==========================================================================================================

EventBusAnnotationProcessor 需要引入,示例代码如下:

// build.gradle

android {

defaultConfig {

javaCompileOptions {

annotationProcessorOptions {

arguments = [ eventBusIndex : ‘com.example.myapp.MyEventBusIndex’ ]

}

}

}

}

dependencies {

def eventbus_version = ‘3.2.0’

implementation “org.greenrobot:eventbus:$eventbus_version”

annotationProcessor “org.greenrobot:eventbus-annotation-processor:$eventbus_version”

}

然后在项目中,写入 @Subscribe方法:

public class MainActivity extends AppCompatActivity {

@Subscribe(threadMode = ThreadMode.MAIN)

public void onMainActivityEvent(MainActivityEvent event){

}

}

点击编译,就会在下图目录中看到编译时生成的文件:

在这里插入图片描述

来看下文件内容:

public class MyEventBusIndex implements SubscriberInfoIndex {

private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;

static {

SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>(); // 1

putIndex(new SimpleSubscriberInfo(MainActivity.class, true, new SubscriberMethodInfo[] {

new SubscriberMethodInfo(“onMainActivityEvent”, MainActivityEvent.class, ThreadMode.MAIN),

})); // 2

}

private static void putIndex(SubscriberInfo info) {

SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);

}

@Override

public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) { // 3

SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);

if (info != null) {

return info;

} else {

return null;

}

}

}

注释1: 在静态代码块中, 创建一个 Map,key是所有的订阅者, value 是该订阅者所有的订阅方法

注释2:对每个订阅者,将其内部所有的 @Subscribe方法添加其 SubscriberMethodInfo[] 这个数组中,并封装到 SubscriberInfo中,put到注释1的Map中。

注释3:getSubscriberInfo()用来拿到对应订阅者的信息,在第一节中的 SubscriberMethodFiner.getSubscriberInfo()里面,我们看到的正是调用了这个方法。

但是如果我们默认情况下使用 EventBus.getDefault().register()是不会调用到这个方法的,因为默认方法并不会 赋值给subscriberInfoIndexes,所以我们要手动加入:

EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus(); // 1

EventBus.getDefault().register(this);

注释1:通过 addIndex()把生成出来的Java文件加入进去,它会给 subscriberInfoIndexes赋值。这个方法只能使用一次,建议在Application的onCreate中使用。

到这里,索引的用法就结束了。通过使用索引,把搜索订阅方法的做法放在了编译时做,相比于运行时暴力搜索,性能相比可见一斑。官方给出了在Nexus5上性能图:

在这里插入图片描述

EventBus3.0在没有使用注解生成器的性能比2.x都低(我现在就是处在这个位置),但是使用索引之后,速度能快到究极独一档。所以没什么好说的,赶紧用了。

接下来我要分析一下EventBusAnnotationProcessor的源码,这部分不感兴趣的同学可以不看了。

3.EventBusAnnotationProcessor部分源码分析

====================================================================================================

3.1 process方法


AnnotationProcessor的关键地方都是在与 process()是怎么生成Java文件的,来看下它的 process()

// EventBusAnnotationProcessor.java

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {

Messager messager = processingEnv.getMessager(); // 1

try {

String index = processingEnv.getOptions().get(OPTION_EVENT_BUS_INDEX); // 2

verbose = Boolean.parseBoolean(processingEnv.getOptions().get(OPTION_VERBOSE)); // 3

int lastPeriod = index.lastIndexOf(‘.’);

String indexPackage = lastPeriod != -1 ? index.substring(0, lastPeriod) : null; // 4

collectSubscribers(annotations, env, messager); // 5

checkForSubscribersToSkip(messager, indexPackage); // 6

if (!methodsByClass.isEmpty()) {

createInfoIndexFile(index); // 7

} …

} …

}

process() 仅留下比较关键的代码,其他非关键代码更多是容错相关。

注释1: 获取信使,它用来打印log,因为AnnotationProcessor组件是Java的,所以不能使用Android的Log工具来打印,它就起到这么一个作用。

注释2:OPTION_EVENT_BUS_INDEX就是我们在 gradle文件中写的 : [eventBusIndex : 'xxx']的内容,通过注释2的方法,可以拿到当前项目EventBus的 eventBusIndex。它的作用是用来创建索引文件。

注释3:拿到verbose值,它同样在 gralde中拿,但是我们导包时没有写出来,annotationProcessor的完整导包是:

javaCompileOptions {

annotationProcessorOptions {

arguments = [ eventBusIndex : ‘com.example.myapp.MyEventBusIndex’

verbose : ‘true’]

}

}

这个 verbose如果为true,那我们注释1的信使就会在 编译时打印编译这些代码的log,如果为false则不打印。所以这个字段并不重要,我们一般都会忽略。

注释4:根据注释2拿到的索引,生成一个包名,就是新生成的Java文件的所在包的包名。

注释5:粗略收集所有的订阅者信息,因为订阅者是带有 @Subscribe注解的文件,所以 AnnotationProcessor能够扫描到。

注释6:检查注释5中的订阅者,如果他们不是public,或者说他们的内部包含的事件方法不是public,则去除掉这些订阅者。

注释7:创建索引文件

上面最终要的方法是注释5、注释7。

先来看下注释5。

3.2 collectSubscribers方法


// EventBusAnnotationProcessor.java

private final ListMap<TypeElement, ExecutableElement> methodsByClass = new ListMap<>();

private void collectSubscribers(Set<? extends TypeElement> annotations, RoundEnvironment env, Messager messager) {

// 1

for (TypeElement annotation : annotations) {

Set<? extends Element> elements = env.getElementsAnnotatedWith(annotation); // 2

for (Element element : elements) { // 3

if (element instanceof ExecutableElement) {

ExecutableElement method = (ExecutableElement) element; // 4

if (checkHasNoErrors(method, messager)) {

TypeElement classElement = (TypeElement) method.getEnclosingElement(); // 5

methodsByClass.putElement(classElement, method);// 6

}

} else {

messager.printMessage(Diagnostic.Kind.ERROR, “@Subscribe is only valid for methods”, element);

}

}

}

}

注释1: 遍历所有的 annotations,annotations是被AnnotationProcessor扫描项目扫出来的,这个Set只有一个元素,那就是 Subscribe。

注释2:拿到所有被 @Subscribe 标记的 元素(也就是方法)

注释3:遍历所有的 注释2中获取的元素

注释4:把 元素(Element) 转换成 方法(ExecutableElement)

注释5:getEnclosingElement()可以拿到方法的外部类,比如说我们例子的 onMainActivityEvent,它的外部信息,就是 MainAcitivty

注释6:把 订阅方法和其所在的类(订阅者)放到ListMap中。

collectSubscribers()这个方法最终可以整理出一个 Map,key是所有的订阅者,value是这个订阅者里面的订阅方法。

接下来就是把上面的信息写成一个文件。就是 createInfoIndexFile()

3.3 createInfoIndexFile方法


它就是生成文件的方法:

private void createInfoIndexFile(String index) {

BufferedWriter writer = null;

try {

JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(index);

int period = index.lastIndexOf(‘.’);

String myPackage = period > 0 ? index.substring(0, period) : null;

String clazz = index.substring(period + 1);

writer = new BufferedWriter(sourceFile.openWriter());

if (myPackage != null) {

writer.write("package " + myPackage + “;\n\n”);

}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

尾声

面试成功其实都是必然发生的事情,因为在此之前我做足了充分的准备工作,不单单是纯粹的刷题,更多的还会去刷一些Android核心架构进阶知识点,比如:JVM、高并发、多线程、缓存、热修复设计、插件化框架解读、组件化框架设计、图片加载框架、网络、设计模式、设计思想与代码质量优化、程序性能优化、开发效率优化、设计模式、负载均衡、算法、数据结构、高级UI晋升、Framework内核解析、Android组件内核等。

不仅有学习文档,视频+笔记提高学习效率,还能稳固你的知识,形成良好的系统的知识体系。这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。

Android进阶学习资料库

一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!

image

大厂面试真题

PS:之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

《2017-2021字节跳动Android面试历年真题解析》

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

月最新录制的,相信这份视频能给你带来不一样的启发、收获。

[外链图片转存中…(img-X7BCjQUA-1712487102852)]

Android进阶学习资料库

一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!

[外链图片转存中…(img-cyadMQJk-1712487102853)]

大厂面试真题

PS:之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

[外链图片转存中…(img-ovcadsaC-1712487102853)]

《2017-2021字节跳动Android面试历年真题解析》

[外链图片转存中…(img-zuRWZiUe-1712487102853)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 16
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值