美文网首页
Android APT 注解处理器

Android APT 注解处理器

作者: 挂云帆love | 来源:发表于2022-01-14 20:27 被阅读0次

    1、概述

    APT (Annotation Processing Tool)注解处理工具。在代码编译时,即gradle要执行javac时,

     :app:compileDebugJavaWithJavac
    

    会先执行注解处理器,扫描和处理注解,生成一些.java文件,之后才真正执行javac,编译代码生成.class文件

    使用APT知名的第三方库,如:butterknife、dagger2、hilt、databinding等。

    关于注解的相关知识可以看: Java注解

    2、实现一个简单的注解处理器

    2.1 创建注解module

    创建Java library Module,命名为 lib-annotation

    1.png

    创建注解 BindView

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

    APT起作用在真正执行javac之前,所以注解的作用域可以:RetentionPolicy.SOURCE

    2.2 创建注解处理器module

    创建Java library Module,命名为 lib-processor

    2.png

    创建注解处理器类,BindViewProcessor,继承AbstractProcessor

    public class BindViewProcessor extends AbstractProcessor {
    
        /**
         * 生成文件的工具类
         */
        private Filer filer;
    
        /**
         * 打印类
         */
        private Messager messager;
    
        //元素相关工具类
        private Elements elementUtils;
        private Types typeUtils;
    
        /**
         * 初始化
         */
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
            super.init(processingEnv);
            filer = processingEnv.getFiler();
            messager = processingEnv.getMessager();
            elementUtils = processingEnv.getElementUtils();
            typeUtils = processingEnv.getTypeUtils();
        }
    
        /**
         * 设置支持的版本
         */
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }
    
        /**
         * 设置注解处理器要处理的注解
         */
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            Set<String> types = new HashSet<>();
            types.add(BindView.class.getCanonicalName());
            return types;
        }
    
        /**
         * 处理注解,可以生成一些文件
         */
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            return false;
        }
        
        /**
         * 打印
         */
        private void print(String log){
            messager.printMessage(Diagnostic.Kind.NOTE, "BindViewProcessor " + log);
        }
    }
    

    需要重写的相关方法:

    • init():初始化,获取一些有用的系统工具类,比如生成文件、打印信息、处理元素等
    • getSupportedSourceVersion():设置支持的版本,一般用:SourceVersion.latestSupported()
    • getSupportedAnnotationTypes():设置注解处理器要处理的注解
    • process():处理注解,可以生成一些文件。

    2.2.1 手动生成 META-INF 信息

    注解处理器需要设置 META-INF 信息,才能被编译期自动识别,在

     :app:compileDebugJavaWithJavac
    

    就能生效了。

    1)在main目录下创建文件夹:resources

    2)在resources目录下创建文件夹:META-INF.services

    3)在META-INF.services目录下创建文件:javax.annotation.processing.Processor, 文件里写上注解处理器的全路径类名,如:com.yang.apt.lib.processor.BindViewProcessor

    在编译时,java编译器(javac)会去META-INF中查找实现了的AbstractProcessor的子类,并且调用该类的process函数,最终生成.java文件。其实就像activity需要注册一样,就是要到META-INF注册 ,javac才知道要给你调用哪个类来处理注解。

    META-INF 信息是告诉编译器,我有一个注解处理器,我的注解处理器全路径类名为:com.yang.apt.lib.processor.BindViewProcessor

    3.png 4.png

    2.2.2 使用auto-service,自动生成 META-INF 信息(推荐)

    上面设置 META-INF 信息是不是很麻烦,这时自动生成 META-INF 信息就来了,只需要一行注解。

    在lib-processor的build.gradle,引用auto-service

    apply plugin: 'java-library'
    
    java {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    
    dependencies {
        implementation project(':lib-annotation')
    
        //依赖auto-service,自动生成META-INF 信息
        implementation "com.google.auto.service:auto-service:1.0-rc4"
        annotationProcessor "com.google.auto.service:auto-service:1.0-rc4"
    }
    

    在我们的注解处理器,添加注解:@AutoService

    import javax.annotation.processing.Processor;
    
    @AutoService(Processor.class)
    public class BindViewProcessor extends AbstractProcessor {
    }
    

    构建我们的lib-processor module,会在build目录下,生成META-INF 信息,如图:

    5.png

    auto-service是谷歌的一个库,可以帮助我们自动生成META-INF 信息,它也是个注解处理器,有兴趣的话可以看看它的源码,很简单,可以学习一下

    2.3、实现process()方法,生成.java文件

    首先,我们在app module的buid.gradle,依赖注解module和注解处理器module:

    dependencies {
        implementation 'androidx.core:core-ktx:1.3.2'
        implementation 'androidx.appcompat:appcompat:1.2.0'
        testImplementation 'junit:junit:4.+'
        androidTestImplementation 'androidx.test.ext:junit:1.1.2'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    
        implementation project(':lib-annotation')
        //依赖注解处理器
        annotationProcessor project(':lib-processor')
    }
    

    在MainActivity中使用注解@BindView

    public class MainActivity extends AppCompatActivity {
    
        @BindView(R.id.textView)
        public TextView textView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    }
    

    我们要实现一个这样的类,达到自动findViewById,类似于:butterknife

    package com.yang.apt.demo;
    
    public class MainActivity_ViewBinding {
    
        public void bind(MainActivity target) {
            target.textView = (android.widget.TextView)target.findViewById(2131165337);
        }
    }
    

    这个类肯定不是手写的,需要我们注解处理器自动生成的,下面代码实现了process()方法,并生成了com.yang.apt.demo.MainActivity_ViewBinding.java文件

        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            print("process:开始");
    
            //此方法可能会进入三次,第一次annotations不为空,后面为空
            if (annotations.isEmpty()){
                return false;
            }
    
            //获取被注解的对象集合
            Set<? extends Element> elementsAnnotatedSet = roundEnv.getElementsAnnotatedWith(BindView.class);
    
            Map<String, List<VariableElement>> map = new HashMap<>();
            for (Element element : elementsAnnotatedSet){
    
    //        TypeElement//类,注解使用到类上,就是得到类元素
    //        ExecutableElement//方法,注解使用到方法上,就是得到可执行元素
    //        VariableElement//变量,注解使用到成员变量上,就是得到变量元素
    
                VariableElement variableElement = (VariableElement) element;
                //获取类名
                String clazzName = variableElement.getEnclosingElement().getSimpleName().toString();
                List<VariableElement> variableElements = map.computeIfAbsent(clazzName, k -> new ArrayList<>());
                variableElements.add(variableElement);
            }
    
            if (!map.isEmpty()){
                for (String clazzName : map.keySet()) {
                    List<VariableElement> variableElements = map.get(clazzName);
    
                    //生成  **_ViewBinding.java文件
                    createViewBindingFile(clazzName, variableElements);
                }
            }
    
            print("process:结束");
    
            //返回false,此方法还会被进入,因为生成了代码,生成了代码中可能还有其他注解,需要再生成代码
            return false;
        }
        
        /**
         * 生成  **_ViewBinding.java文件
         *
         * 如:com.yang.apt.demo.MainActivity_ViewBinding.java
         */
        private void createViewBindingFile(String clazzName, List<VariableElement> variableElements) {
            TypeElement typeElement = (TypeElement) variableElements.get(0).getEnclosingElement();
    
            PackageElement packageElement = processingEnv.getElementUtils().getPackageOf(typeElement);
    
            //获取包名
            String packageName = packageElement.toString();
            try {
                JavaFileObject file = processingEnv.getFiler().createSourceFile(packageName + "." + clazzName + "_ViewBinding");
                Writer writer = file.openWriter();
                writer.write("package " + packageName + ";");
                writer.write("\n\npublic class " + clazzName + "_ViewBinding {");
    
                writer.write("\n\n\tpublic void bind(" + clazzName + " target) {");
    
                for (VariableElement variableElement : variableElements) {
                    //获取变量名
                    String variableName = variableElement.getSimpleName().toString();
                    //获取变量的注解值
                    int id = variableElement.getAnnotation(BindView.class).value();
                    //获取变量的类型
                    TypeMirror typeMirror = variableElement.asType();
                    writer.write("\n\t\ttarget." + variableName + " = (" + typeMirror.toString() + ")target.findViewById(" + id + ");");
                }
    
                writer.write("\n\t}");
                writer.write("\n}");
                writer.flush();
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
    
    

    我们构建一下项目(Make Project),在app module的build目录下,生成了我们需要的Java文件:

    6.png

    可以在MainActivity里直接用,实现了自动findViewById:

    
    public class MainActivity extends AppCompatActivity {
    
        @BindView(R.id.textView)
        public TextView textView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            
            new MainActivity_ViewBinding().bind(this);
        }
    }
    

    这样写虽然也行,看着不友好,butterknife是这样用的,每个Activity都是这行代码:

    ButterKnife.bind(this);
    

    2.4、反射调用 MainActivity_ViewBinding

    新建Android library module,命名为:lib-bindview,新建类:ButterKnife,反射实现调用

    new MainActivity_ViewBinding.bind(this);
    

    如下:

    public class ButterKnife {
    
        public static void bind(Activity activity){
            String clazzName = activity.getClass().getName() + "_ViewBinding";
            try {
                Class<?> clazz = Class.forName(clazzName);
                Method method = clazz.getMethod("bind", activity.getClass());
                method.invoke(clazz.newInstance(), activity);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    修改app module的依赖:

    dependencies {
        implementation 'androidx.core:core-ktx:1.3.2'
        implementation 'androidx.appcompat:appcompat:1.2.0'
        testImplementation 'junit:junit:4.+'
        androidTestImplementation 'androidx.test.ext:junit:1.1.2'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    
        implementation project(':lib-annotation')
        implementation project(':lib-bindview')
    
        //依赖注解处理器
        annotationProcessor project(':lib-processor')
    }
    

    修改MainActivity的调用:

    public class MainActivity extends AppCompatActivity {
    
        @BindView(R.id.textView)
        public TextView textView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            //new MainActivity_ViewBinding().bind(this);
            ButterKnife.bind(this);
        }
    }
    

    优雅美观了!

    3、使用JavaPoet优化process()生成Java文件

    在上面process()中,生成.java文件时,直接拼写的字符串,不美观,没有面向对象。如果生成的类代码很多,方法很多,像缩进、换行、括号之类的控制,会很麻烦了。

    JavaPoet是大名鼎鼎square公司的一款生成.java文件的开源库。

    JavaPoet开源库地址:https://github.com/square/javapoet

    官方的文档很详细,如果想看中文的,可以看:https://blog.csdn.net/l540675759/article/details/82931785

    使用JavaPoet优化process()后:

        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            print("process:开始");
    
            //此方法可能会进入三次,第一次annotations不为空,后面为空
            if (annotations.isEmpty()){
                return false;
            }
    
            //获取被注解的对象集合
            Set<? extends Element> elementsAnnotatedSet = roundEnv.getElementsAnnotatedWith(BindView.class);
    
            Map<String, List<VariableElement>> map = new HashMap<>();
            for (Element element : elementsAnnotatedSet){
    
    //        TypeElement//类,注解使用到类上,就是得到类元素
    //        ExecutableElement//方法,注解使用到方法上,就是得到可执行元素
    //        VariableElement//变量,注解使用到成员变量上,就是得到变量元素
    
                VariableElement variableElement = (VariableElement) element;
                //获取类名
                String clazzName = variableElement.getEnclosingElement().getSimpleName().toString();
                List<VariableElement> variableElements = map.computeIfAbsent(clazzName, k -> new ArrayList<>());
                variableElements.add(variableElement);
            }
    
            if (!map.isEmpty()){
                for (String clazzName : map.keySet()) {
                    List<VariableElement> variableElements = map.get(clazzName);
    
                    //生成  **_ViewBinding.java文件
                    createViewBindingFileByJavaPoet(clazzName, variableElements);
                }
            }
    
            print("process:结束");
    
            //返回false,此方法还会被进入,因为生成了代码,生成了代码中可能还有其他注解,需要再生成代码
            return false;
        }
    
    
        /**
         * 使用JavaPoet生成  **_ViewBinding.java文件
         *
         * 如:com.yang.apt.demo.MainActivity_ViewBinding.java
         */
        private void createViewBindingFileByJavaPoet(String clazzName, List<VariableElement> variableElements) {
            //获取类元素
            TypeElement typeElement = (TypeElement) variableElements.get(0).getEnclosingElement();
            PackageElement packageElement = processingEnv.getElementUtils().getPackageOf(typeElement);
            //获取包名
            String packageName = packageElement.toString();
            try {
                //构建方法:public void bind(MainActivity target)
                MethodSpec.Builder bindBuilder = MethodSpec.methodBuilder("bind")
                        .addModifiers(Modifier.PUBLIC)
                        .addParameter(TypeName.get(typeElement.asType()), "target")
                        .returns(void.class);
    
                for (VariableElement variableElement : variableElements) {
                    //获取变量名
                    String variableName = variableElement.getSimpleName().toString();
                    //获取变量的注解值
                    int id = variableElement.getAnnotation(BindView.class).value();
                    //获取变量的类型
                    TypeMirror typeMirror = variableElement.asType();
    
                    //给方法添加语句:target.textView = (android.widget.TextView)target.findViewById(2131165338);
                    bindBuilder.addStatement("target.$L = ($L)target.findViewById($L)", variableName, typeMirror.toString(), id);
                }
    
                //构建类: MainActivity_ViewBinding
                TypeSpec typeSpec = TypeSpec.classBuilder(clazzName + "_ViewBinding")
                        .addModifiers(Modifier.PUBLIC)
                        .addMethod(bindBuilder.build())
                        .build();
    
                //构建Java文件
                JavaFile javaFile = JavaFile.builder(packageName, typeSpec).build();
    
                //写入.java文件
                javaFile.writeTo(processingEnv.getFiler());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
    

    自动生成的代码,和我们手动拼接的差不多,这样写更显得高大上,优雅!

    7.png

    源码

    本文示例代码 源码https://github.com/jinxiyang/AptDemo

    项目结构:

    • app : Android Module,可运行的APP,依赖lib-annotation、lib-processor、lib-bindview
    • lib-annotation : Java Library module,注解库
    • lib-processor:Java Library module,注解处理器库,依赖lib-annotation、auto-service、JavaPoet (推荐)
    • lib-processor2:Java Library module,注解处理器库,另一种写法,依赖lib-annotation,手写META-INF信息
    • lib-bindview:Android Library module,反射执行viewbinding,依赖lib-annotation

    如图:


    0.png

    相关文章

      网友评论

          本文标题:Android APT 注解处理器

          本文链接:https://www.haomeiwen.com/subject/sykzcrtx.html