前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android APT(编译时代码生成)最佳实践

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

作者头像
用户1269200
发布2018-07-30 10:37:04
1.2K0
发布2018-07-30 10:37:04
举报
文章被收录于专栏:刘望舒刘望舒

作者:悦跑圈技术团队 https://joyrun.github.io/2016/07/19/AptHelloWorld/

越来越多第三方库使用apt技术,如DBflow、Dagger2、ButterKnife、ActivityRouter、AptPreferences。在编译时根据Annotation生成了相关的代码,非常高大上但是也非常简单的技术,可以给开发带来了很大的便利。

如果想学习APT,那么就必须先了解Annotation的基础。可以查看这篇文章:https://blog.csdn.net/javazejian/article/details/71860633

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

创建Annotation Module

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

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

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

apply plugin: 'java'
sourceCompatibility = 1.7 
targetCompatibility = 1.7 
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
}

创建Compiler Module

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

apply plugin: 'java'
sourceCompatibility = 1.7 
targetCompatibility = 1.7 
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.google.auto.service:auto-service:1.0-rc2'
    compile 'com.squareup:javapoet:1.7.0'
    compile project(':annotation')
}
  1. 定义编译的jdk版本为1.7,这个很重要,不写会报错。
  2. AutoService 主要的作用是注解 processor 类,并对其生成 META-INF 的配置信息。
  3. JavaPoet 这个库的主要作用就是帮助我们通过类调用的形式来生成代码。
  4. 依赖上面创建的annotation Module。
定义Processor类

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

@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的代码:

package com.example.helloworld;

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

修改上述TestProcessor的process方法

@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中使用

配置项目根目录的build.gradle

dependencies {
    classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}

配置app的build.gradle

apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
//...
dependencies {
    //..
    compile project(':annotation')
    apt project(':compiler')
}

编译使用 在随意一个类添加@Test注解

@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/source/apt目录下,即可看到生成的代码。

基于注解的View注入:DIActivity

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

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface DIActivity {

}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DIView {
    int value() default 0;
}

创建DIProcessor方法

@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_7;
    }
}

使用DIActivity

@DIActivity
public class MainActivity extends Activity {
    @DIView(R.id.text)
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DIMainActivity.bindView(this);
        textView.setText("Hello World!");
    }
}

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

public final class DIMainActivity extends MainActivity {
 public static void bindView(MainActivity activity) {
   activity.textView = (android.widget.TextView) activity.findViewById(R.id.text);
 }
}

常用方法

常用Element子类

  1. TypeElement:类
  2. ExecutableElement:成员方法
  3. VariableElement:成员变量

通过包名和类名获取TypeName

TypeName targetClassName = ClassName.get("PackageName", "ClassName");

通过Element获取TypeName

TypeName type = TypeName.get(element.asType());

获取TypeElement的包名

String packageName = processingEnv.getElementUtils().getPackageOf(type).getQualifiedName().toString();

获取TypeElement的所有成员变量和成员方法

List members = processingEnv.getElementUtils().getAllMembers(typeElement);

总结

推荐阅读dagger2、dbflow、ButterKnife等基于apt的开源项目代码。JavaPoet 也有很多例子可以学习。

Example代码 https://github.com/taoweiji/DemoAPT

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-07-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 刘望舒 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 创建Annotation Module
  • 创建Compiler Module
    • 定义Processor类
      • 生成第一个类
  • 在app中使用
  • 基于注解的View注入:DIActivity
  • 常用方法
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档