EventBus3.0源码解析-03

EventBus3.0源码解析-02中我们介绍到,当用户调用EventBus的register方法时,会收集保存相应的订阅信息。而在收集订阅信息的过程中,有两种方式,一种通过反射收集,一种通过注解处理器在编译期生成的索引文件进行收集。第一种方式已经在EventBus3.0源码解析-02中进行了详细的介绍,本文将介绍通过索引文件的方式。

注解处理器

如果读者对注解处理器还不是很了解,可以阅读这篇文章进行学习。本文我假设大家对注解处理器已经有了基本的了解,将主要介绍EventBus3.0的注解处理器逻辑。其主要实现如下:

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
    Messager messager = processingEnv.getMessager();
    try {
        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;
        }
        verbose = Boolean.parseBoolean(processingEnv.getOptions().get(OPTION_VERBOSE));
        int lastPeriod = index.lastIndexOf('.');
        String indexPackage = lastPeriod != -1 ? index.substring(0, lastPeriod) : null;

        round++;
        if (verbose) {
            messager.printMessage(Diagnostic.Kind.NOTE, "Processing round " + round + ", new annotations: " +
                    !annotations.isEmpty() + ", processingOver: " + env.processingOver());
        }
        if (env.processingOver()) {
            if (!annotations.isEmpty()) {
                messager.printMessage(Diagnostic.Kind.ERROR,
                        "Unexpected processing state: annotations still available after processing over");
                return false;
            }
        }
        if (annotations.isEmpty()) {
            return false;
        }

        if (writerRoundDone) {
            messager.printMessage(Diagnostic.Kind.ERROR,
                    "Unexpected processing state: annotations still available after writing.");
        }
        collectSubscribers(annotations, env, messager);
        checkForSubscribersToSkip(messager, indexPackage);

        if (!methodsByClass.isEmpty()) {
            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;
}

通过上面代码我们知道,其核心逻辑应该在collectSubscriberscheckForSubscribersToSkipcreateInfoIndexFile三个方法中。

注意这句代码String index = processingEnv.getOptions().get(OPTION_EVENT_BUS_INDEX),OPTION_EVENT_BUS_INDEX的值为“eventBusIndex”,它将获取注解处理器的可选参数,是由用户指定的,那么怎么指定呢?

1、由@SupportedOptions指定注解处理器可以接收哪些可选参数:

2、在app工程的build.gradle文件中传入参数值:

在这里插入图片描述

后面我们接着介绍collectSubscriberscheckForSubscribersToSkipcreateInfoIndexFile三个方法。

collectSubscribers方法

collectSubscribers方法的实现如下:

private void collectSubscribers(Set<? extends TypeElement> annotations, RoundEnvironment env, Messager messager) {
    for (TypeElement annotation : annotations) {
    	// 找到所有有annotation注解的元素信息
        Set<? extends Element> elements = env.getElementsAnnotatedWith(annotation);
        for (Element element : elements) {
            // 元素是否是可执行元素,比如方法
            if (element instanceof ExecutableElement) {
                ExecutableElement method = (ExecutableElement) element;
                // 检查方法定义是否符合规范
                if (checkHasNoErrors(method, messager)) {
                    // 方法所在的类元素
                    TypeElement classElement = (TypeElement) method.getEnclosingElement();
                    // 保存找到的元素
                    methodsByClass.putElement(classElement, method);
                }
            } else {
                messager.printMessage(Diagnostic.Kind.ERROR, "@Subscribe is only valid for methods", element);
            }
        }
    }
}

collectSubscribers方法逻辑相对简单,相信读者通过阅读代码注释就能明白,就是把所有有用的信息收集到了methodsByClass变量中。其中,checkHasNoErrors方法的实现如下:

private boolean checkHasNoErrors(ExecutableElement element, Messager messager) {
    // 方法修饰符是否包含static
    if (element.getModifiers().contains(Modifier.STATIC)) {
        messager.printMessage(Diagnostic.Kind.ERROR, "Subscriber method must not be static", element);
        return false;
    }
    
    // 方法修饰符是否包含public
    if (!element.getModifiers().contains(Modifier.PUBLIC)) {
        messager.printMessage(Diagnostic.Kind.ERROR, "Subscriber method must be public", element);
        return false;
    }
   
    List<? extends VariableElement> parameters = ((ExecutableElement) element).getParameters();
    // 方法是否只有一个参数
    if (parameters.size() != 1) {
        messager.printMessage(Diagnostic.Kind.ERROR, "Subscriber method must have exactly 1 parameter", element);
        return false;
    }
    return true;
}

checkForSubscribersToSkip

checkForSubscribersToSkip方法的实现如下:

private void checkForSubscribersToSkip(Messager messager, String myPackage) {
    for (TypeElement skipCandidate : methodsByClass.keySet()) {
        TypeElement subscriberClass = skipCandidate;
        while (subscriberClass != null) {
            // subscriberClass类在myPackage包名下面是否可见
            if (!isVisible(myPackage, subscriberClass)) {
                boolean added = classesToSkip.add(skipCandidate);
                if (added) {
                    String msg;
                    if (subscriberClass.equals(skipCandidate)) {
                        msg = "Falling back to reflection because class is not public";
                    } else {
                        msg = "Falling back to reflection because " + skipCandidate +
                                " has a non-public super class";
                    }
                    messager.printMessage(Diagnostic.Kind.NOTE, msg, subscriberClass);
                }
                break;
            }
            List<ExecutableElement> methods = methodsByClass.get(subscriberClass);
            if (methods != null) {
                for (ExecutableElement method : methods) {
                    String skipReason = null;
                    VariableElement param = method.getParameters().get(0);
                    TypeMirror typeMirror = getParamTypeMirror(param, messager);
                    if (!(typeMirror instanceof DeclaredType) ||
                            !(((DeclaredType) typeMirror).asElement() instanceof TypeElement)) {
                        skipReason = "event type cannot be processed";
                    }
                    if (skipReason == null) {
                        TypeElement eventTypeElement = (TypeElement) ((DeclaredType) typeMirror).asElement();
                        // eventTypeElement事件类在myPackage包名下面是否可见
                        if (!isVisible(myPackage, eventTypeElement)) {
                            skipReason = "event type is not public";
                        }
                    }
                    if (skipReason != null) {
                        boolean added = classesToSkip.add(skipCandidate);
                        if (added) {
                            String msg = "Falling back to reflection because " + skipReason;
                            if (!subscriberClass.equals(skipCandidate)) {
                                msg += " (found in super class for " + skipCandidate + ")";
                            }
                            messager.printMessage(Diagnostic.Kind.NOTE, msg, param);
                        }
                        break;
                    }
                }
            }
            subscriberClass = getSuperclass(subscriberClass);
        }
    }
}

checkForSubscribersToSkip方法的逻辑是,因为我们将在myPackage包名下面创建索引文件,而如果某些订阅类或者父类在myPackage包名下面是不可见的,那么这些类不应该创建相应的索引代码。同样的,如果订阅方法的事件参数类在myPackage包名下面是不可见的,那么这些类也不应该创建相应的索引代码。

createInfoIndexFile

createInfoIndexFile方法就是按照一定规则自动生成java代码,创建相应java源文件。下面通过一个具体的例子来说明createInfoIndexFile方法会生成什么样的文件。

1、在OtherClass类中使用@Subscribe声明了一个订阅方法:

public class OtherClass {

    @Subscribe()
    public void test(String str){
        Log.d("eventbusTest", "OtherClass");
    }
}

2、在MainActivity类中使用@Subscribe声明了一个订阅方法:

public class MainActivity extends AppCompatActivity {
	@Subscribe()
    public void test(String str){
        Log.d("eventbusTest", "MainActivity");
    }
}

3、点击build,会在你通过eventBusIndex指定的路径下面生成一个索引文件,本文指定是”com.yong.cugyong.eventbus_sample.MyEventBusIndex“:

在这里插入图片描述

MyEventBusIndex文件的内容如下:

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

        putIndex(new SimpleSubscriberInfo(MainActivity.class, true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("test", String.class),
        }));

        putIndex(new SimpleSubscriberInfo(OtherClass.class, true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("test", String.class),
        }));

    }

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

这里大家只要知道编译工程之后会生成这样一个文件,在下一部分将告诉大家具体含义。

索引文件使用

在文章开头我们介绍到,用户调用EventBus的register方法时,会收集保存相应的订阅信息,而这里我们就介绍怎么通过索引文件找到想要的信息,对register逻辑还不是很了解的朋友可以先看一下EventBus3.0源码解析-02

通过索引文件收集信息的入口函数是SubscriberMethodFinder类的findUsingInfo方法,该方法的实现如下:

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
    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);
}

该方法里面的大多数逻辑我们都在EventBus3.0源码解析-02中进行了介绍,这里我们只介绍不同点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;
        }
    }
    // 通常会直接走这一块逻辑
    if (subscriberInfoIndexes != null) {
        // 遍历所有索引文件
        for (SubscriberInfoIndex index : subscriberInfoIndexes) {
            SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
            if (info != null) {
                return info;
            }
        }
    }
    return null;
}

该方法遍历所有索引文件,调用索引文件中getSubscriberInfo方法根据订阅类寻找订阅信息。因为这种寻找订阅信息的方式没有反射,所以效率很高。

总结

EventBus通过索引文件寻找订阅信息,是通过在编译期通过注解处理器生成相应的索引文件,然后在运行期使用索引文件寻找订阅信息,这样在运行期就不需要使用到反射了,提高了效率。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值