美文网首页
利用APT实现android路由框架一

利用APT实现android路由框架一

作者: biubiubiuboy | 来源:发表于2020-11-25 17:14 被阅读0次

    随着业务的发展,项目会越来越大,实现组件化便于更好的维护项目以及拆分。

    3.png
    业务组件相互独立,需要路由来完成之间的跳转与数据传递。
    实现方法:
    1、 使用 EventBus的方式,缺点是:EventBean维护成本太高,不好去管理:
    2、 使用广播的方式,缺点是:不好管理,都统一发出去了
    3、 使用隐士意图方式,缺点是:在AndroidManifest.xml里面配置xml写的太多了
    4、 使用类加载方式,缺点就是,容易写错包名类名,缺点较少
    5、 使用全局Map的方式,缺点是,要注册很多的对象
    6、事件总线的思路。(可以参考CC
    1~5这些不好维护,我们可以利用APT、JavaPoet技术参考阿里的ARouter、ButterKnife等思路来完成自己的一个Router路由框架,先了解一下APT以及JavaPoet。
    APT: annotation process tool是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,使用Annotation进行额外的处理。
    APT 编译的时候 ----------> 处理注解 --------> 生成代码----------> 编译完成
    JavaPoet: JavaPoet是square推出的开源java代码生成框架,提供Java Api生成.java源文件,是Java面向对象OOP语法 ,可以很方便的使用它根据注解生成对应代码,通过这种自动化生成代码的方式, 可以让我们用更加简洁优雅的方式要替代繁琐冗杂的重复工作。
    传统的APT : package ---> class ---> method
    JavaPoet: method---->class----->package

    我们看一下我们项目的框架


    image.png

    我们类跳转的时候,先找到group,在找到对应的path就可以跳转的目标了。我们可以用两个Map来实现group和path映射。先看一下效果:
    group

    public class ARouter$$Group$$user implements ARouterGroup {
      @Override
      public Map<String, Class<? extends ARouterPath>> getGroupMap() {
        Map<String, Class<? extends ARouterPath>> groupMap = new HashMap<>();
        groupMap.put("user",ARouter$$Path$$user.class);
        return groupMap;
      }
    }
    

    path

    public class ARouter$$Path$$user implements ARouterPath {
      @Override
      public Map<String, RouterBean> getPathMap() {
        Map<String, RouterBean> pathMap = new HashMap<>();
        pathMap.put("/user/User_LoginAcitivty", RouterBean.create(RouterBean.TypeEnum.ACTIVITY,User_LoginAcitivty.class,"/user/User_LoginAcitivty","user"));
        pathMap.put("/user/User_MainActivity", RouterBean.create(RouterBean.TypeEnum.ACTIVITY,User_MainActivity.class,"/user/User_MainActivity","user"));
        return pathMap;
      }
    }
    

    1、我们可以仿照ARouter的架构来搭建自己的框架


    image.png

    arouter-annotation、arouter-compiler为java-library。把注解列存放在arouter-annotation包中。(注解不清楚的先了解下)


    image.png

    arouter-compiler的gradle要依赖


    image.png


    ArouterPath接口

    public interface ARouterPath {
    
        /**
         * 存放的内容key:/app/MainActivity value:RouterBean == App_MainActivity.class
         */
        Map<String, RouterBean> getPathMap();
    }
    

    RouterBean用于封装app、user等组件中的MainActivity等对象, 封装成对象会有更多的属性

    public class RouterBean {
    
        public enum TypeEnum {//可扩展 fragment
            ACTIVITY
        }
    
        private TypeEnum typeEnum;//枚举类型activity
        private Element element;//节点 JavaPoet,可以拿到很多的信息
        private Class<?> myClass;// 被注解 class对象 eg: MainActivity.class
        private String path;// 路由地址 eg: /app/MainActivity
        private String group;// 路由组  eg: app user等
    
    
        public TypeEnum getTypeEnum() {
            return typeEnum;
        }
    
        public void setTypeEnum(TypeEnum typeEnum) {
            this.typeEnum = typeEnum;
        }
    
        public Element getElement() {
            return element;
        }
    
        public void setElement(Element element) {
            this.element = element;
        }
    
        public Class<?> getMyClass() {
            return myClass;
        }
    
        public void setMyClass(Class<?> myClass) {
            this.myClass = myClass;
        }
    
        public String getPath() {
            return path;
        }
    
        public void setPath(String path) {
            this.path = path;
        }
    
        public String getGroup() {
            return group;
        }
    
        public void setGroup(String group) {
            this.group = group;
        }
    
        private RouterBean(TypeEnum typeEnum, Class<?> myClass, String path, String group) {
            this.typeEnum = typeEnum;
            // this.element = element;
            this.myClass = myClass;
            this.path = path;
            this.group = group;
        }
    
        //对外暴露 为了方便APT生成代码
        public static RouterBean create(TypeEnum typeEnum, Class<?> clazz,String path, String group) {
            return new RouterBean(typeEnum, clazz, path, group);
        }
    
        /**
         * 构建者模式
         */
    
        public static class Builder {
            // 枚举类型:Activity
            private TypeEnum type;
            // 类节点
            private Element element;
            // 注解使用的类对象
            private Class<?> clazz;
            // 路由地址
            private String path;
            // 路由组
            private String group;
    
            public Builder addType(TypeEnum type) {
                this.type = type;
                return this;
            }
    
            public Builder addElement(Element element) {
                this.element = element;
                return this;
            }
    
            public Builder addClazz(Class<?> clazz) {
                this.clazz = clazz;
                return this;
            }
    
            public Builder addPath(String path) {
                this.path = path;
                return this;
            }
    
            public Builder addGroup(String group) {
                this.group = group;
                return this;
            }
    
            public RouterBean build() {
                if (path == null || path.length() == 0) {
                    throw new IllegalArgumentException("path路径为空,请检查" + clazz);
                }
    
                return new RouterBean(this);
            }
        }
    
        private RouterBean(Builder builder) {
            this.typeEnum = builder.type;
            this.element = builder.element;
            this.myClass = builder.clazz;
            this.path = builder.path;
            this.group = builder.group;
        }
    
        @Override
        public String toString() {
            return "RouterBean{" +
                    "path='" + path + '\'' +
                    ", group='" + group + '\'' +
                    '}';
        }
    }
    
    
    public interface ARouterGroup {
    
        /**
         *
         * 存放的内容key:app  value:app所有的path类
         */
        Map<String, Class<? extends ARouterPath>> getGroupMap();
    }
    
    

    工具类

    package com.htf.arouter_compiler.util;
    
    public interface ProcessorConfig {
    
        // @ARouter注解 的 包名 + 类名
        String AROUTER_PACKAGE =  "com.htf.arouter_annotation.ARouter";
    
        //
        String PARAMETER_PACKAGE =  "com.htf.arouter_annotation.Parameter";
    
        // 接收module参数
        String OPTIONS = "moduleName";
    
        // 目的是接收 包名
        String APT_PACKAGE = "packageNameForAPT";
    
        // 为了匹配加注解的类是否为Activity
        public static final String ACTIVITY_PACKAGE = "android.app.Activity";
    
        // ARouter api 包名
        String AROUTER_API_PACKAGE = "com.htf.arouter_api";
    
        // ARouter api 的 ARouterPath 高层标准
        String AROUTER_API_PATH = AROUTER_API_PACKAGE + ".ARouterPath";
    
        // ARouter api 的 ARouterGroup 高层标准
        String AROUTER_API_GROUP = AROUTER_API_PACKAGE + ".ARouterGroup";
    
        // 路由组,中的 Path 里面的 方法名
        String PATH_METHOD_NAME = "getPathMap";
    
        // 路由组,中的 Path 里面 的 变量名
        String PATH_VAR1 = "pathMap";
    
        // 路由组,PATH 最终要生成的 文件名
        String PATH_FILE_NAME = "ARouter$$Path$$";
        // 路由组,中的 Group 里面的 方法名
        String GROUP_METHOD_NAME = "getGroupMap";
    
        // 路由组,中的 Group 里面 的 变量名 1
        String GROUP_VAR1 = "groupMap";
    
        // 路由组,GROUP 最终要生成的 文件名
        String GROUP_FILE_NAME = "ARouter$$Group$$";
    
        // ARouter api 的 ParameterGet 高层标准
        String AROUTER_AIP_PARAMETER_DATA = AROUTER_API_PACKAGE + ".ParameterData";
    
        // ARouter api 的 ParmeterGet 方法的名字
        String PARAMETER_METHOD_NAME = "getParameter";
    
        // ARouter api 的 ParameterGet 方法参数的名字
        String PARAMETER_NAME = "targetParameter";
    
        // String全类名
        String STRING = "java.lang.String";
    
        //用户判断是否是自定义对象类型 要实现Parcelable
        String PARCELABLE = "android.os.Parcelable";
    
        // ARouter aip 的 ParmeterGet 的 生成文件名称 $$Parameter
        String PARAMETER_FILE_NAME = "$$Parameter";
    
        //用户判断是否是List类型
        String LIST_STRING = "java.util.List<java.lang.String>";
    
        String LIST_PARCELABLE = "java.util.List<&T>";
    }
    
    

    利用APT生成ARouter$$Path$$userARouter$$Group$$user文件

    @AutoService(Processor.class)
    @SupportedSourceVersion(SourceVersion.RELEASE_8)
    @SupportedAnnotationTypes("com.htf.arouter_annotation.ARouter")//自己的注解类
    @SupportedOptions({"moduleName", "packageNameForAPT"})//  moduleName接收module参数;packageNameForAPT目的是接收 包名
    public class ARouterProcessor extends AbstractProcessor {
    
        private Types typeTool;//// type(类信息)的工具类,包含用于操作TypeMirror的工具方法
    
        private Elements elementTool;// 操作Element的工具类(类,函数,属性,其实都是Element)
    
        private Messager messager;
    
        private Filer filer;
    
        private String options; // 各个模块传递过来的模块名 例如:app user zixun order等
    
        private String aptPackage; // 各个模块传递过来的目录 用于统一存放 apt生成的文件
    
        private Map<String, List<RouterBean>> mAllPathMap = new HashMap<>();
    
        // Map<"user", "ARouter$$Path$$user.class">
        private Map<String, String> mAllGroupMap = new HashMap<>();
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
            elementTool = processingEnvironment.getElementUtils();
            filer = processingEnvironment.getFiler();
            messager = processingEnvironment.getMessager();
            typeTool = processingEnvironment.getTypeUtils();
            options = processingEnvironment.getOptions().get(ProcessorConfig.OPTIONS);
            aptPackage = processingEnvironment.getOptions().get(ProcessorConfig.APT_PACKAGE);
    
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            if (set.isEmpty()) {
                messager.printMessage(Diagnostic.Kind.NOTE, "没有发现注解的类");
                return false;
            }
            // // 获取所有被 @ARouter 注解的 元素集合
            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
            // 通过Element工具类,获取android.app.Activity类型
            TypeElement activityElement = elementTool.getTypeElement(ProcessorConfig.ACTIVITY_PACKAGE);
            TypeMirror activityMirror = activityElement.asType();
            for (Element element : elements) {
                String className = element.getSimpleName().toString();
                ARouter aRouter = element.getAnnotation(ARouter.class);
                messager.printMessage(Diagnostic.Kind.NOTE, "aRouter.group() == " + aRouter.group());
                RouterBean routerBean = new RouterBean.Builder()
                        .addElement(element)
                        .addGroup(options)
                        .addPath(aRouter.path())
                        .build();
                TypeMirror myMirror = element.asType();//自己类的TypeMirror 用于判断是不是继承android.app.Activity
                if (typeTool.isSubtype(myMirror, activityMirror)) {//是activity
                    routerBean.setTypeEnum(RouterBean.TypeEnum.ACTIVITY);
                } else {
                    throw new RuntimeException(className + "必须是Activity");
                }
                //把所有的注解类封以module名为key封装到mAllPathMap中
                if (checkRouterPath(routerBean)) {
                    List<RouterBean> routerBeanList = mAllPathMap.get(routerBean.getGroup());
                    if (ProcessorUtils.isEmpty(routerBeanList)) {
                        routerBeanList = new ArrayList<>();
                        routerBeanList.add(routerBean);
                        mAllPathMap.put(routerBean.getGroup(), routerBeanList);
                    } else {
                        routerBeanList.add(routerBean);
                    }
                } else {
                    messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解未按规范配置,如:/app/MainActivity");
                }
            }//循环结束,把所有的注解类以module名为key封装到mAllPathMap
    
            // TODO 开始用javapoet生成文件
            //定义文件要实现的接口
            TypeElement pathTypeElement = elementTool.getTypeElement(ProcessorConfig.AROUTER_API_PATH);
            try {
                createPathFile(pathTypeElement);
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            TypeElement groupType = elementTool.getTypeElement(ProcessorConfig.AROUTER_API_GROUP);
            try {
                createGroupFile(groupType, pathTypeElement);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return true;
        }
    
        //生成路由组Group文件,如:ARouter$$Group$$app
        private void createGroupFile(TypeElement groupType, TypeElement pathType) throws IOException {
            if (ProcessorUtils.isEmpty(mAllGroupMap) || ProcessorUtils.isEmpty(mAllPathMap)) return;
            //要生成的内容
    
            //Map<String, Class<? extends ARouterPath>>
            TypeName methodReturn = ParameterizedTypeName.get(
                    ClassName.get(Map.class),
                    ClassName.get(String.class),
                    //Class<? extends ARouterPath>>
                    ParameterizedTypeName.get(ClassName.get(Class.class),
                            WildcardTypeName.subtypeOf(ClassName.get(pathType))) // ? extends ARouterLoadPath)
            );
    
            MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(ProcessorConfig.GROUP_METHOD_NAME)
                    .addAnnotation(Override.class)
                    .addModifiers(Modifier.PUBLIC)
                    .returns(methodReturn);
    
            //方法里的内容
            // Map<String, Class<? extends ARouterPath>> groupMap = new HashMap<>();
            methodBuilder.addStatement("$T<$T, $T> $N = new $T<>()",
                    ClassName.get(Map.class),
                    ClassName.get(String.class),
                    //Class<? extends ARouterPath>
                    ParameterizedTypeName.get(ClassName.get(Class.class),
                            WildcardTypeName.subtypeOf(ClassName.get(pathType))), // ? extends ARouterPath
                    ProcessorConfig.GROUP_VAR1,
                    ClassName.get(HashMap.class));
    
            //  groupMap.put("app", ARouter$$Path$$app.class);
            //  groupMap.put("user", ARouter$$Path$$user.class);
            methodBuilder.addStatement("$N.put($S,$T.class)",
                    ProcessorConfig.GROUP_VAR1,
                    options,
                    ClassName.get(aptPackage, mAllGroupMap.get(options)));
            methodBuilder.addStatement("return $N", ProcessorConfig.GROUP_VAR1);
    
            ///生成文件
            String fileClassName = ProcessorConfig.GROUP_FILE_NAME + options;
            JavaFile.builder(aptPackage,
                    TypeSpec.classBuilder(fileClassName)
                            .addSuperinterface(ClassName.get(groupType))
                            .addModifiers(Modifier.PUBLIC)
                            .addMethod(methodBuilder.build())
                            .build())
                    .build()
                    .writeTo(filer);
    
        }
    
        private void createPathFile(TypeElement typeElement) throws IOException {
    
            //先把方法返回类型生成   Map<String, RouterBean>
            ParameterizedTypeName methodReturn = ParameterizedTypeName.get(
                    ClassName.get(Map.class),
                    ClassName.get(String.class),
                    ClassName.get(RouterBean.class)
            );
            //生成方法
            MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(ProcessorConfig.PATH_METHOD_NAME)
                    .addModifiers(Modifier.PUBLIC)
                    .addAnnotation(Override.class)
                    .returns(methodReturn);
    
            for (Map.Entry<String, List<RouterBean>> entry : mAllPathMap.entrySet()) {
                ////方法里的内容 Map<String, RouterBean> pathMap = new HashMap<>();
                methodBuilder.addStatement("$T<$T, $T> $N = new $T<>()",
                        ClassName.get(Map.class),
                        ClassName.get(String.class),
                        ClassName.get(RouterBean.class),
                        ProcessorConfig.PATH_VAR1,
                        ClassName.get(HashMap.class));
                List<RouterBean> routerBeanList = entry.getValue();
    
                for (RouterBean routerBean : routerBeanList) {
                    methodBuilder.addStatement("$N.put($S, $T.create($T.$L, $T.class, $S, $S))",
                            ProcessorConfig.PATH_VAR1,
                            routerBean.getPath(),
                            ClassName.get(RouterBean.class),
                            ClassName.get(RouterBean.TypeEnum.class),
                            routerBean.getTypeEnum(),
                            ClassName.get((TypeElement) routerBean.getElement()),
                            routerBean.getPath(),
                            routerBean.getGroup());
                }
                // return pathMap;
                methodBuilder.addStatement("return $N", ProcessorConfig.PATH_VAR1);
            }
    
            //生成的类文件名ARouter$$Path$$user    有implements所以方法和类要合为一体生成
            String fileName = ProcessorConfig.PATH_FILE_NAME + options;
            messager.printMessage(Diagnostic.Kind.NOTE, "APT生成路由Path类文件:" +
                    aptPackage + "." + fileName);
            JavaFile.builder(aptPackage,
                    TypeSpec.classBuilder(fileName)
                            .addSuperinterface(ClassName.get(typeElement))
                            .addMethod(methodBuilder.build())
                            .addModifiers(Modifier.PUBLIC)
                            .build())
                    .build()
                    .writeTo(filer);
            mAllGroupMap.put(options, fileName);
        }
    
        private boolean checkRouterPath(RouterBean bean) {
            String group = bean.getGroup();//app、user、zixun等module
            String path = bean.getPath();//   /app/MainActivity
            // TODO 校验path
            // @ARouter注解中的path值,必须要以 / 开头
            if (ProcessorUtils.isEmpty(path) || !path.startsWith("/")) {
                messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解中的path值,必须要以 / 开头");
                return false;
            }
            if (path.lastIndexOf("/") == 0) {//开发者必须遵循
                messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解未按规范配置,如:/app/MainActivity");
                return false;
            }
    
            // 从第一个 / 到第二个 / 中间截取,如:/app/MainActivity 截取出 app,order,personal 作为group
            String finalGroup = path.substring(1, path.indexOf("/", 1));
            if (!ProcessorUtils.isEmpty(group) && !group.equals(options)) {
                messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解中的group值必须和子模块名一致!");
                return false;
            } else {
                bean.setGroup(finalGroup);
            }
            return true;
        }
    }
    

    ARouterProcessor会检测带注解,利用roundEnvironment.getElementsAnnotatedWith(ARouter.class);来判断哪些是自己ARouter注解类,然后javapoet语法生成Java文件。build一下项目。
    不要忘了在类上添加注解


    image.png

    最后生成文件的效果


    image.png

    最后调用

    ARouter$$Group$$user  group$$user = new ARouter$$Group$$user();
            Map<String, Class<? extends ARouterPath>> groupMap = group$$user .getGroupMap();
            Class<? extends ARouterPath> myClass = groupMap.get("user ");
    
            try {
                ARouter$$Path$$user path = (ARouter$$Path$$user ) myClass.newInstance();
                Map<String, RouterBean> pathMap = path.getPathMap();
                RouterBean bean = pathMap.get("/user /User_LoginAcitivty");
    
                if (bean != null) {
                    Intent intent = new Intent(this, bean.getMyClass());
                    startActivity(intent);
                }
    
            } catch (Exception e) {
                e.printStackTrace();
            }
    

    现在调用还复杂化、不能参数据,在第二篇文章将会再次封装,源码链接放在第二篇文章里。

    相关文章

      网友评论

          本文标题:利用APT实现android路由框架一

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