专栏名称: 努力啊Ant
Android开发工程师
目录
相关文章推荐
余杭发布  ·  下车即达!余杭就医更加便捷 ·  15 小时前  
余杭发布  ·  余杭将新增一个大型展示馆!最新进展公布 ·  18 小时前  
余杭时报  ·  好消息!浙A浙M车主,可直接补办了 ·  昨天  
余杭发布  ·  好消息!浙A浙M车主,可直接补办了 ·  昨天  
51好读  ›  专栏  ›  努力啊Ant

EventBus3.0解析之注解处理器

努力啊Ant  · 掘金  ·  · 2019-04-23 07:33

正文

阅读 108

EventBus3.0解析之注解处理器

在上一篇 EventBus3.0源码解析 中,在介绍查找订阅方法时提到了 APT 解析,当时一笔带过,主要是觉得这个特性比较重要,所以单独拎出来写一篇来介绍。

源码

先来回忆下查找订阅方法:


    List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if (subscriberMethods != null) {
            return subscriberMethods;
        }

        if (ignoreGeneratedIndex) {
            // 使用反射查找
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
            //  使用生成的index查找
            subscriberMethods = findUsingInfo(subscriberClass);
        }
        if (subscriberMethods.isEmpty()) {
            throw new EventBusException("Subscriber " + subscriberClass
                    + " and its super classes have no public methods with the @Subscribe annotation");
        } else {
            METHOD_CACHE.put(subscriberClass, subscriberMethods);
            return subscriberMethods;
        }
    }
复制代码

findSubscriberMethods 方法中,会根据 ignoreGeneratedIndex 来进行策略执行,这个变量默认是 false ,调用了 findUsingInfo 方法:


    private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        // 准备一个findState实例
        FindState findState = prepareFindState();
        // 初始化
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
            // 直接获取订阅者信息
            findState.subscriberInfo = getSubscriberInfo(findState);
            if (findState.subscriberInfo != null) {
                // 有订阅者信息直接进行下一步检查
                SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
                for (SubscriberMethod subscriberMethod : array) {
                    if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                        findState.subscriberMethods.add(subscriberMethod);
                    }
                }
            } else {
                // 没有订阅者信息则使用反射一个个类查找
                findUsingReflectionInSingleClass(findState);
            }
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }

复制代码

方法如其名,这个方法主要就是用来查找本地已经生成的订阅者信息,如果没有查找到还是要去使用反射获取。关键方法就是 getSubscriberInfo 方法,它返回了一个 SubscriberInfo实例 并保存在了 FindState实例 中,来看看 SubscriberInfo 里有什么:

public interface SubscriberInfo {
    // 订阅类
    Class<?> getSubscriberClass();
    // 订阅方法集合
    SubscriberMethod[] getSubscriberMethods();

    SubscriberInfo getSuperSubscriberInfo();

    boolean shouldCheckSuperclass();
}
复制代码

SubscriberInfo 是一个接口,通过这个接口可以拿到封装了订阅类、订阅方法以及父类的 SubscriberInfo 。再回到上一步,看看关键方法 getSubscriberInfo

    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;
            }
        }
        // 这里判断了subscriberInfoIndexes不为空,从subscriberInfoIndexes中去取SubscriberInfo
        if (subscriberInfoIndexes != null) {
            for (SubscriberInfoIndex index : subscriberInfoIndexes) {
                SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
                if (info != null) {
                    return info;
                }
            }
        }
        return null;
    }
复制代码

一共两步,第一步是在查找过一次之后并且找到了目标 SubscriberInfo 之后才会执行。重点是在第二步,判断了一个参数 subscriberInfoIndexes 不为空,然后从 subscriberInfoIndexes 中遍历出目标类的 SubscriberInfo 。那么 subscriberInfoIndexes 又是什么呢:

class SubscriberMethodFinder {
    // 一个SubscriberInfoIndex集合
    private List<SubscriberInfoIndex> subscriberInfoIndexes;
    
    ...
}

// 一个根据订阅类查找订阅信息的接口
public interface SubscriberInfoIndex {
    SubscriberInfo getSubscriberInfo(Class<?> subscriberClass);
}
    
复制代码

subscriberInfoIndexes 是一个 SubscriberInfoIndex 集合,它是在 EventBus初始化 时被赋值的:

public class EventBus {
    EventBus(EventBusBuilder builder) {
    ...
    // 传入builder.subscriberInfoIndexes
    subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
                builder.strictMethodVerification, builder.ignoreGeneratedIndex);
    
    ...
    }
}
复制代码

EventBus初始化 则是使用的 Builder模式 ,Builder里有个 addIndex 方法:

    public EventBusBuilder addIndex(SubscriberInfoIndex index) {
        if(subscriberInfoIndexes == null) {
            subscriberInfoIndexes = new ArrayList<>();
        }
        subscriberInfoIndexes.add(index);
        return this;
    }
复制代码

到了这里显而易见, SubscriberInfoIndex 是由我们传参进 Builder 的。其实 SubscriberInfoIndex 就是 APT 预先解析出来的 订阅者信息提供者 ,它需要开发者自行在编译器中配置 APT 后编译生成,接下来就来看看如何才能生成并使用索引类 SubscriberInfoIndex

Subscriber Index

APT(Annotation Processing Tool)配置

首先要在项目module的 build.gradle 中引入 EventBusAnnotationProcessor依赖 , 并设置生成类参数

android {
    defaultConfig {
        javaCompileOptions {
            // 注解处理器参数配置
            annotationProcessorOptions {
                // 配置参数名和值
                arguments = [ eventBusIndex : 'com.example.myapp.MyEventBusIndex' ]
            }
        }
    }
}

dependencies {
    // 注解依赖
    implementation 'org.greenrobot:eventbus:3.0.0'
    // 注解处理器依赖
    annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.0.0'
}
复制代码

如果使用的是 Kotlin 语言,就需要使用 Kotlin 的专用APT: kapt

apply plugin: 'kotlin-kapt' // ensure kapt plugin is applied

dependencies {
    implementation 'org.greenrobot:eventbus:3.0.0'
    kapt 'org.greenrobot:eventbus-annotation-processor:3.0.0'
}

kapt {
    arguments {
        // 包名可以自定义
        arg('eventBusIndex', 'com.example.myapp.MyEventBusIndex')
    }
}
复制代码

如果项目中是 Java Kotlin 同时使用,可能会存在 annotationProcessor kapt 同时存在,建议统一改为 kapt ,因为后者会兼容前者。

Index应用

在上一步完成后,只需要 Rebuild Project ,就会在项目路径 app-build-generated-source-apt/kapt 下看到生成的索引类: MyEventBusIndex.java ,它实现了 SubscriberInfoIndex 接口,实现了 getSubscriberInfo 方法。从上边的源码分析就知道,接下来只需要将生成的索引类传参进 EventBus 中:

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

由源码可知, EventBus 中保存的是一个 Subscriber Index集合 ,所以 addIndex 方法可以调用多次,这样,在 非application Model 中也可以生成各自的Index类,最后统一添加到 EventBus 中。

来看看生成的索引类 MyEventBusIndex.java

/** 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>();

        // 直接new一个SimpleSubscriberInfo并加入SUBSCRIBER_INDEX
        putIndex(new SimpleSubscriberInfo(com.example.myapp.MainActivity.Companion.class,
                true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("handleEvent", com.example.myapp.MyEvent.class,
                    ThreadMode.MAIN),
        }));

    }

    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;
        }
    }
}

复制代码

MyEventBusIndex 在初始化时,直接会将订阅者相关信息缓存在Map中,并实现了 SubscriberInfoIndex接口 ,在实现方法 getSubscriberInfo 中根据订阅类返回对应的订阅者信息。到了这里,你肯定会疑惑, EventBus 是如何在编译期间就找到了所有订阅信息并且是如何生成了一个 Java 文件的,奥秘就在 APT 技术中。

EventBusAnnotationProcessor

在上文中,整个 MyEventBusIndex 文件都是由 APT 在编译时解析注解生成的。显然, EventBus 自定义了自己的注解处理器。

我们知道,自定义注解处理器只要三步:

  1. 新建自定义注解器(其实就是一个java类),继承自 Java 提供的 AbstractProcessor
  2. 实现 process 方法,开发者就是在这个方法中进行注解解析并生成Java类
  3. 注册到 Javac (编译器)

这样,在编译期间, Javac 会扫描注解,检查 AbstractProcessor 的子类,并且调用该子类的 process 函数,然后将添加了注解的所有元素都传递到 process 函数中,执行我们的处理逻辑,生成我们想要的Java文件。

那么重点就在 EventBus EventBusAnnotationProcessor process 方法。在开始之前,需要了解下 Element 类:

Element

要想自定义 APT ,必须掌握 Element ,它是 APT 技术的基础。 Element 只有在编译期间是可见的,因为它是用来在编译期间描述Java文件的静态结构的一种类型, Element 可以表示包、类或方法。 JDK 提供了5种 Element ,它们都继承自 Element

- javax.lang.model.element.Element
      - javax.lang.model.element.ExecutableElement
      - javax.lang.model.element.PackageElement
      - javax.lang.model.element.TypeElement 
      - javax.lang.model.element.TypeParameterElement
      - javax.lang.model.element.VariableElement
复制代码

它们分别表示:

  • PackageElement :表示一个包程序元素。提供对有关包及其成员的信息的访问。
  • TypeElement :表示一个类或接口程序元素。提供对有关类型及其成员的信息的访问。注意,枚举类型是一种类,而注释类型是一种接口。
  • ExecutableElement :表示某个类或接口的方法、构造方法或初始化程序,包括注释类型元素。
  • TypeParameterElement :表示一般类、接口、方法或构造方法元素的形式类型(泛型)参数。
  • VariableElement :表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数。

举个栗子:


package com.example;    // PackageElement

public class Demo<T extends List> {    // Demo类是TypeElement,T是TypeParameterElement

    private int a;      // VariableElement
    private String b;  // VariableElement

    public Demo () {}    // ExecuteableElement

    public void setValue (int value) {} // 方法setValue是ExecuteableElement,参数value是VariableElement
}
复制代码

再介绍下几个涉及到的 Element 方法:

asType :返回此元素定义的类型。返回值用 TypeMirror 表示, TypeMirror 表示了Java编程语言中的类型,包括基本类型,一般用来做类型判断。

getModifiers :返回此元素的修饰符,不包括注释。但包括显式修饰符,比如接口成员的 public static 修饰符。

getEnclosingElement :返回封装此元素的最里层元素。

  • 如果此元素的声明在词法上直接封装在另一个元素的声明中,则返回那个封装元素。
  • 如果此元素是顶层类型,则返回它的包。
  • 如果此元素是一个包,则返回 null
  • 如果此元素是一个类型参数,则返回 null

process

接下来进入正题,看看 EventBus 注解处理器的 process 方法:

public class EventBusAnnotationProcessor extends AbstractProcessor {
    public static final String OPTION_EVENT_BUS_INDEX = "eventBusIndex";
    
    /** Found subscriber methods for a class (without superclasses). */
    // 自定义的数据结构,保存`<Key, List<Value>>`类型数据;这里的key是订阅类
    private final ListMap<TypeElement, ExecutableElement> methodsByClass = new ListMap<>();
    // 保存不合法的元素,这里的key是订阅类
    private final Set<TypeElement> classesToSkip = new HashSet<>();
    ...
    
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
        // messager用于输出log
        Messager messager = processingEnv.getMessager();
        try






请到「今天看啥」查看全文