编辑
2021-03-24
客户端技术
00
请注意,本文编写于 922 天前,最后修改于 113 天前,其中某些信息可能已经过时。

目录

APT
APT HelloWorld
创建Annotation Module
创建Compiler Module
定义Processor类
在app中使用
基于注解的View注入:DIActivity
参考资料

在之前的《纯手写路由框架实现Android组件化》中讲到了Android APT技术,并且在讲解视频中使用APT了技术,加以JavaPoet辅助代码生成实现了一个最简单的基于注解的View注入(其实就是省略了大量的findViewById方法)。如果只是单纯的想体验APT技术带来的便捷性,那么这篇文章非常适合,APT从讲是一个编译期的注解处理工具(Annotation Processing Tool)。一些主流的三方库,如ButterKnife、EventBus等都用到了这个技术来生成代码。

下面的内容转载自《Android APT(编译时代码生成)最佳实践》,其中进行了部分删改

APT

APT(Annotation Processing Tool)是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,使用Annotation进行额外的处理。 Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定),APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件。

APT HelloWorld

创建Annotation Module

首先,我们需要新建一个名称为annotation的Java Library,主要放置一些项目中需要使用到的Annotation和关联代码。这里简单自定义了一个注解:

java
@Target(ElementType.TYPE) @Retention(RetentionPolicy.CLASS) public @interface Test { }

配置build.gradle,主要是规定JDK版本

groovy
plugins { id 'java-library' } // 控制台中文设置UTF-8 tasks.withType(JavaCompile){ options.encoding = "UTF-8" } java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 }

创建Compiler Module

创建一个名为compiler的Java Library,这个类将会写代码生成的相关代码。核心就是在这里,配置build.gradle:

groovy
plugins { id 'java-library' } dependencies { implementation fileTree(dir: 'libs', includes: ['*.jar']) // 编译时期进行注解处理 annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4' compileOnly 'com.google.auto.service:auto-service:1.0-rc4' // 帮助我们通过类调用的方式来生成Java代码[JavaPoet] implementation 'com.squareup:javapoet:1.10.0' // 依赖于注解 implementation project(':annotation') } // 控制台中文设置UTF-8 tasks.withType(JavaCompile){ options.encoding = "UTF-8" } java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 }

1、定义编译的jdk版本为1.8,这个很重要,不写会报错。 2、AutoService 主要的作用是注解 processor 类,并对其生成 META-INF 的配置信息。 3、JavaPoet 这个库的主要作用就是帮助我们通过类调用的形式来生成代码。 4、依赖上面创建的annotation Module。

定义Processor类

生成代码相关的逻辑就放在这里。

java
@AutoService(Processor.class) public class TestProcessor extends AbstractProcessor { @Override public Set<String> getSupportedAnnotationTypes() { return Collections.singleton(Test.class.getCanonicalName()); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { return false; } }

生成第一个类,我们接下来要生成下面这个HelloWorld的代码:

java
package com.example.helloworld; public final class HelloWorld { public static void main(String[] args) { System.out.println("Hello, JavaPoet!"); } }

修改上述TestProcessor的process方法

java
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { MethodSpec main = MethodSpec.methodBuilder("main") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(void.class) .addParameter(String[].class, "args") .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!") .build(); TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addMethod(main) .build(); JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld) .build(); try { javaFile.writeTo(processingEnv.getFiler()); } catch (IOException e) { e.printStackTrace(); } return false; }

在app中使用

配置app模块的build.gradle

groovy
dependencies { ...... implementation project(':annotation') annotationProcessor project(':annotation-processor') }

在随意一个类添加@Test注解,比如在MainActivity中:

java
@Test public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }

点击Android Studio的ReBuild Project,可以在在app的build/generated/ap_generated_sources/debug/out目录下,即可看到生成的代码。

基于注解的View注入:DIActivity

到目前我们还没有使用注解,上面的@Test也没有实际用上,下面我们做一些更加实际的代码生成。实现基于注解的View,代替项目中的findByView。这里仅仅是学习怎么用APT,如果真的想用DI框架,推荐使用ButterKnife,功能全面。

第一步,在annotation module创建@DIActivity、@DIView注解。

java
package cn.tim.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.TYPE) @Retention(RetentionPolicy.CLASS) public @interface DIActivity { }
java
package cn.tim.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface DIView { int value() default 0; }

创建DIProcessor方法

java
package cn.tim.annotation_processor; import com.google.auto.service.AutoService; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; import cn.tim.annotation.DIActivity; import cn.tim.annotation.DIView; @AutoService(Processor.class) public class DIProcessor extends AbstractProcessor { private Elements elementUtils; @Override public Set<String> getSupportedAnnotationTypes() { // 规定需要处理的注解 return Collections.singleton(DIActivity.class.getCanonicalName()); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { System.out.println("DIProcessor"); Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(DIActivity.class); for (Element element : elements) { // 判断是否Class TypeElement typeElement = (TypeElement) element; List<? extends Element> members = elementUtils.getAllMembers(typeElement); MethodSpec.Builder bindViewMethodSpecBuilder = MethodSpec.methodBuilder("bindView") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(TypeName.VOID) .addParameter(ClassName.get(typeElement.asType()), "activity"); for (Element item : members) { DIView diView = item.getAnnotation(DIView.class); if (diView == null){ continue; } bindViewMethodSpecBuilder.addStatement(String.format("activity.%s = (%s) activity.findViewById(%s)",item.getSimpleName(),ClassName.get(item.asType()).toString(),diView.value())); } TypeSpec typeSpec = TypeSpec.classBuilder("DI" + element.getSimpleName()) .superclass(TypeName.get(typeElement.asType())) .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addMethod(bindViewMethodSpecBuilder.build()) .build(); JavaFile javaFile = JavaFile.builder(getPackageName(typeElement), typeSpec).build(); try { javaFile.writeTo(processingEnv.getFiler()); } catch (IOException e) { e.printStackTrace(); } } return true; } private String getPackageName(TypeElement type) { return elementUtils.getPackageOf(type).getQualifiedName().toString(); } @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); elementUtils = processingEnv.getElementUtils(); } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.RELEASE_8; } }

使用DIActivity

java
package cn.tim.apt_demo; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView; import cn.tim.annotation.DIActivity; import cn.tim.annotation.DIView; @DIActivity public class MainActivity extends AppCompatActivity { @DIView(value = R.id.text) TextView textView; @DIView(value = R.id.text1) TextView textView1; @DIView(value = R.id.text2) TextView textView2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DIMainActivity.bindView(this); textView.setText("Hello, JavaPoet!"); textView2.setText("Tim"); } }

实际上就是通过apt生成了以下代码:

java
public final class DIMainActivity extends MainActivity { public static void bindView(MainActivity activity) { activity.textView = (android.widget.TextView) activity.findViewById(2131231086); activity.textView1 = (android.widget.TextView) activity.findViewById(2131231087); activity.textView2 = (android.widget.TextView) activity.findViewById(2131231088); } }

示例代码:Github -> aptdemo

参考资料

《Android APT(编译时代码生成)最佳实践》

本文作者:Tim

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!