Android常用注解和APT的使用

695 阅读3分钟

一、编译时注解处理:

编译时注解的核心依赖APT(Annotation Processing Tools)实现,原理是添加完注解后,在编译时编译器会检查AbstractProcessor的子类,并且调用该类型的process函数,可以在该方法进行相应的处理。

1、定义自定义注解。新建一个Java library,名为 Annolib,定义一个自定义注解

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
    int id();
}

2、定义注解处理器。再次新建一个Java library,用来定义一个注解处理器,名为Processor:

@AutoService(javax.annotation.processing.Processor.class)
@SupportedAnnotationTypes({"example.mydemo.com.annolib.Person"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class Processor extends AbstractProcessor{

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
		  .....
    }
}
  • @AutoService(Processor.class),用于自动为 JAVA Processor 生成 META-INF 信息。方便注册注解处理器
  • @SupportedAnnotationTypes({}),声明 Processor 处理的注解,注意这是一个数组,表示可以处理多个注解;也可以通过getSupportedAnnotationTypes 方法来处理
  • @SupportedSourceVersion(SourceVersion.RELEASE_7),声明支持的源码版本。也可以通过getSupportedSourceVersion方法来处理。
  • process() 方法的实现,分为两个步骤: (1)收集 Class 内的所有被自定义注解的类;
    (2)生成 .java 源文件。可以使用JavaPoet开源库进行编写,提升效率。

Element的子类有:

  • ExecutableElement: 表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注释类型元素。 对应@Target(ElementType.METHOD) @Target(ElementType.CONSTRUCTOR)

  • PackageElement; 表示一个包程序元素。提供对有关包极其成员的信息访问。 对应@Target(ElementType.PACKAGE)

  • TypeElement; 表示一个类或接口程序元素。提供对有关类型极其成员的信息访问。 对应@Target(ElementType.TYPE) 注意:枚举类型是一种类,而注解类型是一种接口。

  • TypeParameterElement; 表示一般类、接口、方法或构造方法元素的类型参数。 对应@Target(ElementType.PARAMETER)

  • VariableElement; 表示一个字段、enum常量、方法或构造方法参数、局部变量或异常参数。 对应@Target(ElementType.LOCAL_VARIABLE)

Processor完整代码:

@AutoService(javax.annotation.processing.Processor.class)
@SupportedAnnotationTypes({"example.mydemo.com.annolib.BindView"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class Processor extends AbstractProcessor{

    /**
     * 指定支持的Java版本,
     * @return
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    /**
     * 指定处理器需要处理哪些注解
     * @return 存储注解的set
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> type = new LinkedHashSet<>();
        type.add(BindView.class.getCanonicalName());
        return type;
    }

    /**
     * 注解器核心代码,return false是不做任何事情
     * @param set
     * @param roundEnvironment
     * @return
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        Messager messager = processingEnv.getMessager();
        //通过自定义的注解拿到element的集合
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
        //遍历所有被注解的程序元素
        for (Element element : elements) {
            ElementKind kind = element.getKind();
            //判断该元素是否为字段
            if (kind == ElementKind.FIELD) {
                //通过message输出
                messager.printMessage(Diagnostic.Kind.NOTE,"field:" + element.toString());
            }
        }
        return true;
    }
}

build.gradle:

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    //依赖自定义注解
    implementation project(':Annolib')
    //用于生成 .java 源文件
    implementation 'com.squareup:javapoet:1.11.1'
    //用于自动为 JAVA Processor 生成 META-INF 信息。
    compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"
  • https://github.com/square/javapoet 方便生成.java文件

3、app module中使用注解: (1)build.gradle:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.0'
    //自定义注解库的依赖
    implementation project(':Annolib')
    //自定义注解处理器的依赖
    annotationProcessor project(':processor')
}

annotationProcessor 仅仅在编译期去依赖注解处理器所在库进行工作,不会打包到APK 中。

(2)注解使用:

 @BindView(id = R.id.tv_title)
 TextView mTextView;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.activity_main);

 }

通过命令 ./gradlew build去构建工程,输出了注解View的Id:

二、Android中常用注解

依赖: 在build.gradle中增加:

implementation 'com.android.support:support-annotations:27.1.1'   

官方文档:
https://developer.android.com/reference/android/support/annotation/package-summary
1、@NonNull 声明方法的参数不能为空

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    String name = null;
    initView(name);
}
private void initView(@NonNull String s) {
   
}

2、@Nullable注解表示一个方法的参数或者返回值可以是Null

 private void initView(@Nullable String s) {

    }

3、@StringRes注解表示需要一个String类型的资源id

 private void initView(@StringRes int strId) {

    }

4、@IdRes 表示需要一个资源id

private void initView(@IdRes int id) {

    }

5、@IntDef 用来替代枚举:

public static final int ONE = 1;
public static final int TWO = 2;
public static final int THREE = 3;

@IntDef({ONE, TWO, THREE})
public @interface Common{

}

public void setValue(@Common int value) {

}


private void initView() {
    setValue(ONE);
}