当前位置:   article > 正文

Android架构师-组件化-组件化APT生成路由Group和Path文件 7_apt 获取group名称

apt 获取group名称

上文  即组件化文章6中,我们遗留的问题是在app中通过RouterManager去add每一个Activity,这样操作很耗时,之前我们也说过,可以在ARouterProcessor,即注解处理器中动态扫描到添加了ARouter注解的文件,然后在process中动态解析,我们现在主要来完成这一步操作

目标:我们需要完成以下两个文件的生成

1:首先我们需要知道我们要通过注解处理器生成的目标文件

  1. /**
  2. * 路由组Group加载数据接口
  3. */
  4. public interface ARouterLoadGroup {
  5. /**
  6. * 加载路由组Group数据
  7. * 比如:"app", ARouter$$Path$$app.class(实现了ARouterLoadPath接口)
  8. *
  9. * @return key:"app", value:"app"分组对应的路由详细对象类
  10. */
  11. Map<String, Class<? extends ARouterLoadPath>> loadGroup();
  12. }

模拟ARouter路由组文件  ,实现了ARouterLoadGroup接口 ,在Map集合中添加对应的ARouter

Path
xx.class文件

  1. /**
  2. * 模拟ARouter路由器的组文件
  3. */
  4. public class ARouter$$Group$$order implements ARouterLoadGroup {
  5. @Override
  6. public Map<String, Class<? extends ARouterLoadPath>> loadGroup() {
  7. Map<String, Class<? extends ARouterLoadPath>> groupMap = new HashMap<>();
  8. groupMap.put("order", ARouter$$Path$$order.class);
  9. return groupMap;
  10. }
  11. }

 还有ARouterLoadPath,

  1. /**
  2. * 路由组Group对应的详细Path加载数据接口
  3. * 比如:app分组对应有哪些类需要加载
  4. */
  5. public interface ARouterLoadPath {
  6. /**
  7. * 加载路由组Group中的Path详细数据
  8. * 比如:"app"分组下有这些信息:
  9. *
  10. * @return key:"/app/MainActivity", value:MainActivity信息封装到RouterBean对象中
  11. */
  12. Map<String, RouterBean> loadPath();
  13. }

路由族文件继承自ARouterLoadpath文件 

  1. /**
  2. * 模拟ARouter路由器的组文件,对应的路径文件
  3. */
  4. public class ARouter$$Path$$order implements ARouterLoadPath {
  5. @Override
  6. public Map<String, RouterBean> loadPath() {
  7. Map<String, RouterBean> pathMap = new HashMap<>();
  8. pathMap.put("/order/Order_MainActivity",
  9. RouterBean.create(RouterBean.Type.ACTIVITY, Order_MainActivity.class,
  10. "/order/Order_MainActivity", "order"));
  11. return pathMap;
  12. }
  13. }

那么组名是怎么传到注解处理器中的呢?

我们前面讲过,是通过build.gradle

 

还有一个问题,我们所有生成的APT文件都放到哪里呢?

我们建一个apt的包,把这些文件都归类到apt的包下面。

 

所以我们需要做两件事,第一件事通过build.gradle传子模块名(Group名字)

                                       第二件事把apt生成的类文件归类到另外一个包下面来。

 

1:在每个子模块的build.gradle中将这两个参数传过来

首先在项目的config.gradle中定义apt类文件生成路径

  1. // 包名,用于存放APT生成的类文件
  2. packageNameForAPT = "com.netease.modular.apt"

 

  1. // 在gradle文件中配置选项参数值(用于APT传参接收)
  2. // 切记:必须写在defaultConfig节点下
  3. javaCompileOptions {
  4. annotationProcessorOptions {
  5. arguments = [moduleName: project.getName(), packageNameForAPT: packageNameForAPT]
  6. }
  7. }

第一个moduleName:子模块名字

第二个:apt生成在哪个包下面

我们在arouter_compiler中的常量类中添加两个常量

  1. // 每个子模块的模块名
  2. public static final String MODULE_NAME = "moduleName";
  3. // 包名,用于存放APT生成的类文件
  4. public static final String APT_PACKAGE = "packageNameForAPT";

最后生成的效果如下:

 

2:我们需要在注解处理器中获取这两个参数

  1. @Override
  2. public synchronized void init(ProcessingEnvironment processingEnvironment) {
  3. super.init(processingEnvironment);
  4. elementUtils = processingEnvironment.getElementUtils();
  5. typeUtils = processingEnvironment.getTypeUtils();
  6. messager = processingEnvironment.getMessager();
  7. filer = processingEnvironment.getFiler();
  8. // 通过ProcessingEnvironment去获取对应的参数
  9. Map<String, String> options = processingEnvironment.getOptions();
  10. if (!EmptyUtils.isEmpty(options)) {
  11. moduleName = options.get(Constants.MODULE_NAME);
  12. packageNameForAPT = options.get(Constants.APT_PACKAGE);
  13. // 有坑:Diagnostic.Kind.ERROR,异常会自动结束,不像安卓中Log.e
  14. messager.printMessage(Diagnostic.Kind.NOTE, "moduleName >>> " + moduleName);
  15. messager.printMessage(Diagnostic.Kind.NOTE, "packageNameForAPT >>> " + packageNameForAPT);
  16. }
  17. // 必传参数判空(乱码问题:添加java控制台输出中文乱码)
  18. if (EmptyUtils.isEmpty(moduleName) || EmptyUtils.isEmpty(packageNameForAPT)) {
  19. throw new RuntimeException("注解处理器需要的参数moduleName或者packageName为空,请在对应build.gradle配置参数");
  20. }
  21. }

我们拿到具体的参数后,开始处理数据,通过APT+JavaPoet动态生成apt类文件

  1. /**
  2. * 相当于main函数,开始处理注解
  3. * 注解处理器的核心方法,处理具体的注解,生成Java文件
  4. *
  5. * @param set 使用了支持处理注解的节点集合
  6. * @param roundEnvironment 当前或是之前的运行环境,可以通过该对象查找的注解。
  7. * @return true 表示后续处理器不会再处理(已经处理完成)
  8. */
  9. @Override
  10. public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
  11. // 一旦有类之上使用@ARouter注解
  12. if (!EmptyUtils.isEmpty(set)) {
  13. // 获取所有被 @ARouter 注解的 元素集合
  14. Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
  15. if (!EmptyUtils.isEmpty(elements)) {
  16. // 解析元素
  17. try {
  18. parseElements(elements);
  19. return true;
  20. } catch (IOException e) {
  21. e.printStackTrace();
  22. }
  23. }
  24. // 坑:必须写返回值,表示处理@ARouter注解完成
  25. return true;
  26. }
  27. return false;
  28. }

 

  1. // 解析所有被 @ARouter 注解的 类元素集合
  2. private void parseElements(Set<? extends Element> elements) throws IOException {
  3. // 通过Element工具类,获取Activity、Callback类型
  4. TypeElement activityType = elementUtils.getTypeElement(Constants.ACTIVITY);
  5. // 显示类信息(获取被注解节点,类节点)这里也叫自描述 Mirror
  6. TypeMirror activityMirror = activityType.asType();
  7. // 遍历节点
  8. for (Element element : elements) {
  9. // 获取每个元素类信息,用于比较
  10. TypeMirror elementMirror = element.asType();
  11. messager.printMessage(Diagnostic.Kind.NOTE, "遍历元素信息:" + elementMirror.toString());
  12. // 获取每个类上的@ARouter注解中的注解值
  13. ARouter aRouter = element.getAnnotation(ARouter.class);
  14. // 路由详细信息,最终实体封装类
  15. RouterBean bean = new RouterBean.Builder()
  16. .setGroup(aRouter.group())
  17. .setPath(aRouter.path())
  18. .setElement(element)
  19. .build();
  20. // 高级判断:ARouter注解仅能用在类之上,并且是规定的Activity
  21. // 类型工具类方法isSubtype,相当于instance一样
  22. if (typeUtils.isSubtype(elementMirror, activityMirror)) {
  23. bean.setType(RouterBean.Type.ACTIVITY);
  24. } else {
  25. // 不匹配抛出异常,这里谨慎使用!考虑维护问题
  26. throw new RuntimeException("@ARouter注解目前仅限用于Activity类之上");
  27. }
  28. // 赋值临时map存储,用来存放路由组Group对应的详细Path类对象
  29. valueOfPathMap(bean);
  30. }
  31. // routerMap遍历后,用来生成类文件
  32. // 获取ARouterLoadGroup、ARouterLoadPath类型(生成类文件需要实现的接口)
  33. TypeElement groupLoadType = elementUtils.getTypeElement(Constants.AROUTE_GROUP); // 组接口
  34. TypeElement pathLoadType = elementUtils.getTypeElement(Constants.AROUTE_PATH); // 路径接口
  35. // 第一步:生成路由组Group对应详细Path类文件,如:ARouter$$Path$$app
  36. createPathFile(pathLoadType);
  37. // 第二步:生成路由组Group类文件(没有第一步,取不到类文件),如:ARouter$$Group$$app
  38. createGroupFile(groupLoadType, pathLoadType);
  39. }

 建立一个临时map的存储,用来存放路由组Group对应的详细Path类对象,

//例如:

第一次扫描到一个文件:/app/MainActivity

此时的List<RouterBean> routerBeans 为空,创建了一个新的ArraryList集合,以app(组名)为key,RouterBeans为值添加到Map集合中

第二次扫描到一个文件/app/MySetActivity,此时List<RouterBean> routerBeans不为空,直接以以app(组名)为key,RouterBeans为值添加到Map集合中

  1. /**
  2. * 赋值临时map存储,用来存放路由组Group对应的详细Path类对象,生成路由路径类文件时遍历
  3. *
  4. * @param bean 路由详细信息,最终实体封装类
  5. */
  6. private void valueOfPathMap(RouterBean bean) {
  7. if (checkRouterPath(bean)) {
  8. messager.printMessage(Diagnostic.Kind.NOTE, "RouterBean >>> " + bean.toString());
  9. // 开始赋值Map
  10. List<RouterBean> routerBeans = tempPathMap.get(bean.getGroup());
  11. // 如果从Map中找不到key为:bean.getGroup()的数据,就新建List集合再添加进Map
  12. if (EmptyUtils.isEmpty(routerBeans)) {
  13. routerBeans = new ArrayList<>();
  14. routerBeans.add(bean);
  15. tempPathMap.put(bean.getGroup(), routerBeans);
  16. } else { // 找到了key,直接加入List集合
  17. routerBeans.add(bean);
  18. }
  19. } else {
  20. messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解未按规范配置,如:/app/MainActivity");
  21. }
  22. }

这段是为了防止项目合作过程中出现ARouter注解填写不规范的情况

  1. /**
  2. * 校验@ARouter注解的值,如果group未填写就从必填项path中截取数据
  3. *
  4. * @param bean 路由详细信息,最终实体封装类
  5. */
  6. private boolean checkRouterPath(RouterBean bean) {
  7. String group = bean.getGroup();
  8. String path = bean.getPath();
  9. // @ARouter注解中的path值,必须要以 / 开头(模仿阿里Arouter规范)
  10. if (EmptyUtils.isEmpty(path) || !path.startsWith("/")) {
  11. messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解中的path值,必须要以 / 开头");
  12. return false;
  13. }
  14. // 比如开发者代码为:path = "/MainActivity",最后一个 / 符号必然在字符串第1位
  15. if (path.lastIndexOf("/") == 0) {
  16. // 架构师定义规范,让开发者遵循
  17. messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解未按规范配置,如:/app/MainActivity");
  18. return false;
  19. }
  20. // 从第一个 / 到第二个 / 中间截取,如:/app/MainActivity 截取出 app 作为group
  21. String finalGroup = path.substring(1, path.indexOf("/", 1));
  22. // @ARouter注解中的group有赋值情况
  23. if (!EmptyUtils.isEmpty(group) && !group.equals(moduleName)) {
  24. // 架构师定义规范,让开发者遵循
  25. messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解中的group值必须和子模块名一致!");
  26. return false;
  27. } else {
  28. bean.setGroup(finalGroup);
  29. }
  30. return true;
  31. }

最后从临时Map集合中开始通过JavaPoet去生成

ARouter$$Path$$xx (--->ARouter$$Path$$app )
  1. // APT生成的路由组Group类文件名
  2. public static final String GROUP_FILE_NAME = "ARouter$$Group$$";
  3. // APT生成的路由组Group对应的详细Path类文件名
  4. public static final String PATH_FILE_NAME = "ARouter$$Path$$";
  1. /**
  2. * 生成路由组Group对应详细Path,如:ARouter$$Path$$app
  3. *
  4. * @param pathLoadType ARouterLoadPath接口信息
  5. */
  6. private void createPathFile(TypeElement pathLoadType) throws IOException {
  7. // 判断是否有需要生成的类文件
  8. if (EmptyUtils.isEmpty(tempPathMap)) return;
  9. TypeName methodReturns = ParameterizedTypeName.get(
  10. ClassName.get(Map.class), // Map
  11. ClassName.get(String.class), // Map<String,
  12. ClassName.get(RouterBean.class) // Map<String, RouterBean>
  13. );
  14. // 遍历分组,每一个分组创建一个路径类文件,如:ARouter$$Path$$app
  15. for (Map.Entry<String, List<RouterBean>> entry : tempPathMap.entrySet()) {
  16. // 方法配置:public Map<String, RouterBean> loadPath() {
  17. MethodSpec.Builder methodBuidler = MethodSpec.methodBuilder(Constants.PATH_METHOD_NAME) // 方法名
  18. .addAnnotation(Override.class) // 重写注解
  19. .addModifiers(Modifier.PUBLIC) // public修饰符
  20. .returns(methodReturns); // 方法返回值
  21. // 遍历之前:Map<String, RouterBean> pathMap = new HashMap<>();
  22. //StringFormat形式
  23. methodBuidler.addStatement("$T<$T, $T> $N = new $T<>()",
  24. ClassName.get(Map.class),
  25. ClassName.get(String.class),
  26. ClassName.get(RouterBean.class),
  27. Constants.PATH_PARAMETER_NAME,
  28. HashMap.class);
  29. // 一个分组,如:ARouter$$Path$$app。有很多详细路径信息,如:/app/MainActivity、/app/OtherActivity
  30. List<RouterBean> pathList = entry.getValue();
  31. // 方法内容配置(遍历每个分组中每个路由详细路径)
  32. for (RouterBean bean : pathList) {
  33. // 类似String.format("hello %s net163 %d", "net", 163)通配符
  34. // pathMap.put("/app/MainActivity", RouterBean.create(
  35. // RouterBean.Type.ACTIVITY, MainActivity.class, "/app/MainActivity", "app"));
  36. methodBuidler.addStatement(
  37. "$N.put($S, $T.create($T.$L, $T.class, $S, $S))",
  38. Constants.PATH_PARAMETER_NAME, // pathMap.put
  39. bean.getPath(), // "/app/MainActivity"
  40. ClassName.get(RouterBean.class), // RouterBean
  41. ClassName.get(RouterBean.Type.class), // RouterBean.Type
  42. bean.getType(), // 枚举类型:ACTIVITY
  43. ClassName.get((TypeElement) bean.getElement()), // MainActivity.class
  44. bean.getPath(), // 路径名
  45. bean.getGroup() // 组名
  46. );
  47. }
  48. // 遍历之后:return pathMap;
  49. methodBuidler.addStatement("return $N", Constants.PATH_PARAMETER_NAME);
  50. // 最终生成的类文件名
  51. String finalClassName = Constants.PATH_FILE_NAME + entry.getKey();
  52. messager.printMessage(Diagnostic.Kind.NOTE, "APT生成路由Path类文件:" +
  53. packageNameForAPT + "." + finalClassName);
  54. // 生成类文件:ARouter$$Path$$app
  55. JavaFile.builder(packageNameForAPT, // 包名
  56. TypeSpec.classBuilder(finalClassName) // 类名
  57. .addSuperinterface(ClassName.get(pathLoadType)) // 实现ARouterLoadPath接口
  58. .addModifiers(Modifier.PUBLIC) // public修饰符
  59. .addMethod(methodBuidler.build()) // 方法的构建(方法参数 + 方法体)
  60. .build()) // 类构建完成
  61. .build() // JavaFile构建完成
  62. .writeTo(filer); // 文件生成器开始生成类文件
  63. // 非常重要一步!!!!!路径文件生成出来了,才能赋值路由组tempGroupMap
  64. //例如:tempGroupMap.put("app","ARouter$$Path$$app");
  65. tempGroupMap.put(entry.getKey(), finalClassName);
  66. }
  67. }
  68. .writeTo(filer); // 文件生成器开始生成类文件
  69. }

我们可以看到,在生成Path路径集合后,又向临时的group集合中添加了数据,然后开始创建路由group文件

  1. /**
  2. * 生成路由组Group文件,如:ARouter$$Group$$app
  3. *
  4. * @param groupLoadType ARouterLoadGroup接口信息
  5. * @param pathLoadType ARouterLoadPath接口信息
  6. */
  7. private void createGroupFile(TypeElement groupLoadType, TypeElement pathLoadType) throws IOException {
  8. // 判断是否有需要生成的类文件
  9. if (EmptyUtils.isEmpty(tempGroupMap) || EmptyUtils.isEmpty(tempPathMap)) return;
  10. TypeName methodReturns = ParameterizedTypeName.get(
  11. ClassName.get(Map.class), // Map
  12. ClassName.get(String.class), // Map<String,
  13. // 第二个参数:Class<? extends ARouterLoadPath>
  14. // 某某Class是否属于ARouterLoadPath接口的实现类
  15. ParameterizedTypeName.get(ClassName.get(Class.class),
  16. WildcardTypeName.subtypeOf(ClassName.get(pathLoadType)))
  17. );
  18. // 方法配置:public Map<String, Class<? extends ARouterLoadPath>> loadGroup() {
  19. MethodSpec.Builder methodBuidler = MethodSpec.methodBuilder(Constants.GROUP_METHOD_NAME) // 方法名
  20. .addAnnotation(Override.class) // 重写注解
  21. .addModifiers(Modifier.PUBLIC) // public修饰符
  22. .returns(methodReturns); // 方法返回值
  23. // 遍历之前:Map<String, Class<? extends ARouterLoadPath>> groupMap = new HashMap<>();
  24. methodBuidler.addStatement("$T<$T, $T> $N = new $T<>()",
  25. ClassName.get(Map.class),
  26. ClassName.get(String.class),
  27. ParameterizedTypeName.get(ClassName.get(Class.class),
  28. WildcardTypeName.subtypeOf(ClassName.get(pathLoadType))),
  29. Constants.GROUP_PARAMETER_NAME,
  30. HashMap.class);
  31. // 方法内容配置
  32. for (Map.Entry<String, String> entry : tempGroupMap.entrySet()) {
  33. // 类似String.format("hello %s net163 %d", "net", 163)通配符
  34. // groupMap.put("main", ARouter$$Path$$app.class);
  35. methodBuidler.addStatement("$N.put($S, $T.class)",
  36. Constants.GROUP_PARAMETER_NAME, // groupMap.put
  37. entry.getKey(),
  38. // 类文件在指定包名下
  39. ClassName.get(packageNameForAPT, entry.getValue()));
  40. }
  41. // 遍历之后:return groupMap;
  42. methodBuidler.addStatement("return $N", Constants.GROUP_PARAMETER_NAME);
  43. // 最终生成的类文件名
  44. String finalClassName = Constants.GROUP_FILE_NAME + moduleName;
  45. messager.printMessage(Diagnostic.Kind.NOTE, "APT生成路由组Group类文件:" +
  46. packageNameForAPT + "." + finalClassName);
  47. // 生成类文件:ARouter$$Group$$app
  48. JavaFile.builder(packageNameForAPT, // 包名
  49. TypeSpec.classBuilder(finalClassName) // 类名
  50. .addSuperinterface(ClassName.get(groupLoadType)) // 实现ARouterLoadGroup接口
  51. .addModifiers(Modifier.PUBLIC) // public修饰符
  52. .addMethod(methodBuidler.build()) // 方法的构建(方法参数 + 方法体)
  53. .build()) // 类构建完成
  54. .build() // JavaFile构建完成

我们为了能看出效果,在src下再新建两个Actvity

运行后

 Path路由:将扫描到的Activity都添加到了app下

  1. public class ARouter$$Path$$app implements ARouterLoadPath {
  2. @Override
  3. public Map<String, RouterBean> loadPath() {
  4. Map<String, RouterBean> pathMap = new HashMap<>();
  5. pathMap.put("/app/MainActivity", RouterBean.create(RouterBean.Type.ACTIVITY, MainActivity.class, "/app/MainActivity", "app"));
  6. pathMap.put("/app/MainActivity", RouterBean.create(RouterBean.Type.ACTIVITY, MainActivity2.class, "/app/MainActivity", "app"));
  7. pathMap.put("/app/MainActivity", RouterBean.create(RouterBean.Type.ACTIVITY, MainActivity3.class, "/app/MainActivity", "app"));
  8. return pathMap;
  9. }
  10. }
  1. public class ARouter$$Group$$app implements ARouterLoadGroup {
  2. @Override
  3. public Map<String, Class<? extends ARouterLoadPath>> loadGroup() {
  4. Map<String, Class<? extends ARouterLoadPath>> groupMap = new HashMap<>();
  5. groupMap.put("app", ARouter$$Path$$app.class);
  6. return groupMap;
  7. }
  8. }

我们再查看一下其他分组的显示:

生成的apt文件都在自己的目录下,这个是rebuild project的运行结果,但是我们可以观察到他们的包名与config.gradle中定义的是一样的

所以build apk分析一下生成的apk

 

 

 

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/240152
推荐阅读
相关标签
  

闽ICP备14008679号