这是我参与更文挑战的第8天,活动详情查看: 更文挑战
前情提要
上一篇文章我们学习了通过 gradle 脚本控制 app 的打包生成逻辑,算是可以完成了组件化的第一步。在独立打包过程中,这样就够了,但是如果在集成打包的时候我们需要从app 工程跳转到 userinfo 工程,需要怎么处理呢?
如何跳转到子模块
当然这时候我们可以直接通过 intent 的方式来实现,但是如果这样直接写上的话。在独立打包的时候,这些类是找不到的。这时候就需要我们来处理了。
// 直接跳转的方式,在独立打包的时候 UserInfoActivity.class 是找不到的
Intent intent = new Intent(MainActivity.this, UserInfoActivity.class);
startActivity(intent);
复制代码
我们可以想到的有 eventbus、广播、类加载等方式,我们来写一个反射的示例代码。
private void goUserInfo() {
// 使用类加载器的方式
try {
Class<?> clazz = Class.forName("xyz.xyz0z0.userinfo.UserInfoActivity");
Intent intent = new Intent(MainActivity.this, clazz);
startActivity(intent);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
复制代码
因为这里 intent 的第二个参数接收的是一个 class 对象,所以我们通过 Class.forName 构造出我们需要跳转的类就可以这样使用了。我们运行尝试一下,是可以正常跳转的。
这里我们更进一步,把这些需要跳转的类路径全部通过一个 map 保存起来,在需要跳转的时候直接获取就可以了,这种思想也就是 ARouter 的思想。但是如果一个个的手动添加不仅繁琐而且容易出错,自然是需要采用程序来自动添加,而且是需要在打包之前就需要添加上的,那么我们能够采用什么技术呢?就是 APT ,编译时注解技术,在编译时替我们自动生成代码,像 eventbus、butterknife 等框架都采用了 APT 相关技术。
APT 技术实战
那么应该如何实现呢,传统方式是在编译过程中通过我们的注解获取到相关类信息,然后手动拼接代码并生成成文件。当然这也很繁琐,自然可以通过框架来解决,需要的框架就是 JakeWharton 大声开发的 javapoet (github.com/square/java…
首先我们模仿其他开源项目新建 annotation 和 compiler 两个 java 项目。(我这里为了后续的仿写 ARouter 框架,就将这两个子项目分别命名为 arouter-annotations 和 arouter-compiler)。在 annotation 项目中新建上我们需要的注解类,在 compiler 中新建我们需要的注解处理类 ARouterProcessor.java 。
// google 的 auto-service 提供的,让我们的注解处理类可以成为一个服务,在编译过程中进行一些处理
@AutoService(Processor.class)
// 这个注解表示我们需要关注哪一个注解
@SupportedAnnotationTypes({"xyz.xyz0z0.arouter_annotations.ARouter"})
// 支持的版本号
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class ARouterProcessor extends AbstractProcessor {
// 日志相关
private Messager messager;
// 生成文件相关操作
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
messager = processingEnv.getMessager();
filer = processingEnv.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
messager.printMessage(Diagnostic.Kind.NOTE, ">>>>> start");
if (annotations.isEmpty()) {
return false;
}
// 这里可以获取处理过程中拿到的注解详细
for (TypeElement annotation : annotations) {
messager.printMessage(Diagnostic.Kind.NOTE, "annotation " + annotation.toString());
}
messager.printMessage(Diagnostic.Kind.NOTE, ">>>>> end");
return false;
}
}
复制代码
接着我们的 app 项目分别依赖 annotations 和 compile 项目,因为我们的 ARouterProcessor 设置了关注 “xyz.xyz0z0.arouter_annotations.ARouter” 这个注解,所以我们需要在 app 项目中使用一下这个注解。接着编译我们的项目,就可以在 build 面板中看到我们的注解处理器输出的信息了。具体代码可以到 github 上面查看。
JavaPoet 上手
到这里,我们已经了解了 APT 技术和流程了,那么我们来试试生成代码吧,这里就需要用到 JavaPoet 了。JavaPoet 采用了面向对象的方法,我们通过框架为我们提供的方法可以分别构造出方法,类,包,最后生成我们的需要的文件,就是这么简单。下面我们试试输出一个简单的 HelloWorld 文件吧。
直接上代码。
private void generateHelloWorld() {
// 方法
MethodSpec mainMethod = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello world.")
.build();
// 类
TypeSpec typeSpec = TypeSpec.classBuilder("MainTest")
.addMethod(mainMethod)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.build();
// 包
JavaFile javaFile = JavaFile.builder("com.hello.test", typeSpec)
.build();
// 生成文件
try {
javaFile.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
messager.printMessage(Diagnostic.Kind.NOTE, "生成文件失败,异常:" + e.getMessage());
}
}
复制代码
再次编译,查看一下 app 项目里面的 build/generated/ap_generated_sources 目录,就可以找到我们生成的 MainTest 文件了。这里需要解释一下,因为我们的注解是在 app 项目中使用的,所以生成的代码也是在该项目下面。
这样我们就初步完成了 JavaPoet 的使用了,也就能理解 butterknife 这一类采用了编译时注解技术的框架的实现原理。看来我们和 JakeWharton 大神的距离又缩短了一步了。这篇分享就到这里,下一期让我们自定义实现我们的路由跳转,我是不要注水,我们下期见。
本文是个人的学习记录,不免对技术点有错误或不够深入的理解,还望大神小白批评指教。
对应的源码地址 github.com/xyz0z0/Comp… 可以参考对应的 commit 记录查看