美文网首页
ARouter注解处理器分析-RouteProcessor

ARouter注解处理器分析-RouteProcessor

作者: dashingqi | 来源:发表于2020-07-12 17:50 被阅读0次
    Android_Banner.jpg

    简介

    • 该篇是ARouter中注解处理器分析的第一篇文章,RouteProcessor的分析
    • 我们知道在ARouter中我们可以简单的分成三部分 ,arouter_annotation(自定义注解模块),arouter_compiler(自定义注解处理器模块),arouter_api(模块)
    • 其中我们要分析的注解处理器就是存在于arouter_compiler模块中

    怎样自定义注解处理器(通用步骤)

    • 通常自定义的注解处理器是处在于新建的Java模块中,因为你如果建Android的module,其中有些api是不存在的。
    • 自定义的注解处理器 要 extends AbstractProvessor,还需实现如下几个方法
      • init() ---> 初始化方法,在该方法中我们可以获取到很多工具类
      • getSupportedSourceVersion() ---> 用于制定Java版本 通常写成 SourceVersion.lastSupported()就可以
      • getSupportedAnnotationTypes() ---> 用于制定注解处理器要处理的注解
      • process() ----> 很重要的一个方法,用于编写我们在编译期间生成文件的逻辑
    • 正常来说,以上是叫做APT技术,通常我们还需要使用的AutoService 和JavaPoet框架
      • AutoService的使用 是将注册注解处理器
      • JavaPoet是用来便捷的生成文件类 从类到变量和方法都能用此框架来生成。

    注解处理器到底是干嘛用的呢?

    • 主要是为了在编译期间自动帮我们写代码的功能,对帮我们写代码的功能,我只需要在注解处理器的process()方法中声明好规则,就可以了。
    • 那么我们就先看下ARouter在编译期间都生成那些文件了呢?
    • ARouter-Compiler
    • 看下ARouterGroupXX类文件中的代码
    public class ARouter$$Group$$test implements IRouteGroup {
      @Override
      public void loadInto(Map<String, RouteMeta> atlas) {
        atlas.put("/test/activity1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/activity1", "test", new java.util.HashMap<String, Integer>(){{put("ser", 9); put("ch", 5); put("fl", 6); put("dou", 7); put("boy", 0); put("url", 8); put("pac", 10); put("obj", 11); put("name", 8); put("objList", 11); put("map", 11); put("age", 3); put("height", 3); }}, -1, -2147483648));
        atlas.put("/test/activity2", RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, "/test/activity2", "test", new java.util.HashMap<String, Integer>(){{put("key1", 8); }}, -1, -2147483648));
        atlas.put("/test/activity3", RouteMeta.build(RouteType.ACTIVITY, Test3Activity.class, "/test/activity3", "test", new java.util.HashMap<String, Integer>(){{put("name", 8); put("boy", 0); put("age", 3); }}, -1, -2147483648));
        atlas.put("/test/activity4", RouteMeta.build(RouteType.ACTIVITY, Test4Activity.class, "/test/activity4", "test", null, -1, -2147483648));
        atlas.put("/test/fragment", RouteMeta.build(RouteType.FRAGMENT, BlankFragment.class, "/test/fragment", "test", new java.util.HashMap<String, Integer>(){{put("obj", 11); put("name", 8); }}, -1, -2147483648));
        atlas.put("/test/webview", RouteMeta.build(RouteType.ACTIVITY, TestWebview.class, "/test/webview", "test", null, -1, -2147483648));
      }
    }
    
    • 看下ARouterRootXX类文件中的代码
    public class ARouter$$Root$$app implements IRouteRoot {
      @Override
      public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
        routes.put("test", ARouter$$Group$$test.class);
        routes.put("yourservicegroupname", ARouter$$Group$$yourservicegroupname.class);
      }
    }
    
    • 看下ArouterProviderXX类文件中的代码
    public class ARouter$$Providers$$app implements IProviderGroup {
      @Override
      public void loadInto(Map<String, RouteMeta> providers) {
        providers.put("com.alibaba.android.arouter.demo.testservice.HelloService", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/yourservicegroupname/hello", "yourservicegroupname", null, -1, -2147483648));
        providers.put("com.alibaba.android.arouter.facade.service.SerializationService", RouteMeta.build(RouteType.PROVIDER, JsonServiceImpl.class, "/yourservicegroupname/json", "yourservicegroupname", null, -1, -2147483648));
        providers.put("com.alibaba.android.arouter.demo.testservice.SingleService", RouteMeta.build(RouteType.PROVIDER, SingleService.class, "/yourservicegroupname/single", "yourservicegroupname", null, -1, -2147483648));
      }
    }
    
    
    • 以上就是ARouter在编译期间给我们生成的代码文件,其中ARouterGroupXX,ARouterRootXX,和ARouterProciderXX,我们该篇文章都会涉及到
    • 注明:一般我所指的一级地址就是("/test/activity")就是"/test",路由地址就是"/test/activity"

    ARouter之 RouteProcessor注解处理器的分析

    • 源码如下
    //使用了AutoService注解,将注解处理器进行注册
    @AutoService(Processor.class)
    //使用了注解的方式来指明当前注解处理器要处理的注解
    @SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED})
    // 继承了BaseProcessor,在BaseProcessor中在init方法中我们获取了各种工具类,
    //重写了getSupportedSourceVersion() 来指明版本
    public class RouteProcessor extends BaseProcessor {
    .....
    }
    
    process()
     @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            if (CollectionUtils.isNotEmpty(annotations)) {
                //扫描Route注解 Route注解是作用于类上的
                Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);
                try {
                    logger.info(">>> Found routes, start... <<<");
                    // 交给了parseRoutes()方法来处理了
                    this.parseRoutes(routeElements);
    
                } catch (Exception e) {
                    logger.error(e);
                }
                return true;
            }
    
            return false;
        }
    
    parseRoutes()
     private void parseRoutes(Set<? extends Element> routeElements) throws IOException {
            if (CollectionUtils.isNotEmpty(routeElements)) {
                // prepare the type an so on.
    
                logger.info(">>> Found routes, size is " + routeElements.size() + " <<<");
    
                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();
    
                // Interface of 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);
    
                /*
                   Build input type, format as :
    
                   ```Map<String, Class<? extends IRouteGroup>>```
                   构造入参参数,用于存储一级路由路径
                   一级路由路径(组名)
                   该组对应生成的路由地址清单文件的名字是ARouter$$Root$$Demo
    
                 */
                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>```
                  构造入参参数,用于路由清单中
                  路由路径全称
                  RouteMeta 包含目标类的相关信息
                  用于构建ARouter$$Group$$Demo类中的方法
                 */
                ParameterizedTypeName inputMapTypeOfGroup = ParameterizedTypeName.get(
                        ClassName.get(Map.class),
                        ClassName.get(String.class),
                        ClassName.get(RouteMeta.class)
                );
    
                /*
                  Build input param name.
                  构建参数名字
                 */
    
                // 生成ARouter$$Root$$Demo 类中 loadInto() 入参的参数名字  routes
                ParameterSpec rootParamSpec = ParameterSpec.builder(inputMapTypeOfRoot, "routes").build();
                // 生成ARouter$$Group$$Demo 类中 loadInto()方法的入参的参数名字  atlas
                ParameterSpec groupParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "atlas").build();
                // 生成ARouter$$Provider$$Demo类中loadInto()方法的入参的参数名字  providers
                ParameterSpec providerParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "providers").build();  // Ps. its param type same as groupParamSpec!
    
                /*
                  Build method : 'loadInto'
                  构建ARouter$$Route$$Demo文件中的方法
                  @Override
                  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes){}
                 */
                MethodSpec.Builder loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                        .addAnnotation(Override.class)
                        .addModifiers(PUBLIC)
                        .addParameter(rootParamSpec);
    
                //  Follow a sequence, find out metas of group first, generate java file, then statistics them as root.
                for (Element element : routeElements) {
                    TypeMirror tm = element.asType();
                    Route route = element.getAnnotation(Route.class);
                    RouteMeta routeMeta;
    
                    // Activity or Fragment
                    if (types.isSubtype(tm, type_Activity) || types.isSubtype(tm, fragmentTm) || types.isSubtype(tm, fragmentTmV4)) {
                        // Get all fields annotation by @Autowired
                        Map<String, Integer> paramsType = new HashMap<>();
                        Map<String, Autowired> injectConfig = new HashMap<>();
                        for (Element field : element.getEnclosedElements()) {
                            if (field.getKind().isField() && field.getAnnotation(Autowired.class) != null && !types.isSubtype(field.asType(), iProvider)) {
                                // It must be field, then it has annotation, but it not be provider.
                                Autowired paramConfig = field.getAnnotation(Autowired.class);
                                String injectName = StringUtils.isEmpty(paramConfig.name()) ? field.getSimpleName().toString() : paramConfig.name();
                                paramsType.put(injectName, typeUtils.typeExchange(field));
                                injectConfig.put(injectName, paramConfig);
                            }
                        }
    
                        if (types.isSubtype(tm, type_Activity)) {
                            // Activity
                            logger.info(">>> Found activity route: " + tm.toString() + " <<<");
                            routeMeta = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);
                        } else {
                            // Fragment
                            logger.info(">>> Found fragment route: " + tm.toString() + " <<<");
                            routeMeta = new RouteMeta(route, element, RouteType.parse(FRAGMENT), paramsType);
                        }
    
                        routeMeta.setInjectConfig(injectConfig);
                    } else if (types.isSubtype(tm, iProvider)) {         // IProvider
                        logger.info(">>> Found provider route: " + tm.toString() + " <<<");
                        routeMeta = new RouteMeta(route, element, RouteType.PROVIDER, null);
                    } else if (types.isSubtype(tm, type_Service)) {           // Service
                        logger.info(">>> Found service route: " + tm.toString() + " <<<");
                        routeMeta = new RouteMeta(route, element, RouteType.parse(SERVICE), null);
                    } else {
                        throw new RuntimeException("The @Route is marked on unsupported class, look at [" + tm.toString() + "].");
                    }
    
                    // 对路由节点进行分组
                    categories(routeMeta);
                }
                
                /**
                *构建provider类型方法
                *也就是ARouter$$Providers$$app类文件中的loadInto的方法
                */
                MethodSpec.Builder loadIntoMethodOfProviderBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                        .addAnnotation(Override.class)
                        .addModifiers(PUBLIC)
                        .addParameter(providerParamSpec);
    
                Map<String, List<RouteDoc>> docSource = new HashMap<>();
    
                // Start generate java source, structure is divided into upper and lower levels, used for demand initialization.
                for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) {
                    String groupName = entry.getKey();
                    /**
                     * 构建 ARouter$$Group$$Demo类中的方法 loadInto
                     */
                    MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                            .addAnnotation(Override.class)
                            .addModifiers(PUBLIC)
                            .addParameter(groupParamSpec);
    
                    List<RouteDoc> routeDocList = new ArrayList<>();
    
                    // Build group method body
                    Set<RouteMeta> groupData = entry.getValue();
                    // 一个Group中可以有多个路由节点
                    // 一个Group对应一个路由清单文件
                    // 一个路由清单文件中loadInto()方法可以存储多个路由节点的映射关系
                    for (RouteMeta routeMeta : groupData) {
                        RouteDoc routeDoc = extractDocInfo(routeMeta);
    
                        ClassName className = ClassName.get((TypeElement) routeMeta.getRawType());
    
                        // 这里做了节点的类别判断 仅仅做了Provider类型的判断
                        // 这也是为什么Provider不仅出现在 PROVIDER类型的路由节点列表中,也出现在GROUP类型的路由节点中
                        switch (routeMeta.getType()) {
                            case PROVIDER:  // Need cache provider's super class
                                List<? extends TypeMirror> interfaces = ((TypeElement) routeMeta.getRawType()).getInterfaces();
                                for (TypeMirror tm : interfaces) {
                                    routeDoc.addPrototype(tm.toString());
                                    // 为Provider类型的类文件中的 loadInto()方法中添加方法体的
                                    if (types.isSameType(tm, iProvider)) {   // Its implements iProvider interface himself.
                                        // This interface extend the IProvider, so it can be used for mark provider
                                        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)) {
                                        // This interface extend the IProvider, so it can be used for mark provider
                                        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;
                        }
    
                        // Make map body for paramsType
                        StringBuilder mapBodyBuilder = new StringBuilder();
                        Map<String, Integer> paramsType = routeMeta.getParamsType();
                        Map<String, Autowired> injectConfigs = routeMeta.getInjectConfig();
                        if (MapUtils.isNotEmpty(paramsType)) {
                            List<RouteDoc.Param> paramList = new ArrayList<>();
    
                            for (Map.Entry<String, Integer> types : paramsType.entrySet()) {
                                mapBodyBuilder.append("put(\"").append(types.getKey()).append("\", ").append(types.getValue()).append("); ");
    
                                RouteDoc.Param param = new RouteDoc.Param();
                                Autowired injectConfig = injectConfigs.get(types.getKey());
                                param.setKey(types.getKey());
                                param.setType(TypeKind.values()[types.getValue()].name().toLowerCase());
                                param.setDescription(injectConfig.desc());
                                param.setRequired(injectConfig.required());
    
                                paramList.add(param);
                            }
    
                            routeDoc.setParams(paramList);
                        }
                        String mapBody = mapBodyBuilder.toString();
    
                        // 构建group分组的loadInto的方法体
                        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());
    
                        routeDoc.setClassName(className.toString());
                        routeDocList.add(routeDoc);
                    }
    
                    // Generate groups
                    //完成整个group分组中的loadInto()方法的构建,以及类文件的创建
                    /**
                     * public class ARouter$$Group$$xxxx{
                     *      public void loadInto(Map<String,RouteMeta> alts){
                     *          alts.put("/test/test1",RouteMeta.build())
                     *      }
                     * }
                     */
                    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);
    
                    logger.info(">>> Generated group: " + groupName + "<<<");
                    rootMap.put(groupName, groupFileName);
                    docSource.put(groupName, routeDocList);
                }
    
                if (MapUtils.isNotEmpty(rootMap)) {
                    // Generate root meta by group name, it must be generated before root, then I can find out the class of group.
                    for (Map.Entry<String, String> entry : rootMap.entrySet()) {
                        // 构建ARouter$$Root$$XXX类文件中的loadInto()方法
                        loadIntoMethodOfRootBuilder.addStatement("routes.put($S, $T.class)", entry.getKey(), ClassName.get(PACKAGE_OF_GENERATE_FILE, entry.getValue()));
                    }
                }
    
                // Output route doc
                if (generateDoc) {
                    docWriter.append(JSON.toJSONString(docSource, SerializerFeature.PrettyFormat));
                    docWriter.flush();
                    docWriter.close();
                }
    
                // Write provider into disk
                //创建 ARouter$$Provider$$xxx类文件
                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);
    
                logger.info(">>> Generated provider map, name is " + providerMapFileName + " <<<");
    
                // Write root meta into disk.
                // 完成ARouter$$Root$$xxx 类文件的生成
                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);
    
                logger.info(">>> Generated root, name is " + rootFileName + " <<<");
            }
        }
    
    • 总结
      • 在parseRoutes方法中分别生成了 ARouter_Root_XX类文件、ARouter_Group_XX类文件以及ARouter_Provider_XX类文件
      • 同时在此方法中为生成文件类中生成了laodInto()方法,和各自对应的方法体
      • 对于Providr类型的,不仅存在ARouter_Provider_XX类文件中,也存在于ARouter_Group_XX类文件
      • 只不过对于Provider类型,在ARouter_Provider_XX类文件中与在ARouter_Group_XX类文件中存储的形式不同,key是不同的,在ARouter_Provider_XX类文件中是以实现IProvider类型接口的全限定名为key,在ARouter_Group_XX类文件中是以路由地址为key的
    categories()
    private void categories(RouteMeta routeMete) {
            // 校验工作 校验path地址的合法性,并为group属性设置一个具体路径,(默认可以是"",但是在组装的时候拿的是路由地址的一级路径)
            if (routeVerify(routeMete)) {
                logger.info(">>> Start categories, group = " + routeMete.getGroup() + ", path = " + routeMete.getPath() + " <<<");
                // groupMap 是以路由地址的一级路径为key,value是相同的group路由节点集合
                Set<RouteMeta> routeMetas = groupMap.get(routeMete.getGroup());
                if (CollectionUtils.isEmpty(routeMetas)) {
                    Set<RouteMeta> routeMetaSet = new TreeSet<>(new Comparator<RouteMeta>() {
                        @Override
                        public int compare(RouteMeta r1, RouteMeta r2) {
                            try {
                                return r1.getPath().compareTo(r2.getPath());
                            } catch (NullPointerException npe) {
                                logger.error(npe.getMessage());
                                return 0;
                            }
                        }
                    });
                    //添加路由节点
                    routeMetaSet.add(routeMete);
                    groupMap.put(routeMete.getGroup(), routeMetaSet);
                } else {
                    //groupMap中已有对应到的group集合,那么将路由节点直接添加到集合中
                    routeMetas.add(routeMete);
                }
            } else {
                logger.warning(">>> Route meta verify error, group is " + routeMete.getGroup() + " <<<");
            }
        }
        
    
        /**
         * Verify the route meta
         *
         * @param meta raw meta
         */
        private boolean routeVerify(RouteMeta meta) {
            //获取到路由地址
            String path = meta.getPath();
            
            // 检验路由地址
            if (StringUtils.isEmpty(path) || !path.startsWith("/")) {   // The path must be start with '/' and not empty!
                return false;
            }
            
            // 当是一个默认分组的话(默认组是一个空的字符串)
            if (StringUtils.isEmpty(meta.getGroup())) { // Use default group(the first word in path)
                try {
                    // 获取到路由地址的一级地址
                    String defaultGroup = path.substring(1, path.indexOf("/", 1));
                    if (StringUtils.isEmpty(defaultGroup)) {
                        return false;
                    }
                    
                    // 设置一级地址给 RouteMeta对象中的group字段
                    meta.setGroup(defaultGroup);
                    return true;
                } catch (Exception e) {
                    logger.error("Failed to extract default group! " + e.getMessage());
                    return false;
                }
            }
    
            return true;
        }
    
    • 总结
      • categories()方法中进行分组的操作,将一级地址与路由地址进行匹配,存在到map中,以及地址作为key,路由地址对应的RouteMeta对象存储到Set集合,Set集合为Value。
      • routeVerify()方法是用来校验路由地址的,并且为RouteMeta对象中的group字段赋值(当是""空字符串,把一级地址赋值给group字段)

    相关文章

      网友评论

          本文标题:ARouter注解处理器分析-RouteProcessor

          本文链接:https://www.haomeiwen.com/subject/lckncktx.html