Android注解处理器(Android Annotation Processor)

APT工具
APT(Annotation Processing Tool)是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,根据注解自动生成代码,所以如果想要自定义的注解处理器能够正常运行,必要要通过APT工具来进行处理。 
也可以这样理解,只有通过声明APT工具后,程序在编译期间自定义注解解释器才能执行。

这里使用的APT工具是annotationProcessor,这个是Android Gradle插件2.2版本发布后,Android 官方提供了annotationProcessor来代替android-apt的APT工具,关于annotationProcessor和android-apt以及其他一些常见的
 

概述

现在Android开发中许多流行的第三方库都使用了注解生成代码的方式,例如 ButterKnifeDagger2Glide等等
Java 是从 Java 5 开始支持注解的,是其很重要的语言特性,然而其潜力的发掘也仅仅是近几年的事情。Java 5 真的是Java 一个里程碑式的版本,各种重要特性均源自此版本。

注解一般有两种方式处理方式:
第一:运行时获取并使用注解信息,此方式属于反射范畴,有性能损失,例如Retrofit2等。
第二:编译时获取注解信息,并据此产生java代码文件,无性能损失,例如ButterKnife, Dagger2等。
annotationprocessor编译器,在编译期间创建的需要用到

1  java注解的基本知识,

2  java代码生成库 javapoet

自定义ButterKnife

1:项目结构

整个项目主要有如下四个部分

  1. app : Android 项目主module,是一个 android application
  2. butterknife-annotations: 定义了所有的注解, 是一个 java library
  3. butterknife-compiler: 处理相关注解,关键的代码都在这个module里,是一个 java library。因为在使用自定义AbstractProcessor需要使用到javax包中的相关类和接口,这个在android库中并不存在,所以需要使用到Java库。
  4. butterknife: 处理生成代码的调用,是一个android library

因为自定义AbstractProcessor的作用是在编译时生成相关的java源文件的,在程序中最后需要的是他生成的Java文件,最后打包进apk也是他生成的文件,butterknife-compiler本身是不需要打包的。 
但是在butterknife-compiler和butterknife中需要对具体的注解类进行操作,需要引用butterknife-annotations库,所以将具体的注解放在一个库butterknife-annotations中便于引用
 

2:定义注解 butterknife-annotations

在工程中添加一个java library类型的module,取名butterknife-annotations

Android Studio -> file -> new module -> java library

@BindView 在这个库中主要用于声明具体的注解的,

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {

    int value();
}


 3:定义注解处理器  butterknife-compiler​​​​​​​

新建一个module,取名为butterknife-compiler,类型必须为java library,如下所示

Android Studio -> file -> new module -> java library

在butterknife-compiler库中,主要的功能就是自定义AbstractProcessor,在编译时生成具体的Java文件实现依赖注入功能。因为该注解处理器需要使用到指定注解,所以该库需要依赖butterknife-annotations。

关于AbstractProcessor类中个各个方法在上面已经提到过,这里主要说一下process方法的具体功能。

在process方法中我们主要有两个功能需要完成:

获取同一个类中的所有指定注解修饰的Element;
在process方法中,有一个参数RoundEnvironment,通过该参数可以通过遍历获取代码中所有通过指定注解(这里只@BindeView)修饰的Element对象。通过Element对象可以获取字段名称,字段类型以及注解元素的值。
创建Java文件;
将同一个类中通过指定注解修饰的所有Element在同一个Java文件中实现初始化,这样做的目的是让在最终依赖注入时便于操作。
 

Processor 一般会重写父类的4个方法:

init:
初始化工作,我们可以得到一些有用的工具,例如 Filer,我们需要它将生成的代码写入文件中
process:
最重要的方法,所有的注解处理都是在此完成
getSupportedAnnotationTypes:
返回我们所要处理的注解的一个集合
getSupportedSourceVersion:
要支持的java版本
下面是自定义AbstractProcessor中的具体逻辑:


@AutoService(Process.class)
public class ButterKnifeProcessor extends AbstractProcessor {

    private Messager messager;
    private Elements elementUtils;
    private Filer filer;
    private Types typeUtils;

    /**
     * `
     * 初始化操作
     *
     * @param processingEnvironment
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        typeUtils = processingEnvironment.getTypeUtils();
        elementUtils = processingEnvironment.getElementUtils();
        filer = processingEnvironment.getFiler();
        messager = processingEnvironment.getMessager();
    }

    /**
     * 返回此Porcessor可以处理的注解操作
     *
     * @return
     */
    @Override
    public Set getSupportedOptions() {
        return super.getSupportedOptions();
    }

    /**
     * 返回此注释 Processor 支持的最新的源版本
     * 

* 该方法可以通过注解@SupportedSourceVersion指定 * * @return */ @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } /** * 返回此 Processor 支持的注释类型的名称。结果元素可能是某一受支持注释类型的规范(完全限定)名称。它也可能是 " name.*" 形式的名称,表示所有以 " name." 开头的规范名称的注释类型集合。最后, "*" 自身表示所有注释类型的集合,包括空集。注意,Processor 不应声明 "*",除非它实际处理了所有文件;声明不必要的注释可能导致在某些环境中的性能下降。 *

* 该方法可以通过注解@SupportedSourceVersion指定 * * @return */ @Override public Set getSupportedAnnotationTypes() { Set set = new LinkedHashSet<>(); set.add(BindView.class.getCanonicalName()); return set; } /** * 注解处理器的核心方法,处理具体的注解 * * @param set * @param roundEnvironment * @return */ @Override public boolean process(Set set, RoundEnvironment roundEnvironment) { // 通过roundEnvironment扫描所有的类文件,获取所有存在指定注解的字段 Map> targetMap = getTargetMap(roundEnvironment); createJavaFile(targetMap.entrySet()); return false; } /** * 获取所有存在注解的类 * * @param roundEnvironment * @return */ private Map> getTargetMap(RoundEnvironment roundEnvironment) { /** * 键:TypeElement,指定Activity; * 值:List,activiyt中所有的注解修饰的字段 */ Map> targetMap = new HashMap<>(); // 1、获取代码中所有使用@BindView注解修饰的字段 Set annotatedElements = roundEnvironment.getElementsAnnotatedWith(BindView.class); for (Element element : annotatedElements) { // 获取字段名称 (textView) String fieldName = element.getSimpleName().toString(); // 获取字段类型 (android.widget.TextView) TypeMirror fieldType = element.asType(); // 获取注解元素的值 (R.id.textView) int viewId = element.getAnnotation(BindView.class).value(); // 获取声明element的全限定类名 (com.zhangke.simplifybutterknife.MainActivity) TypeElement typeElement = (TypeElement) element.getEnclosingElement(); List list = targetMap.get(typeElement); if (list == null) { list = new ArrayList<>(); targetMap.put(typeElement, list); } list.add(new FieldViewBinding(fieldName, fieldType, viewId)); } return targetMap; } /** * 创建Java文件 * @param entries */ private void createJavaFile(Set>> entries) { for (Map.Entry> entry : entries) { TypeElement typeElement = entry.getKey(); List list = entry.getValue(); if (list == null || list.size() == 0) { continue; } // 获取包名 String packageName = elementUtils.getPackageOf(typeElement).getQualifiedName().toString(); // 根据旧Java类名创建新的Java文件 String className = typeElement.getQualifiedName().toString().substring(packageName.length() + 1); String newClassName = className + "_ViewBinding"; MethodSpec.Builder methodBuilder = MethodSpec.constructorBuilder() .addModifiers(Modifier.PUBLIC) .addParameter(ClassName.bestGuess(className), "target"); for (FieldViewBinding fieldViewBinding : list) { String packageNameString = fieldViewBinding.getFieldType().toString(); ClassName viewClass = ClassName.bestGuess(packageNameString); methodBuilder.addStatement ("target.$L=($T)target.findViewById($L)", fieldViewBinding.getFieldName() , viewClass, fieldViewBinding.getViewId()); } TypeSpec typeBuilder = TypeSpec.classBuilder(newClassName) .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addMethod(methodBuilder.build()) .build(); JavaFile javaFile = JavaFile.builder(packageName, typeBuilder) .addFileComment("Generated code from Butter Knife. Do not modify!") .build(); try { javaFile.writeTo(filer); } catch (IOException e) { e.printStackTrace(); } } } }

build.gradle配置:

apply plugin: 'java-library'

dependencies {
//    引入注解
    compile project(':butterknife-annotations')
    //google提供的auto-service开源库
    compile 'com.google.auto.service:auto-service:1.0-rc3'
  //  compile 'com.google.auto:auto-common:0.8'
   //java 代码生成
 compile 'com.squareup:javapoet:1.8.0'
    

}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"

注解处理器的配置
在上面基本就实现了一个自定义注解处理器,但是如果想要注解处理器能够在编译器生成代码,这里还需要做一个配置说明,这里有两种配置方法:

第一种方法: 
在butterknife-compiler的main目录下创建目录resources/META-INF/services,并在该目录下创建文件javax.annotation.processing.Processor,在该文件中写入注解处理器的全限定类名(这里是com.zhangke.compiler.ButterKnifeProcessor)

第二种方法: 
如何感觉通过上面的方法配置特别麻烦,这里也可以通过google提供的auto-service开源库。通过该方式我们只需要在注解处理器上添加注解@AutoService(Processor.class)即可。

4:定义初始化模块 butterknife

新建一个module,取名为butterknife,类型必须为android library,如下所示

Android Studio -> file -> new module -> android library

butterknife是一个android库工程,该库的作用主要就是完成依赖注入。在该库中会依赖butterknife-annotations

butterknife中定义的用于实现依赖注入的工具类

public class ButterKnife {

    public static void bind(Activity activity) {
        //1、获取全限定类名
        String name = activity.getClass().getName();
        try {
            //2、 根据全限定类名获取通过注解解释器生成的Java类
            Class clazz = Class.forName(name + "_ViewBinding");
            //3、 通过反射获取构造方法并创建实例完成依赖注入
            clazz.getConstructor(activity.getClass()).newInstance(activity);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

 

5:使用

需要在项目中也就是在app/build.gradle文件中添加库依赖并且声明APT工具,如下:

dependencies {
    // 省略

    // 添加butterknife依赖 grade3.0以上才能用
    implementation project(':butterknife')

    annotationProcessor project(':butterknife-compiler')
}

6:测试


在项目主工程中定义了MainActivity,在这Activity中使用注解@BindView,如下:
 

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.button)
    Button button;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 通过工具类实现依赖注入
        ButterKnife.bind(this);
        textView.setText("hahhah");
    }

}

通过Android Studio的Rebuild Project,或者直接运行,就可以看到在主app工程build/generated/source/apt/debug目录下就已经生成了新的Java文件MainActiivty_ViewBinding

这里可以看看MainActivity_ViewBinding类中的具体内容,发现在该文件中为MainActivity_ViewBinding创建了一个构造方法,该构造方法中需要传入指定的activity,然后在该构造方法中就可以完成button和textView的初始化即findViewByid的过程。
 

参考:https://blog.csdn.net/kaifa1321/article/details/79683246

     https://blog.csdn.net/ShuSheng0007/article/details/90734159

 

你可能感兴趣的:(android-第三方框架)