当前位置:   article > 正文

组件化二、APT+JavaPoet配合搭建组件化架构

apt+javapoet

1、APT(注解处理器)介绍

APT(Annotation Processing Tool)是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,根据注解主动生成代码,如果想要自定义注解处理器能够正常运行,必须要通过APT工具来进行处理。
也可以这样理解,只有通过申明APT工具后,程序在编译期间自定义注解处理器才能执行
通过来讲:根据规则,帮我们自动生成代码,生成类文件
Google提供了APT的库(Java库),后面会介绍如何导入使用

2、Element程序元素

  • PackageElement,表示一个包程序元素。提供对有关包及其成员的信息的访问
  • ExecutableElement,表示某个类或接口的方法、构造方法或初始化程序(静态或实例)
  • TypeElement,表示一个类或接口程序元素。提供对有关类型及其成员的信息的访问
  • VariableElement,表示一个字段、enum常量、方法或构造方法参数、局部变量或异常参数

3、常用API

属性名说明
getEnclosedElements()返回该元素直接包含的子元素
getEnclosingElement()返回包含该element的父element,与上一个方法相反
getKind()返回element的类型,判断是哪种element
getModifiers()获取修饰关键字,如public static final等关键字
getSimpleName()获取名字,不带包名
getQualifiedName()获取全名,如果是类的话,包含完整的包名路径
getParameters()获取方法的参数元素,每个元素是一个VariableElement
getReturnType()获取方法的返回值
getConstantValue()如果属性变量被final修饰,则可以使用该方法获取它的值

4、APT的开发环境

  • Android Studio 3.3.2 + Gradle4.10.1(临界版本)
// 注册注解,并对其生成META-INF的配置信息,rc2在gradle5.0后有坑
// As-3.2.1 + gradle4.10.1-all + auto-service:1.0-rc2
implementation 'com.google.auto.service:auto-service:1.0-rc2'
  • 1
  • 2
  • 3
  • Android Studio 3.4.1 + Gradle 5.1.1(向下兼容)
// As-3.4.1 + gradle5.1.1-all + auto-service:1.0-rc4
compileOnly'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'
  • 1
  • 2
  • 3
  • 我如今使用的版本,Android Studio 4.0.1 + Gradle 6.1.1
// 最新的Android Studio + 最新的auto-service
// As-4.0.1 + gradle-6.1.1-all + auto-service:1.0-rc7
implementation 'com.google.auto.service:auto-service:1.0-rc7'
annotationProcessor'com.google.auto.service:auto-service:1.0-rc7'
  • 1
  • 2
  • 3
  • 4

5、JavaPoet
square公司的产品,OKhttp也是他们的产品,(#.#),square/javapoet
JavaPoet的使用,可以查看github上的demo

  • 什么是JavaPoet(JavaPoet + APT超级好用)

JavaPoet是square推出的开源的Java源代码生成框架,提供Java Api生成.java的源文件,使用方法也是符合我们日常书写习惯的Java面向对象OOP语法,可以很方便的时候它根据注解生成对应的代码,通过这种自动化生成代码的方式,可以让我们用更加简洁优雅的方式替代繁琐冗杂的重复工作

  • 依赖库
// 通过类调用的形式来生成Java代码(用来生成指定JAVA代码)
implementation 'com.squareup:javapoet:1.13.0'
  • 1
  • 2
  • JavaPoet的8个常用类
类对象说明
MethodSpec代表一个构造函数或者方法声明
TypeSpec代表一个类、接口、或者枚举类型
FieldSpec代表一个成员变量,一个字段声明
JavaFile包含一个顶级类的Java文件
ParameterSpec用来创建参数
AnnotationSpec用来创建注解
ClassName用来包装一个类
TypeName类型,如在添加返回值类型是使用TypeName.VOID
  • JavaPoet字符串格式化规则
$L:字面量,如:"int value = $L", 1
$S:字符串,如:"$S""hello"
$T:类、接口,如:"$T",MainActivity
$N:变量,如:"user.$N", user.name
  • 1
  • 2
  • 3
  • 4
接下来开始代码的书写,结构的搭建接上一篇

组件化一、组件化结构搭建

组件的目的就是解耦,各个Module之间没有相互依赖,但是组件之间还是会存在相互跳转

1、各个Module中的Activity互相跳转,可以使用全类名的方式使用startActivity()进行Activity之间的跳转
2、上一节讲到,将每个Module中的Activity封装到各个组代表的RouterBean的List集合中,然后根据 组名 将List集合封装到Map集合中

这一节我们将使用 注解 + APT(注解处理器) + JavaPoet将第二种方式进行的重复工作(重复代码)使用APT工具来帮我们自动生成,只需要在需要跳转的Activity上加上规定的注解,然后APT工具就能帮助我们自动完成封装代码的书写

一、新建两个Java Library,因为我们需要使用javax包中的API,新建一个Android Library的库annotation_api

1、annotation库,存储所有的注解和RouterBean
2、annotation_processor,用来处理注解
3、annotation_api,书写公共的接口,路由管理类等

接下来将上述的库做一个引入

  • 需要在annotation_api引入annotation,因为我们需要使用RouterBean对象
// 引入注解中RouterBean对象(java项目才有javax.lang包)
implementation project(':annotation')
  • 1
  • 2
  • 因为每个Module都需要使用annotation_api中的接口和RouterManger工具类,因此需要将annotation_api导入common_library,因为所有的module都导入了common_library,以此来避免需要在每个Module中都去导入annotation_api
// 每个功能子模块既然都要生成APT源文件,而且又是所有模块依赖的公共库
// 那么公共基础库依赖路由annotation_api就能向每个子模块提供开放api了
// 路由对外开放api模块
api project(':annotation_api')
  • 1
  • 2
  • 3
  • 4
  • 在所有的Module中引入common_library,annotation,annotation_processor
// 导入公共库
implementation project(':common_library')
// 注解
implementation project(':annotation')
// 注解处理器
annotationProcessor project(':annotation_processor')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 最后我们需要在annotation_processor中引入APT+JavaPoet开发环境
// APT
// As-4.0.1 + gradle6.1.1-all + auto-service:1.0-rc7
implementation 'com.google.auto.service:auto-service:1.0-rc7'
annotationProcessor'com.google.auto.service:auto-service:1.0-rc7'

// 通过类调用的形式来生成Java代码(用来生成指定JAVA代码)
implementation 'com.squareup:javapoet:1.13.0'

// 引入annotation,让注解处理器-处理注解
implementation project(':annotation')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

最后为了防止build控制台中文乱码

// java控制台输出中文乱码
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}
  • 1
  • 2
  • 3
  • 4
二、代码书写

如果操作的代码就懒得写在这里了,可以去github上把源代码下载下来看,┭┮﹏┭┮
组件化结构搭建代码
组件化结构搭建代码
组件化结构搭建代码

我们把注解文件贴一下吧,我们需要使用的注解是用在类之上的,并且是为了生成处理逻辑的源码,需要被打包在apk中

/**
 * <strong>Activity使用的布局文件注解</strong>
 * <ul>
 * <li>@Target(ElementType.TYPE)   // 接口、类、枚举、注解</li>
 * <li>@Target(ElementType.FIELD) // 属性、枚举的常量</li>
 * <li>@Target(ElementType.METHOD) // 方法</li>
 * <li>@Target(ElementType.PARAMETER) // 方法参数</li>
 * <li>@Target(ElementType.CONSTRUCTOR)  // 构造函数</li>
 * <li>@Target(ElementType.LOCAL_VARIABLE)// 局部变量</li>
 * <li>@Target(ElementType.ANNOTATION_TYPE)// 该注解使用在另一个注解上</li>
 * <li>@Target(ElementType.PACKAGE) // 包</li>
 * <li>@Retention(RetentionPolicy.RUNTIME) <br>注解会在class字节码文件中存在,jvm加载时可以通过反射获取到该注解的内容</li>
 * </ul>
 *
 * 生命周期:SOURCE < CLASS < RUNTIME
 * 1、一般如果需要在运行时去动态获取注解信息,用RUNTIME注解
 * 2、要在编译时进行一些预处理操作,如ButterKnife,用CLASS注解。注解会在class文件中存在,但是在运行时会被丢弃
 * 3、做一些检查性的操作,如@Override,用SOURCE源码注解。注解仅存在源码级别,在编译的时候丢弃该注解
 */
@Target(ElementType.TYPE) // 该注解作用在类之上
@Retention(RetentionPolicy.CLASS) // 要在编译时进行一些预处理操作,注解会在class文件中存在
public @interface ARouter {

    // 详细路由路径(必填),如:"/app/MainActivity"
    String path();

    // 路由组名(选填,如果开发者不填写,可以从path中截取出来)
    String group() default "";
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

简单的看下如何使用吧
1、在Activity上加上注解

@ARouter(path = "/app/MainActivity")
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
  • 1
  • 2

2、调用,如果实在app Module中,因为正式打包的时候,会将其他Module作为Library引入到app Module中,所以app中可以直接使用生成的类

private void jumpOrder() {
        // 最终集成化模式,所有子模块app/order/personal通过APT生成的类文件都会打包到apk里面,
        // 不用担心找不到
        ARouter$$Group$$order group$$order = new ARouter$$Group$$order();
        Map<String, Class<? extends ARouterLoadPath>> groupMap = group$$order.loadGroup();

        // 通过order组名获取对应路由路径对象
        Class<? extends ARouterLoadPath> orderClazz = groupMap.get("order");

        if (orderClazz == null) return;
        // 类加载动态加载路由路径对象
        try {
            ARouter$$Path$$order path$$order = (ARouter$$Path$$order) orderClazz.newInstance();
            Map<String, RouterBean> pathMap = path$$order.loadPath();
            RouterBean bean = pathMap.get("/order/OrderMainActivity");
            if (bean == null) return;
            Class<?> targetClass = bean.getClazz();
            jumpActivity(targetClass);
        } catch (IllegalAccessException | InstantiationException e) {
            e.printStackTrace();
        }
    }

private void jumpActivity(Class<?> clazz) {
        Intent intent = new Intent(this, clazz);
        intent.putExtra("name", "张三");
        intent.putExtra("age", 100);
        startActivity(intent);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

上述代码就是我们正常使用APT帮我们生成的类去处理跳转逻辑,但是这种方式只适用于App Module跳转到其他Module,其他Module之间因为是完全解耦的,所以没法使用上述代码来进行跳转。

因此我们需要将上述的逻辑抽取出来到RouterManager中(RouterManger是放在annotation_api中,所有Module都能方法),使用路由管理器来处理上述的逻辑实现跳转,路由管理器的实现请到github下载源码查看。

路由管理器的使用,只需要将 上下文对象和 注册的 Path路径(/app/MainActivity) 传递给路由管理器,就帮我们处理了上述复杂的跳转逻辑

RouterManager.getInstance()
                    .build(RouterClassConstants.ORDER_OrderMainActivity)
                    .navigation(this);
  • 1
  • 2
  • 3

组件化结构搭建源码
组件化结构搭建源码
组件化结构搭建源码

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

闽ICP备14008679号