Java AbstractProcessor实现自定义ButterKnife

在上一节中Java注解及自定义注解处理器,主要是说明了Java注解的基本用法以及通过反射在JVM运行时实现Java的注解处理器,同时也说明了通过反射实现注解处理器会对代码的运行性能有一定影响。

所以这里主要说明另一种方式,也就是通过AbstractProcessor在Java编译时生成代码的方式实现注解处理器。

AbstractProcessor介绍

AbstractProcessor,是一个抽象类,该类实现了接口Processor。
抽象类AbstractProcessor以及接口Processor都是位于包javax.annotation.processing中。该接口中定义的所有类、接口都是与实现注解处理器相关的。如下图所示:
这里写图片描述

这里主要说说AbstractProcessor类中各方法的用法。
这里写图片描述

init

void init(ProcessingEnvironment processingEnv) ,该方法主要用于一些初始化的操作,通过该方法的参数ProcessingEnvironment可以获取一些列有用的工具类。

ProcessingEnvironment源码如下:

public interface ProcessingEnvironment {

        /**
         * 返回用来在元素上进行操作的某些实用工具方法的实现。<br>
         *
         * Elements是一个工具类,可以处理相关Element(包括ExecutableElement, PackageElement, TypeElement, TypeParameterElement, VariableElement)
         */
        Elements getElementUtils();

        /**
         * 返回用来报告错误、警报和其他通知的 Messager。
         */
        Messager getMessager();

        /**
         *  用来创建新源、类或辅助文件的 Filer。
         */
        Filer getFiler();

        /**
         *  返回用来在类型上进行操作的某些实用工具方法的实现。
         */
        Types getTypeUtils();

        // 返回任何生成的源和类文件应该符合的源版本。
        SourceVersion getSourceVersion();

        // 返回当前语言环境;如果没有有效的语言环境,则返回 null。
        Locale getLocale();

        // 返回传递给注释处理工具的特定于 processor 的选项
        Map<String, String> getOptions();
    }

getSupportedSourceVersion

返回此注释 Processor 支持的最新的源版本,该方法可以通过注解@SupportedSourceVersion指定。

@Override
public SourceVersion getSupportedSourceVersion() {
  return SourceVersion.latestSupported();
}

getSupportedAnnotationTypes

返回此 Processor 支持的注释类型的名称。结果元素可能是某一受支持注释类型的规范(完全限定)名称。它也可能是 ” name.” 形式的名称,表示所有以 ” name.” 开头的规范名称的注释类型集合。最后,自身表示所有注释类型的集合,包括空集。注意,Processor 不应声明 “*”,除非它实际处理了所有文件;声明不必要的注释可能导致在某些环境中的性能下降。

process

注解处理器的核心方法,处理具体的注解,这个将在下面的自定义注解中详细说明该方法应该如何使用。

其中,getSupportedSourceVersion和getSupportedAnnotationTypes也可以通过给注解处理器添加注解指定具体值。
如下:

@SupportedOptions()
@SupportedAnnotationTypes()
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class ButterKnifeProcessor extends AbstractProcessor {
    // 省略具体代码
}

自定义ButterKnife

在Android中,ButterKnife是一个通过注解实现依赖注入的框架,所以这里也通过实现一个简单的ButterKnife框架,熟悉通过 AbstractProcessor实现注解处理器的过程。

首先,需要创建两个Java库butterknife-annotations和butterknife-compiler以及一个android库butterknife

  • butterknife-annotations,用于声明具体的注解的,如BindView、OnClick等)。主要供butterknife-compiler和butterknife库引用
  • butterknife-compiler,用于声明自定义AbstractProcessor,在编译时生成具体的Java文件。
  • butterknife,这个就是具体android项目中需要引用的库,主要实现具体依赖注入的功能。

这里有两个问题需要说明:
为什么需要创建Java库? 创建Java库是因为在使用自定义AbstractProcessor需要使用到javax包中的相关类和接口,这个在android库中并不存在,所以需要使用到Java库。

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

butterknife-annotations

在这个库中主要用于声明具体的注解的,这里通过定义一个@BindView注解作为示例,如下

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

    int value();
}

butterknife-compiler

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

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

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

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

对于生成Java文件需要使用到开源库javapoet,这是square专门开发的用于辅助生成Java文件的库,关于该库的用法这里不做过多说明。

下面是自定义AbstractProcessor中的具体逻辑:

package com.zhangke.compiler;

import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import com.zhangke.annotations.BindView;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;

@AutoService(Processor.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();
            }

        }
    }

}

注解处理器的配置

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

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

如下图:
这里写图片描述

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

build.gradle配置:

apply plugin: 'java-library'

dependencies {
    compile project(':butterknife-annotations')

    compile 'com.google.auto.service:auto-service:1.0-rc3'
    compile 'com.google.auto:auto-common:0.8'
    compile 'com.squareup:javapoet:1.8.0'
}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"

注解处理器注解:

@AutoService(Processor.class)
public class ButterKnifeProcessor extends AbstractProcessor {
    // 代码省略
}

这样一个注解处理器算是正式完成了,接下来可以看看这个注解处理器是否能生成新的Java文件。

butterknife

butterknife是一个android库工程,该库的作用主要就是完成依赖注入。在该库中会依赖butterknife-annotations,最终在Android项目中我们只需要依赖这个工程即可。

下面看看在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();
        }

    }
}

使用butterknife完成依赖注入

APT工具

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

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

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

dependencies {
    // 省略

    // 添加butterknife依赖
    implementation project(':butterknife')
    annotationProcessor project(':butterknife-compiler')
}

测试

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

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.button)
    Button button;
    @BindView(R.id.textview)
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

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

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

    @BindView(R.id.imageView)
    ImageView imageView;
    @BindView(R.id.progressBar)
    ProgressBar progressBar;
    @BindView(R.id.checkBox)
    CheckBox checkBox;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_two);
    }
}

通过Android Studio的Rebuild Project,就可以看到在主app工程build/generated/source/apt/debug目录下就已经生成了两个新的Java文件,
如下图:
这里写图片描述

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

// Generated code from Butter Knife. Do not modify!
package com.zhangke.simplifybutterknife;

import android.widget.Button;
import android.widget.TextView;

public final class MainActivity_ViewBinding {
    public MainActivity_ViewBinding(MainActivity target) {
        target.button = (Button) target.findViewById(2131165219);
        target.textView = (TextView) target.findViewById(2131165305);
    }
}

最后想完成依赖注入,只需要通过工具类ButterKnife的bind方法就可以了,这个在上面MainActivity的源码中可以看到。

这样一个简单的自定义ButterKnife就完成了,因为这个是在看了ButterKnife的源码后进行总结的,其实以上整个流程就是开源项目ButterKnife的基本工作原理。如果有什么不正确的地方,望指正。这里也提供一遍专门讲解ButterKnife的文章:浅析ButterKnife

最后附上项目源码

  • 9
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值