利用APT实现android路由框架一

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

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

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

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

推荐阅读更多精彩内容