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<String> getSupportedOptions() {
        return super.getSupportedOptions();
    }

    /**
     * 返回此注释 Processor 支持的最新的源版本
     * <p>
     * 该方法可以通过注解@SupportedSourceVersion指定
     *
     * @return
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

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


    /**
     * 注解处理器的核心方法,处理具体的注解
     *
     * @param set
     * @param roundEnvironment
     * @return
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        // 通过roundEnvironment扫描所有的类文件,获取所有存在指定注解的字段
        Map<TypeElement, List<FieldViewBinding>> targetMap = getTargetMap(roundEnvironment);


        createJavaFile(targetMap.entrySet());
        return false;
    }

    /**
     * 获取所有存在注解的类
     *
     * @param roundEnvironment
     * @return
     */
    private Map<TypeElement, List<FieldViewBinding>> getTargetMap(RoundEnvironment roundEnvironment) {
        /**
         * 键:TypeElement,指定Activity;
         * 值:List<FieldViewBinding>,activiyt中所有的注解修饰的字段
         */
        Map<TypeElement, List<FieldViewBinding>> targetMap = new HashMap<>();

        // 1、获取代码中所有使用@BindView注解修饰的字段
        Set<? extends Element> 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<FieldViewBinding> 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<Map.Entry<TypeElement, List<FieldViewBinding>>> entries) {
        for (Map.Entry<TypeElement, List<FieldViewBinding>> entry : entries) {
            TypeElement typeElement = entry.getKey();
            List<FieldViewBinding> 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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值