EventBus3.0 性能提升之添加索引

EventBus3.0 源码解析 一文中,在分析Subscriber的register()过程中,说到过一个方法方法findUsingReflectionInSingleClass,在该方法的作用是在RunTime期间通过反射获取Subscriber中的SubscriberMethod。

这样就会产生一个问题,在RunTime期间使用反射对程序运行的性能有较大影响。这里我们可以看看EventBus作者提供的一张图:

从上图中我们可以看出,RunTime时反射(EventBus3 No Index)的实现方式是性能最差的。为了避免这样的情况,EventBus3.0中增加了一个新特性:通过在编译期创建索引(SubscriberInfoIndex)以提高程序运行性能

所以,这里主要对EventBus中创建索引相关的功能进行分析。


配置使用索引

关于索引的配置,可以参考下EventBus官方文档:Subscriber Index
主要就两个步骤:

  1. 使用annotationProcessor并设置arguments
    arguments就是指定的生成的索引的全限定类名
  2. 创建EventBus实例并传入索引的实例

注解处理器EventBusAnnotationProcessor生成索引

在配置annotationProcessor之后,程序在编译期间就可以通过注解处理器生成一个SubscriberInfoIndex类(SubscriberInfoIndex类的名称就是由arguments指定),在该类中就存储了Subscriber以及相关SubscriberMethod。

现在,我们来分析下索引类是如何生成的,也就是EventBus中注解处理器EventBusAnnotationProcessor是如何工作的。
如何对于注解处理器的工作原理不是很清楚的话,可以先看看Java AbstractProcessor实现自定义ButterKnife

process()

   @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
        Messager messager = processingEnv.getMessager();
        try {
            // 1、获取build.gradle中配置的arguments(这个就是在编译后生成类的全限定类名)
            String index = processingEnv.getOptions().get(OPTION_EVENT_BUS_INDEX);
            if (index == null) {
                messager.printMessage(Diagnostic.Kind.ERROR, "No option " + OPTION_EVENT_BUS_INDEX +
                        " passed to annotation processor");
                return false;
            }

            // 省略部分代码

            // 2、获取每个SubSubscriber中所有的SubscribeMethod
            collectSubscribers(annotations, env, messager);
            // 3、获取所有的不需要被执行的SubSubscriber
            checkForSubscribersToSkip(messager, indexPackage);

            if (!methodsByClass.isEmpty()) {
                //4、创建类文件,初始化所有索引
                createInfoIndexFile(index);
            } else {
                messager.printMessage(Diagnostic.Kind.WARNING, "No @Subscribe annotations found");
            }
            writerRoundDone = true;

        } catch (RuntimeException e) {
            // IntelliJ does not handle exceptions nicely, so log and print a message
            e.printStackTrace();
            messager.printMessage(Diagnostic.Kind.ERROR, "Unexpected error in EventBusAnnotationProcessor: " + e);
        }
        return true;
    }

在process()方法中,首先获取SubscriberInfoIndex类的类名index,如何这个index为空的话,注解处理器就会执行结束也就不能生成SubscriberInfoIndex类,所以在build.gradle必须配置arguments。

获取类名后,开始获取所有的Subscriber以及相关SubscriberMethod,这主要通过collectSubscribers()方法完成。

collectSubscribers()

    private void collectSubscribers(Set<? extends TypeElement> annotations, RoundEnvironment env, Messager messager) {
        for (TypeElement annotation : annotations) {
            Set<? extends Element> elements = env.getElementsAnnotatedWith(annotation);
            for (Element element : elements) {
                if (element instanceof ExecutableElement) {
                    ExecutableElement method = (ExecutableElement) element;
                    if (checkHasNoErrors(method, messager)) {
                        // 获取Subscriber类
                        TypeElement classElement = (TypeElement) method.getEnclosingElement();
                        // 存储每个Subscriber中的SubscriberMethod
                        methodsByClass.putElement(classElement, method);
                    }
                } else {
                    messager.printMessage(Diagnostic.Kind.ERROR, "@Subscribe is only valid for methods", element);
                }
            }
        }
    }

collectSubscribers()方法,主要就是遍历获取所有的Subscriber及SubscriberMethod,并且通过methodsByClass储存起来。

methodsByClass是一个ListMap,Key代表一个Subscriber类,value是一个存储该Subscriber所有SubscriberMethod的list集合。

这里我们通过putElement()方法可以看出:


public synchronized void putElement(K key, V value) {
    C collection = map.get(key);
    if (collection == null) {
        collection = createNewCollection();
        map.put(key, collection);
    }
    collection.add(value);
}

collectSubscribers()

通过collectSubscribers()方法获取到所有的Subscriber后,需要对其中一些不可用的Subscriber做一个过滤,例如私有类型或者受保护的类型。

这个方法在一般情况下也不会使用,所以这里就不做过多的介绍了。

createInfoIndexFile()

createInfoIndexFile()方法,就是具体的创建一个索引类,即生成一个Java文件。这里对这部分代码就不做过多说明了。

这里贴出一个实例代码:

package com.zhangke.eventbusdemo;

import org.greenrobot.eventbus.meta.SimpleSubscriberInfo;
import org.greenrobot.eventbus.meta.SubscriberMethodInfo;
import org.greenrobot.eventbus.meta.SubscriberInfo;
import org.greenrobot.eventbus.meta.SubscriberInfoIndex;

import org.greenrobot.eventbus.ThreadMode;

import java.util.HashMap;
import java.util.Map;

/** This class is generated by EventBus, do not edit. */
public class MyEventBusIndex implements SubscriberInfoIndex {
    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;

    static {
        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();

        // 这里有两个Subscriber,每个Subscriber中都有两个SubscriberMethod
        putIndex(new SimpleSubscriberInfo(SecondActivity.class, true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onFirstEvent", FirstEvent.class),
            new SubscriberMethodInfo("onSecond", FirstEvent.class, ThreadMode.MAIN, 1, true),
        }));

        putIndex(new SimpleSubscriberInfo(MainActivity.class, true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onFirstEvent", FirstEvent.class),
            new SubscriberMethodInfo("onSecond", FirstEvent.class, ThreadMode.MAIN, 1, false),
        }));

    }

    private static void putIndex(SubscriberInfo info) {
        SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
    }

    @Override
    public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
        SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
        if (info != null) {
            return info;
        } else {
            return null;
        }
    }
}

在该类中声明了一个SUBSCRIBER_INDEX的静态Map集合,并然后通过静态代码块将所有的Subscriber存储在该静态集合中。并且在该类中提供了有一个Override方法getSubscriberInfo()用于获取指定Subscriber中的SubscriberInfo。

@Override
public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
    SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
    if (info != null) {
        return info;
    } else {
        return null;
    }
}

使用索引

在官方文档中提到,要想使用索引需要使用下面两种方式创建EventBus实例:

1、
EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();

2、
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
// Now the default instance uses the given index. Use it like this:
EventBus eventBus = EventBus.getDefault();

我们看看addIndex()方法的源码:

    /** Adds an index generated by EventBus' annotation preprocessor. */
    public EventBusBuilder addIndex(SubscriberInfoIndex index) {
        if (subscriberInfoIndexes == null) {
            subscriberInfoIndexes = new ArrayList<>();
        }
        subscriberInfoIndexes.add(index);
        return this;
    }

在该方法中,将SubscriberInfoIndex存储在subscriberInfoIndexes集合中。

那集合subscriberInfoIndexes在什么时候使用呢?
这里我们需要回到register()的过程中的findUsingInfo()方法中,在该方法中有一个方法getSubscriberInfo()在上节中我们不曾提到,这个方法就是用于获取索引中存储的Subscriber信息。

    private SubscriberInfo getSubscriberInfo(FindState findState) {
        if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) {
            SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
            if (findState.clazz == superclassInfo.getSubscriberClass()) {
                return superclassInfo;
            }
        }

        // 获取索引中的Subscriber内容
        if (subscriberInfoIndexes != null) {
            for (SubscriberInfoIndex index : subscriberInfoIndexes) {
                SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
                if (info != null) {
                    return info;
                }
            }
        }
        return null;
    }

这样,就通过索引获取到了SubscriberInfo,这样在findUsingInfo()中,findUsingReflectionInSingleClass()方法就不会被调用了。最终的目的(避免反射的使用)也就达到了。

findUsingReflectionInSingleClass()方法就是在RunTime时使用反射获取SubscriberMethod

总结

EventBus3.0中创建索引,目的就是在编译期间通过注解处理器创建Java类,避免在RunTime期间反射的使用以提高程序整体的运行性能。

<think>好的,我现在需要帮助用户找到Logseq的使用教程或指南。首先,我得回想一下用户提供的引用内容,看看是否有相关的信息。在引用[2]中提到了Logseq与思源笔记、Obsidian等的比较,其中提到Logseq可以直接在文档中嵌入绘图并编辑,而思源笔记需要浏览器编辑。这可能是一个功能点,但用户现在需要的是教程,而不是功能比较。 接下来,用户明确询问的是Logseq的使用教程或操作指南,所以我的回答应该集中在如何获取这些资源上。根据我的知识,Logseq的官方网站通常会有基础教程和文档。此外,社区论坛和视频平台如YouTube也可能有很多用户制作的教程。但需要确认是否有引用中的相关内容。 查看引用[4],里面提到了OneNoteExporter项目,可以将笔记导出到Logseq等应用,但这里可能不直接涉及教程。不过,用户可能需要知道如何将现有笔记迁移到Logseq,这可以作为相关问题的补充。 用户可能需要详细的步骤指南,比如如何安装Logseq,基本操作如创建页面、添加链接、使用块引用等。此外,高级功能如插件安装、同步设置、与Excalidraw的集成也是常见的学习点。 考虑到用户可能需要中文资源,应该推荐中文教程或社区。比如知乎、B站上的中文教程视频,或者官方文档的中文翻译版本。同时,引用[2]提到Logseq的嵌入绘图功能,可以在教程中强调这一点,说明其操作步骤。 另外,相关问题的生成需要围绕用户可能进一步询问的内容,比如如何同步数据、插件推荐、与其他笔记应用的对比等。这些问题有助于用户深入学习Logseq的使用。 总结回答结构:首先推荐官方文档和社区资源,然后分点介绍主要功能,接着提供学习路径建议,最后生成相关问题。同时,确保所有数学表达式正确使用$...$或$$...$$格式,但在这个问题中可能涉及较少数学内容,主要注意代码块和格式要求即可。</think>### Logseq 使用教程与操作指南 #### 一、官方资源与基础教程 1. **官方文档** Logseq 提供完整的[官方文档](https://docs.logseq.com/),涵盖安装、基础操作、高级功能(如双链笔记、模板、插件等)。建议从「Getting Started」部分开始学习如何创建页面、管理任务、使用块引用等[^2]。 2. **视频教程** - **官方 YouTube 频道**:包含功能演示与更新说明,例如「如何搭建知识库」或「与 Excalidraw 集成绘图」[^2]。 - **中文教程**:Bilibili 平台搜索「Logseq 入门指南」,推荐「从零开始学 Logseq」系列,讲解中文环境下的配置与插件安装。 --- #### 二、核心功能与操作 1. **块编辑与双链笔记** - 输入 `- 内容` 创建块,通过 `[[页面名]]` 或 `((块ID))` 实现双链关联。 - 示例: ```plaintext - 学习目标 [[Python基础]] - ((123456)) 引用某块内容 ``` 2. **任务管理与模板** - 使用 `TODO` 标记任务,通过 `/template` 插入预设模板。 - 示例: ```plaintext - TODO 完成项目计划 DEADLINE: <2024-03-01> /模板: 项目规划 ``` 3. **插件与集成** - 通过 `Settings > Plugins` 安装常用插件(如 Calendar、Excalidraw)。 - Excalidraw 集成:直接在文档中嵌入绘图并编辑[^2]。 --- #### 三、进阶学习路径 1. **社区资源** - **GitHub 讨论区**:解决疑难问题(如数据同步或导出)。 - **中文社区**:知乎专栏「Logseq 深度使用指南」分享工作流优化技巧。 2. **书籍与课程** - 《Logseq 极简教程》(电子书):详细解析高级查询语法和 API 调用。 - Udemy 课程「Mastering Logseq」:系统学习数据库管理与团队协作。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值