“终于懂了” 系列:组件化框架 ARouter 完全解析(二)APT技术

ARouter系列文章:

“终于懂了” 系列:组件化框架 ARouter 完全解析(一)原理全解

“终于懂了” 系列:组件化框架 ARouter 完全解析(二)APT—帮助类生成

“终于懂了” 系列:组件化框架 ARouter 完全解析(二)AGP/Transform—动态代码注入

在上一篇《“终于懂了” 系列:组件化框架 ARouter 完全解析(一) 原理详解》中,详细介绍了ARouter的核心原理。其中提到了“帮助类”的概念,也就是在运行时生成 用于帮助填充WareHouse路由元信息的类,这里就涉及到了APT技术。那么本篇就对这一技术点进行介绍,并详细分析ARouter中是如何使用APT来生成帮助类的。

一、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这样的通用能力框架:

  • 提供定义好的注解,例如@Route
  • 提供简洁的API接口,例如ARouter.getInstance().build("/module/1").navigation()

这样上层业务使用极为方便,只需要使用注解进行标记,然后调用API即可。信息如何收集和存储、如何寻找和使用,框架使用者是不用关心的。

APT还有两个特点:

  • 获取注解及生成代码都是在代码编译时候完成的,相比反射在运行时处理注解大大提高了程序性能。
  • 注意APT并不能对源文件进行修改,只能获取注解信息和被注解对象的信息,然后做一些自定义的处理,例如生成java类。

1.2 APT的原理

我们先来看下Java的编译过程:

image.png

可以看到在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()两个方法,并且添加了几个注解。

几个注解是处理器的注册和配置

  1. 使用注解@AutoService进行注册,这样在编译这段就会执行这个处理器了。需要依赖com.google.auto.service:auto-service:1.0-rc4' 才能使用@AutoService
  2. 使用注解@SupportedAnnotationTypes 设置 TestProcessor 要处理的注解,要使用目标注解全类名
  3. 使用注解@SupportedSourceVersion设置支持的Java版本、使用注解@SupportedOptions()配置一些支持的可选配置项

重写的两个方法:init()、process()是Processor的初始化和处理过程:

  1. init() 方法是初始化处理器,每个注解处理器被初始化的时候都会被调用,通常是这里使用 处理环境-ProcessingEnvironment 获取一些工具实例,如上所示是一般通用的写法:
    • mFiler,就是文件流输出路径,当我们用AbstractProcess生成一个java类的时候,我们需要保存在Filer指定的目录下
    • types,类型 相关的工具类。当process执行的时候,由于并没有加载类信息,所以java文件中的类信息都是用element来代替了。类型相关的都被转化成了一个叫TypeMirror,其getKind方法返回类型信息,其中包含了基础类型以及引用类型。
    • elementUtils,获取元素信息的工具,比如说一些类信息继承关系等。
    • messager,来报告错误、警告以及提示信息,用来写一些信息给使用此注解库的第三方开发者的
  2. process() 方法,注解处理器实际处理方法,在这里写处理注解的代码,以及生成Java文件。它有两个参数:
    • annotations,是@SupportedAnnotationTypes声明的注解 和 未被其他Processor消费的注解的子集,也就是剩下的且是本Processor关注的注解,类型是TypeElement
    • roundEnvironment,有关当前和之前处理器的环境信息,可以让你查询出包含特定注解的被注解元素
    • 返回值,true表示@SupportedAnnotationTypes中声明的注解 由此 Processor 消费掉,不会传给下个Processor。注解不断向下分发,每个processor都可以决定是否消费掉自己声明的注解。

在编译流程进入Processor前,APT会对整个Java源文件进行扫描,这样就会获取到 所有添加了的注解和对应被注解的类。 注解和被注解的类,一起被视为一个元素,即TypeElement,就是process()方法参数annotations的数据类型。 通过TypeElement,我们可以获取注解的所有信息、被注解类的所有信息,这样就可以根据这些信息来生成 我们需要的帮助类了

这里重点是理解Processor的原理,关于注解和Element的知识这里不过多介绍,可自行了解。

到这里,Processor的工作流程我们了解了,并且其 定义、配置、注册 这些都是固定的写法,一般无需过多关注。最重要的就是 process()方法的实现:拿到所有关注的注解元素后,就是每个Processor的独自的逻辑——解析注解并生成需要的java文件。

二、ARouter的APT

2.1 ARouter工程介绍

image.png

ARouter是一个典型的 APT+AGP 框架,有4个module:

  • annotation,定义共业务侧使用的注解,例如@Route
  • api,框架的核心逻辑实现 并提供便于业务侧使用的Api,即上一篇介绍的内容
  • compiler,定义注解处理器,用于生Java类,就是对APT的使用,即本篇的内容
  • gradle-plugin,定义Android Gradle 插件,用于编译时在class转为dex文件前扫描目标帮助类 并进行代码注入,将在第三篇中介绍

接下来就来详细分析 compiler,看看ARouter是如何解析注解并生成帮助类的。

2.2 RouteProcessor

2.2.1 BaseProcessor

image.png

有4个Processor,其中 BaseProcessor 是直接继承自 AbstractProcessor 的,其他都是继承BaseProcessor:

  • RouteProcessor,处理注解@Route、@Autowired注解,用于生成路由帮助类
  • InterceptorProcessor,处理@Interceptor注解,用于生成拦截器帮助列
  • AutowiredProcessor,处理@Autowired注解,用户生成处理activity/fragment变量的帮助类

这里我们重点关注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<String, String> 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<String> getSupportedOptions() {
        return new HashSet<String>() {{
            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文件中配置的:

image.png

拿到moduleName有什么作用呢?上一篇中提到的 根帮助类Provider帮助类拦截器帮助类 的类名就需要moduleName构成:

image.png

2.2.2 整体流程

@AutoService(Processor.class)
@SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED})
public class RouteProcessor extends BaseProcessor {
    private Map<String, Set<RouteMeta>> groupMap = new HashMap<>(); // ModuleName and routeMeta.
    private Map<String, String> 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:

  • groupMap,此module所有路由组的信息。key为路由的group,value是此group下所有路由元信息。用于生成 组帮助类
  • rootMap,key是group,value是组帮助类的类名,用于生成 根帮助类

在init()中使用elementUtils获取了 IProvider接口的类型,用于后面判断一个类元素是否是IProvider的实现类。

在process()中获取了添加了@Route的所有Element,然后调用parseRoutes()开始解析。

2.2.3 核心逻辑

在上篇中介绍了 根帮助类的loadInfo方法体内,是用接收的map来put当前module所有路由组帮助类的class。所以我们可以猜想应该是先创建了所有的组帮助类之后,才创建的根帮助类,并且一个module只有一个根帮助类。而组帮助类,则应该是先遍历此module所有 @Route注解类,找到相同group的路由创建组帮助类。

另外这里有个问题是,Java文件要如何生成呢?这里就要介绍 javepoet 这个库了:

  • JavaPoet是一款可以自动生成Java文件的第三方依赖
  • 简洁易懂的API,上手快
  • 让繁杂、重复的Java文件,自动化生成,提高工作效率,简化流程

JavaPoet的常用类:

  • TypeSpec,用于生成类、接口、枚举对象的类
  • MethodSpec,用于生成方法对象的类
  • ParameterSpec,用于生成参数对象的类
  • FieldSpec,用于配置生成成员变量的类
  • ClassName,通过包名和类名生成的对象,在JavaPoet中相当于为其指定Class
  • ParameterizedTypeName,通过MainClass和IncludeClass生成包含泛型的Class
  • JavaFile,控制生成的Java文件的输出的类

通常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<String, Class<? extends IRouteGroup>>
        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<String, RouteMeta>
        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<String, Class<? extends IRouteGroup>> 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<String, Integer> paramsType = new HashMap<>();
                Map<String, Autowired> 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<String,RouteMeta> providers)
        MethodSpec.Builder loadIntoMethodOfProviderBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                .addAnnotation(Override.class)
                .addModifiers(PUBLIC)
                .addParameter(providerParamSpec);

        Map<String, List<RouteDoc>> docSource = new HashMap<>();

------------ 二、遍历groupMap:创建 组帮助类文件 并把类名存入rootMap、构建Provider帮助类方法体语句 ---------------
                
        //遍历groupMap:创建 组帮助类文件 并把类名存入rootMap、构建Provider帮助类方法体语句
        for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) {
            String groupName = entry.getKey();
            //组帮助类的 的 loadInto 方法
            MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                    .addAnnotation(Override.class)
                    .addModifiers(PUBLIC)
                    .addParameter(groupParamSpec);
            ...
            Set<RouteMeta> 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<String, Integer>(){{" + 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<String, String> 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逻辑)总共有三个步骤:

  • 准备工作,遍历routeElements并创建对应RouteMeta,接着按group分组后 存入 groupMap
  • 遍历groupMap,创建 组帮助类文件 并把类名存入rootMap、构建Provider帮助类方法体语句
  • 遍历rootMap生成方法体语句后创建根帮助类,创建Provider帮助类

整体逻辑和我们的猜想是一致的,逻辑也比较清晰。(对照实际生成的帮助类会更好理解)

到这里,RouteProcessor就分析完了。 InterceptorProcessor、AutowiredProcessor,这里不在分析,大家可以自行查看。

四、总结

本文首先介绍了对ARouter中使用的APT技术的理解——编译时解析注解并生成Java文件,以及ARouter中的RouteProcessor是如何处理@Route注解并生成各种帮助类。

重点掌握对APT技术的理解,以及学习ARouter中帮助类的生成逻辑。这样以后再遇到其他使用到APT技术的框架时就更容易掌握,更深入地,能够在业务中尝试使用APT技术解决问题。

另外,文中有三个未展开介绍的内容:注解的用法、Element相关知识、javapoet用法,它们是掌握和使用APT技术的基础,但由于知识点比较固定,且不是本章重点,就未能详细介绍。大家可在理解本篇内容的基础上再去自行学习。

使用APT生成了帮助类,那么要如何使用帮助类呢?这将在下一篇中介绍帮助类的使用以及涉及的AGP相关知识。

好了本篇就到这里,欢迎继续关注~

你的 点赞、评论,是对我的巨大鼓励!

欢迎关注我的 公众号 胡飞洋 ,文章更新可第一时间收到;

如果有问题或者想进群,号内有加我微信的入口,我拉你进技术讨论群。在技术学习、个人成长的道路上,我们一起前进!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容