之前提到了“帮助类”的概念,也就是在运行时生成 用于帮助填充WareHouse路由元信息的类,这里就涉及到了APT技术。本篇就对这一技术点进行介绍,并详细分析ARouter中是如何使用APT来生成帮助类的。
1.1 APT 的理解
APT(Annotation Processing Tool),即 注解处理器,是javac中提供的编译时扫描和处理注解的工具,它对源代码文件进行检测找出其中的注解,然后使用注解进行额外的处理。
注解就像是一个标签,有很多类型,可以贴在某些元素上面进行标记,并且标签上可以写一些信息。APT就是用来处理标签的工具,在编译开始后,可以拿到自己所关心的类型的所有标签,然后根据标签信息和被标记的元素信息,做一些事情。做那些事呢,这就看你如何写APT了,你让他干啥他就干啥,通常都是会生成一些帮助类——帮助完成你的目的的类。后面无论对这种标签的使用是增加、减少了,每次编译都会重新走这一过程,而上一次的处理结果会被清空。
宏观上理解,APT就是javac提供给开发者在编译时处理注解的一种技术;微观上,具体到实例中就是指 继承自javax.annotation.processing.AbstractProcessor 的实现类,即一个处理特定注解的处理器。(下文提到的APT都是宏观上理解,具体的处理器简称为Processor)
那不使用APT能否完成目的呢? 也是可以的,毕竟APT就是为了帮助我完成目的,那我自己肯定也是可以完成目的的,只不过有了APT会很省事。例如,上篇提到的帮助类,目的就是为了收集路由元信息(路由目标类class),我们如果不使用ARouter,那么就需要自己定义一个moduleA、moduelB共同依赖的muduleX,把需要进行跳转的XXXActivity这些类的class手工写代码保存到muduleX的Map中,key可以用XXXActivity的类名,value就是XXXActivity.class,这样moduleA、moduelB之间发起跳转时 就通过想要跳转的Activity的类名 从muduleX的Map中获取目标Activity的class,这样也是能完成 无相互依赖的moduleA、moduelB之前进行页面跳转的。
而使用了APT,只要使用注解进行标记即可,无论使用者怎么标记,每次编译时都由APT统一处理,不会出错、也不担心有遗漏。
通常用到APT技术的都是像ARouter这样的通用能力框架:
这样上层业务使用极为方便,只需要使用注解进行标记,然后调用API即可。信息如何收集和存储、如何寻找和使用,框架使用者是不用关心的。
APT还有两个特点:
1.2 APT 的原理
我们先来看下Java的编译过程:
可以看到在Java源码到class文件之间,需要经过注解处理器的处理,注解处理器生成的代码也同样会经过这一过程,最终一起生成class文件。在Android中,class文件还会被打进Dex文件中,最后生成APK文件。
注解处理器的执行是在编译的初始阶段,并且会有多个processor(查看所有注册的processor:intermediates/annotation_processor_list/debug/annotationProcessors.json)。
那么我们自定义的注解处理器,如何注册进去呢?又如何实现一个注解处理器呢?
来看一个简单的实例:
@AutoService(Processor.class) //把 TestProcessor注册到编译器中@SupportedAnnotationTypes({"com.hfy.test_annotations.TestAnnotation"})//TestProcessor 要处理的注解@SupportedSourceVersion(SourceVersion.RELEASE_8)//设置jdk环境为java8//@SupportedOptions() //一些支持的可选配置项public class TestProcessor extends AbstractProcessor { public static final String ACTIVITY = "android.app.Activity"; //Filer 就是文件流输出路径,当我们用AbstractProcess生成一个java类的时候,我们需要保存在Filer指定的目录下 Filer mFiler; //类型 相关的工具类。当process执行的时候,由于并没有加载类信息,所以java文件中的类信息都是用element来代替了。 //类型相关的都被转化成了一个叫TypeMirror,其getKind方法返回类型信息,其中包含了基础类型以及引用类型。 Types types; //Elements 获取元素信息的工具,比如说一些类信息继承关系等。 Elements elementUtils; //来报告错误、警告以及提示信息 //用来写一些信息给使用此注解库的第三方开发者的 Messager messager; @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); mFiler = processingEnv.getFiler(); types = processingEnv.getTypeUtils(); elementUtils = processingEnv.getElementUtils(); messager = processingEnv.getMessager(); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) { if (annotations == null || annotations.size() == 0){ return false; } //获取所有包含 @TestAnnotation 注解的元素 Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(TestAnnotation.class); //每个Processor的独自的逻辑,其他的写法一般都是固定的 parseAnnotation(elements) return true; } //解析注解并生成java文件 private boolean parseAnnotation(Set<? extends Element> elements) { ... }}
上面的TestProcessor就是一个典型的注解处理器,继承自javax.annotation.processing.AbstractProcessor。注意到TestProcessor重写了init()和process()两个方法,并且添加了几个注解。
几个注解是处理器的注册和配置 :
重写的两个方法:init()、process()是Processor的初始化和处理过程:
在编译流程进入Processor前,APT会对整个Java源文件进行扫描,这样就会获取到 所有添加了的注解和对应被注解的类。注解和被注解的类,一起被视为一个元素,即TypeElement,就是process()方法参数annotations的数据类型。通过TypeElement,我们可以获取注解的所有信息、被注解类的所有信息,这样就可以根据这些信息来生成 我们需要的帮助类了。
这里重点是理解Processor的原理,关于注解和Element的知识这里不过多介绍,可自行了解。
到这里,Processor的工作流程我们了解了,并且其 定义、配置、注册 这些都是固定的写法,一般无需过多关注。最重要的就是 process()方法的实现:拿到所有关注的注解元素后,就是每个Processor的独自的逻辑——解析注解并生成需要的java文件。
2.1 ARouter 工程介绍
ARouter是一个典型的 APT+AGP 框架,有4个module:
接下来就来详细分析 compiler,看看ARouter是如何解析注解并生成帮助类的。
2.2 RouteProcessor
有4个Processor,其中 BaseProcessor 是直接继承自 AbstractProcessor 的,其他都是继承BaseProcessor:
这里我们重点关注RouteProcessor。先来看下 BaseProcessor:
public abstract class BaseProcessor extends AbstractProcessor { Filer mFiler; Logger logger; Types types; Elements elementUtils; TypeUtils typeUtils; //此Module的名字 String moduleName = null; // 是否生成router doc boolean generateDoc; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); mFiler = processingEnv.getFiler(); types = processingEnv.getTypeUtils(); elementUtils = processingEnv.getElementUtils(); typeUtils = new TypeUtils(types, elementUtils); logger = new Logger(processingEnv.getMessager()); // 获取配置: moduleName、generateDoc Map options = processingEnv.getOptions(); if (MapUtils.isNotEmpty(options)) { moduleName = options.get(KEY_MODULE_NAME); generateDoc = VALUE_ENABLE.equals(options.get(KEY_GENERATE_DOC_NAME)); } if (StringUtils.isNotEmpty(moduleName)) { moduleName = moduleName.replaceAll("[^0-9a-zA-Z_]+", ""); ... } else { //没有配置 moduleName,就报错! logger.error(NO_MODULE_NAME_TIPS); throw new RuntimeException("ARouter::Compiler >>> No module name, for more information, look at gradle log."); } }... //设置关注的配置项,其中KEY_MODULE_NAME就是在.gradle中配置的 @Override public Set getSupportedOptions() { return new HashSet() {{ this.add(KEY_MODULE_NAME); this.add(KEY_GENERATE_DOC_NAME); }}; }}
BaseProcessor主要是在init()中做了各种工具的初始化,同时获取了key为 AROUTER_MODULE_NAME 的配置项 moduleName——module名字。我们注意重写的getSupportedOptions()方法,它和前面的介绍的@SupportedOptions作用是一致的。AROUTER_MODULE_NAME 就是路由所在module的gradle文件中配置的:
拿到moduleName有什么作用呢?上一篇中提到的 根帮助类、Provider帮助类、拦截器帮助类 的类名就需要moduleName构成:
@AutoService(Processor.class)@SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED})public class RouteProcessor extends BaseProcessor { private Map> groupMap = new HashMap<>(); // ModuleName and routeMeta. private Map rootMap = new TreeMap<>(); // Map of root metas, used for generate class file in order. private TypeMirror iProvider = null; private Writer docWriter; // Writer used for write doc @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); ... iProvider = elementUtils.getTypeElement(Consts.IPROVIDER).asType(); ... } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { if (CollectionUtils.isNotEmpty(annotations)) { Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class); try { this.parseRoutes(routeElements); } catch (Exception e) { logger.error(e); } return true; } return false; }
定义了两个Map:
在init()中使用elementUtils获取了 IProvider接口的类型,用于后面判断一个类元素是否是IProvider的实现类。
在process()中获取了添加了@Route的所有Element,然后调用parseRoutes()开始解析。
在上篇中介绍了 根帮助类的loadInfo方法体内,是用接收的map来put当前module所有路由组帮助类的class。所以我们可以猜想应该是先创建了所有的组帮助类之后,才创建的根帮助类,并且一个module只有一个根帮助类。而组帮助类,则应该是先遍历此module所有 @Route注解类,找到相同group的路由创建组帮助类。
另外这里有个问题是,Java文件要如何生成呢?这里就要介绍 javepoet 这个库了:
JavaPoet的常用类:
通常APT框架中生成Java类都是使用javepoet。这里也不展开介绍,下面结合我添加的注释即可理解。
下面就来看parseRoutes()方法:
private void parseRoutes(Set<? extends Element> routeElements) throws IOException {------------ 一、准备工作:遍历routeElements并创建对应RouteMeta,接着按group分组后 存入groupMap --------------- if (CollectionUtils.isNotEmpty(routeElements)) { // 清空rootMap rootMap.clear(); //准备好会用到的元素类型,用于后面的判断 TypeMirror type_Activity = elementUtils.getTypeElement(ACTIVITY).asType(); TypeMirror type_Service = elementUtils.getTypeElement(SERVICE).asType(); TypeMirror fragmentTm = elementUtils.getTypeElement(FRAGMENT).asType(); TypeMirror fragmentTmV4 = elementUtils.getTypeElement(Consts.FRAGMENT_V4).asType(); // ARouter的相关接口 TypeElement type_IRouteGroup = elementUtils.getTypeElement(IROUTE_GROUP); TypeElement type_IProviderGroup = elementUtils.getTypeElement(IPROVIDER_GROUP); ClassName routeMetaCn = ClassName.get(RouteMeta.class); ClassName routeTypeCn = ClassName.get(RouteType.class); //帮助类:就是使用javaPoet生成的所有用于 收集所有路由等信息的类。 //创建一个参数类型:用于存所有 组帮助类class 的Map,Map> ParameterizedTypeName inputMapTypeOfRoot = ParameterizedTypeName.get( ClassName.get(Map.class), ClassName.get(String.class), ParameterizedTypeName.get( ClassName.get(Class.class), WildcardTypeName.subtypeOf(ClassName.get(type_IRouteGroup)) ) ); //创建一个参数类型:用于存某个分组内的所有路由,Map ParameterizedTypeName inputMapTypeOfGroup = ParameterizedTypeName.get( ClassName.get(Map.class), ClassName.get(String.class), ClassName.get(RouteMeta.class) ); //创建输入参数的名字,也就是几种帮助类loadInto方法的参数名 ParameterSpec rootParamSpec = ParameterSpec.builder(inputMapTypeOfRoot, "routes").build(); ParameterSpec groupParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "atlas").build(); ParameterSpec providerParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "providers").build(); //创建方法builder:根帮助类loadInto方法,loadInto(Map> routes) MethodSpec.Builder loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO) .addAnnotation(Override.class) .addModifiers(PUBLIC) .addParameter(rootParamSpec); //遍历routeElements,创建对应类型的RouteMeta,并分组后存入 groupMap 中,统计后最后生成根帮助类 for (Element element : routeElements) { TypeMirror tm = element.asType(); Route route = element.getAnnotation(Route.class); RouteMeta routeMeta; //是 Activity 或者 Fragment if (types.isSubtype(tm, type_Activity) || types.isSubtype(tm, fragmentTm) || types.isSubtype(tm, fragmentTmV4)) { // 获取被@Autowired注解的变量 Map paramsType = new HashMap<>(); Map injectConfig = new HashMap<>(); injectParamCollector(element, paramsType, injectConfig); //对应类型的RouteMeta if (types.isSubtype(tm, type_Activity)) { routeMeta = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType); } else { routeMeta = new RouteMeta(route, element, RouteType.parse(FRAGMENT), paramsType); } routeMeta.setInjectConfig(injectConfig); } else if (types.isSubtype(tm, iProvider)) { //是IProvider实现类 routeMeta = new RouteMeta(route, element, RouteType.PROVIDER, null); } else if (types.isSubtype(tm, type_Service)) { //是Service routeMeta = new RouteMeta(route, element, RouteType.parse(SERVICE), null); } else { throw new RuntimeException("The @Route is marked on unsupported class, look at [" + tm.toString() + "]."); } //按group分类 然后存入groupMap,一个value就是同group的所有路由 categories(routeMeta); } //Provider帮助类的 的 loadInto 方法:loadInto(Map providers) MethodSpec.Builder loadIntoMethodOfProviderBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO) .addAnnotation(Override.class) .addModifiers(PUBLIC) .addParameter(providerParamSpec); Map> docSource = new HashMap<>();------------ 二、遍历groupMap:创建 组帮助类文件 并把类名存入rootMap、构建Provider帮助类方法体语句 --------------- //遍历groupMap:创建 组帮助类文件 并把类名存入rootMap、构建Provider帮助类方法体语句 for (Map.Entry> entry : groupMap.entrySet()) { String groupName = entry.getKey(); //组帮助类的 的 loadInto 方法 MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO) .addAnnotation(Override.class) .addModifiers(PUBLIC) .addParameter(groupParamSpec); ... Set groupData = entry.getValue(); //groupData,一个组 的所有路由 //遍历同组路由:构建Provider帮助类loadInto方法体语句、组帮助类 的方法体语句 for (RouteMeta routeMeta : groupData) { ClassName className = ClassName.get((TypeElement) routeMeta.getRawType()); switch (routeMeta.getType()) { case PROVIDER: List<? extends TypeMirror> interfaces = ((TypeElement) routeMeta.getRawType()).getInterfaces(); for (TypeMirror tm : interfaces) { if (types.isSameType(tm, iProvider)) {//直接实现自IProvider接口 loadIntoMethodOfProviderBuilder.addStatement( "providers.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, null, " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))", (routeMeta.getRawType()).toString(), routeMetaCn, routeTypeCn, className, routeMeta.getPath(), routeMeta.getGroup()); } else if (types.isSubtype(tm, iProvider)) { // //直接实现自IProvider的子接口 loadIntoMethodOfProviderBuilder.addStatement( "providers.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, null, " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))", tm.toString(), // So stupid, will duplicate only save class name. routeMetaCn, routeTypeCn, className, routeMeta.getPath(), routeMeta.getGroup()); } } break; default: break; } //参数 ... //组帮助类 的方法体内的语句 loadIntoMethodOfGroupBuilder.addStatement( "atlas.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, " + (StringUtils.isEmpty(mapBody) ? null : ("new java.util.HashMap(){{" + mapBodyBuilder.toString() + "}}")) + ", " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))", routeMeta.getPath(), routeMetaCn, routeTypeCn, className, routeMeta.getPath().toLowerCase(), routeMeta.getGroup().toLowerCase()); ... } //创建 实现自IRouteGroup的 组帮助类文件(有多个,每个分组一个帮助类,每个帮助内 含有同组的activity、fragment、IProvider) String groupFileName = NAME_OF_GROUP + groupName; JavaFile.builder(PACKAGE_OF_GENERATE_FILE, TypeSpec.classBuilder(groupFileName) .addJavadoc(WARNING_TIPS) .addSuperinterface(ClassName.get(type_IRouteGroup)) .addModifiers(PUBLIC) .addMethod(loadIntoMethodOfGroupBuilder.build()) .build() ).build().writeTo(mFiler); //rootMap 存入了所有的组帮助类 rootMap.put(groupName, groupFileName); }--------------------- 三、创建根帮助类、创建Provider帮助类 ------------------------ //遍历rootMap,创建 根帮助类 方法体语句 if (MapUtils.isNotEmpty(rootMap)) { for (Map.Entry entry : rootMap.entrySet()) { loadIntoMethodOfRootBuilder.addStatement("routes.put($S, $T.class)", entry.getKey(), ClassName.get(PACKAGE_OF_GENERATE_FILE, entry.getValue())); } } ... //创建 Provider帮助类 文件(只有一个,这个是包含所有服务的帮助类) String providerMapFileName = NAME_OF_PROVIDER + SEPARATOR + moduleName; JavaFile.builder(PACKAGE_OF_GENERATE_FILE, TypeSpec.classBuilder(providerMapFileName) .addJavadoc(WARNING_TIPS) .addSuperinterface(ClassName.get(type_IProviderGroup)) .addModifiers(PUBLIC) .addMethod(loadIntoMethodOfProviderBuilder.build()) .build() ).build().writeTo(mFiler); //创建 根帮助类文件(只有一个) String rootFileName = NAME_OF_ROOT + SEPARATOR + moduleName; JavaFile.builder(PACKAGE_OF_GENERATE_FILE, TypeSpec.classBuilder(rootFileName) .addJavadoc(WARNING_TIPS) .addSuperinterface(ClassName.get(elementUtils.getTypeElement(ITROUTE_ROOT))) .addModifiers(PUBLIC) .addMethod(loadIntoMethodOfRootBuilder.build()) .build() ).build().writeTo(mFiler); }}
(以上定义参数、创建方法体语句、生成Java类 就是使用的javapoet相关API)
如上所示,(忽略了参数处理以及路由Doc逻辑)总共有三个步骤:
整体逻辑和我们的猜想是一致的,逻辑也比较清晰。(对照实际生成的帮助类会更好理解)
到这里,RouteProcessor就分析完了。InterceptorProcessor、AutowiredProcessor,这里不在分析,大家可以自行查看。
本文首先介绍了对ARouter中使用的APT技术的理解——编译时解析注解并生成Java文件,以及ARouter中的RouteProcessor是如何处理@Route注解并生成各种帮助类。
重点掌握对APT技术的理解,以及学习ARouter中帮助类的生成逻辑。这样以后再遇到其他使用到APT技术的框架时就更容易掌握,更深入地,能够在业务中尝试使用APT技术解决问题。
另外,文中有三个未展开介绍的内容:注解的用法、Element相关知识、javapoet用法,它们是掌握和使用APT技术的基础,但由于知识点比较固定,且不是本章重点,就未能详细介绍。大家可在理解本篇内容的基础上再去自行学习。
使用APT生成了帮助类,那么要如何使用帮助类呢?大家可以思考思考,下一篇将为你介绍帮助类的使用以及涉及的 AGP 相关的知识,敬请期待~
留言与评论(共有 0 条评论) “” |