Java编译时注解处理器的学习总结

2020-08-03  本文已影响0人  132xin

APT的简介

定义

APT即是Annotation Processing Tool ,它是一个javac的一个工具,中文的意思是编译时注解处理器,可以用来在编译时扫描和处理注解。通过APT可以取到注解和被注解对象的相关信息,在拿到这些信息后我们可以根据需求来自动生成一些代码,省去了手动编写。获取注解及生成代码都是在代码编译的时候完成的,相比反射在运行时处理注解大大提高了程序性能。APT的核心就是AbstractProcessor类。

如何在Android中使用APT呢

在Android工程中使用APT,至少需要两个Java Library模块组成,在Android中创建Java Library的的步骤是,首先建一个Android项目,然后点击File-> New ->New Module,打开如图所示,然后选择Java Library模块。


image.png

这两个模块的作用是:
一个Annotation模块,这个用来存在自定义的注解。
一个Compiler模块,这个模块依赖Annotation模块。
在项目的App模块和其它的业务模块都需要依赖Annotation模块,同时需要通过annotationProcessor依赖Compiler模块。
app模块的gradle中依赖关系如下:

implementation project(':annotation')
annotationProcessor project(':factory-compiler')

实现ButterKnife例子学习APT

步骤:

新建一个Android的app的工程。
创建一个Java Library ,定义要被处理的注解(apt-annotation)。
创建一个Java Library,定义注解处理器生成具体的类。(apt-processor)
创建一个Android library module,通过反射调用apt-processor模块生成的方法,实现View的绑定。

工程目录如下:


image.png
在apt-annotation中定义一个注解
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
    int value() default -1;
}

因为这个注解是在成员变量中使用的,保留的时间是在.class字节码文件,不需要值JVM期间保留,这也体现了APT只是在编译器的编译时期完成工作。

在apt-processor中的gradle文件中加入两个依赖。

为了解决android studio升级到3.4.2,gradle升级到5.1.1后,apt不会执行,没办法自动生成注解文件的问题,还需要添加 annotationProcessor。

  implementation project(':apt-annotation')
  implementation 'com.google.auto.service:auto-service:1.0-rc4'
 annotationProcessor  'com.google.auto.service:auto-service:1.0-rc4'

第二个依赖是AutoService,它是google开发的一个库,在使用@AutoService注解时需要用到,它的作用是用来生成META.INF/services/javax.annotation.processing.Processor文件的,在使用注解器的时候就不需要手动添加该文件。

在apt-processor中创建注解处理器

该Module是Java Library,不能是Android Library,因为Android平台的是基于OpenJDK的,而OpenJDK中是不包含APT的相关代码。

BindViewProcessor要引用AutoService的注解,以及继承AbstractProcessor.并重写相应的方法:

@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        return false;
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return super.getSupportedAnnotationTypes();
    }

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

接下来对重写的方法进行解析

  • public synchronized void init()
    这个 方法是用来初始化处理器的,方法中有个ProcessingEnvironment processingEnvironment类型的参数,它是一个注解处理工具的集合。包含了众多的工具类,例如Filer可以用来编写新文件。Meessage可以用来处理错误信息。Elements是一个可以处理Element的工具类。
  • 什么是Elments?
    在Java语言中,Elenent是一个接口,表示一个程序的元素,例如包,类,方法,变量。Element已知的子接口有如下几种。
    PackageElement 表示一个包程序元素。
    ExecutableElement表示某个类或者接口的方法,构造方法和初始化程序,包括注释类型元素。
    TypeElement 表示一个类或者接口的元素。注意对有关类型及其成员的信息的访问。注意,枚举类型是一种类,而注解类型是一种接口。
    VariableElement表示一个字段,enum常量,方法或者构成方法参数,局部变量或者异常参数。
  • public boolean process()
    这个是AbstractProcessor中最重要的一个方法,该方法的返回值是一个boolean类型,返回值表示注解是否由当前Processor处理,如果返回true,则这些注解由这个处理器进行处理,后续的其他Processor无需处理它们。如果返回false,则这些注解未在次Processor中处理,需要后续的其他Processor处理它们。在这个方法中,我们需要检验被注解的对象是否合法,可以编写处理注解的代码,以及自动生成需要的java文件等。处理的大部分逻辑都在这个类中。
  • public Set<String> getSupportedAnnotationTypes()
    这方法是返回一个set集合,集合中指定那些注解是需要这个处理器处理的。
  • public SourceVersion getSupportedSourceVersion()
    这个返回是返回当前的正在使用的java版本。通常返回SourceVersion.latestSupported()就可以了。

接下来是编写BingViewProcessor的代码,注意改文件中不可以含有中文,否则编译不通过。代码中的中文是为了方便理解加的解析,在运行的时候要删除。如果需要中文解析的可以在build.gradle中添加

//中文乱码问题(错误:编码GBK不可映射字符)
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
    private Elements mElementUtil;
    private Map<String,ClassCreatorFactory> mClassCreatorFactoryMap=new HashMap<>();
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        //初始化Element
        mElementUtil= processingEnvironment.getElementUtils();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        mClassCreatorFactoryMap.clear();
        //获取所有包含了@BingView注解的集合
        Set<? extends Element> elements=roundEnvironment.getElementsAnnotatedWith(BindView.class);
        for (Element element:elements){
            VariableElement variableElement= (VariableElement) element;
            TypeElement typeElement= (TypeElement) variableElement.getEnclosingElement();
            String fullClassName=typeElement.getQualifiedName().toString();
            ClassCreatorFactory classCreatorFactory=mClassCreatorFactoryMap.get(fullClassName);
            if (classCreatorFactory==null){
                classCreatorFactory=new ClassCreatorFactory( mElementUtil,typeElement);
                mClassCreatorFactoryMap.put(fullClassName,classCreatorFactory);
            }
            BindView bindViewAnnotation=variableElement.getAnnotation(BindView.class);
            int id=bindViewAnnotation.value();
            classCreatorFactory.putElement(id,variableElement);
}
   //开始创建Java类
            for (String key:mClassCreatorFactoryMap.keySet()){
                ClassCreatorFactory factory=mClassCreatorFactoryMap.get(key);
                try {
                    JavaFileObject fileObject=processingEnv.getFiler().createSourceFile(factory.getClassFullName(),factory.getTypeElement());
                    Writer writer=fileObject.openWriter();
                    writer.write(factory.generateJavaCode());
                    writer.flush();
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        //返回true说明这个processor要处理这个注解,后续的Processor不需
        return true;
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        //设置这个注解器是给哪个注解类用的,这里是给BingView的注解类使用
        HashSet<String> supportType=new LinkedHashSet<>();
        supportType.add(BindView.class.getCanonicalName());
        return supportType;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        //返回java的版本
        return SourceVersion.latestSupported();
    }

}

ClassCreatorFactory的代码如下,注意该类的代码不能出现中文的注释,该类的代码就是相当于在代码中通过字符串的方式编写代码,需要注意空格等细节问题,这个类很容易出错,可以利用JavaPoet根据实体类生成。

public class ClassCreatorFactory {
    private String mClassName;
    private String mPackageName;
    private TypeElement typeElement;
    private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>();

    public ClassCreatorFactory(Elements elements, TypeElement typeElement) {
        //获取该类
        this.typeElement = typeElement;
        //获取包元素
         PackageElement packageElement = elements.getPackageOf(typeElement);
        mPackageName = packageElement.getQualifiedName().toString();
        String temClassName = typeElement.getSimpleName().toString();
        //生成类的名称
        mClassName = temClassName + "_ViewBinding";
    }

    /**
     * 添加VariableElement,例如字段,局部变量,参数等
     * @param id
     * @param element
     */
    public void putElement(int id,VariableElement element){
        mVariableElementMap.put(id,element);
    }

    /**
     * 创建java代码,可以看出是文本的形式,对字符进行拼接即可。
     * @return
     */
    public String generateJavaCode(){
        StringBuilder stringBuilder=new StringBuilder();
        //注释
        stringBuilder.append("/**\n"+"* 通过APT自动创建的类"+"\n*/\n");
        //包名
        stringBuilder.append("package ").append(mPackageName).append(";\n\n");
        stringBuilder.append("public class ").append(mClassName).append("{\n");
        //创建方法
        generateMethod(stringBuilder);
        stringBuilder.append("\n}\n");
        return stringBuilder.toString();
    }

    /**
     * 创建方法
     * @param stringBuilder
     */
    private void generateMethod(StringBuilder stringBuilder) {
        stringBuilder.append("\tpublic void bindView(");
        stringBuilder.append(typeElement.getQualifiedName());
        stringBuilder.append(" value) { \n");
        for (int id:mVariableElementMap.keySet()){
            VariableElement variableElement=mVariableElementMap.get(id);
            String viewName=variableElement.getSimpleName().toString();
            String viewType=variableElement.asType().toString();
            stringBuilder.append("\t\tvalue.");
            stringBuilder.append(viewName);
            stringBuilder.append(" = ");
            stringBuilder.append("(");
            stringBuilder.append(viewType);
            //findViewById(id);
            stringBuilder.append(")(((android.app.Activity)value).findViewById( ");
            stringBuilder.append(id+" ));");
            stringBuilder.append("\n}\n");
        }

    }
    public String getClassFullName(){
        return mPackageName+"."+mClassName;
    }
    public TypeElement getTypeElement(){
        return typeElement;
    }


}

创建apt-sdk

通过反射调用生成类的方法。

public class BindViewUtil {
    public static void bindView(Activity activity) throws ClassNotFoundException, NoSuchMethodException {
        try {
            Class cla=activity.getClass();
            Class bindViewClass=Class.forName(cla.getName()+"_ViewBinding");
            Method bindViewMethod=bindViewClass.getMethod("bindView",cla);
            bindViewMethod.setAccessible(true);
            bindViewMethod.invoke(bindViewClass.newInstance(),activity);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }

    }
}
在App中调用

需要加入以下的权限:

   implementation project(":apt-annotation")
   //要用 annotationProcessor ,否则编译不通过
   annotationProcessor project(":apt-procrssor")
   implementation project(':apt-sdk')

使用该Apt

public class MainActivity extends AppCompatActivity {
    @BindView(R.id.textView)
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        try {
            BindViewUtil.bindView(this);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        textView.setText("你好,注解处理器");
    }
}

接下来我们看到生成的类在下面的目录中


image.png
public class MainActivity_ViewBinding{
    public void bindView(com.example.hx.apttest.MainActivity value) { 
        value.textView = (android.widget.TextView)(((android.app.Activity)value).findViewById( 2131165353 ));
}

}

从上面的类中可以看到调用用findViewById的方法。

参考链接:
https://blog.csdn.net/qq_20521573/article/details/82321755
http://77blogs.com/?p=199

上一篇下一篇

猜你喜欢

热点阅读