android的APT技术

转载请标明出处:https:////www.cnblogs.com/tangZH/p/12343786.html

APT 是Annotation Processing Tool 的简称。

它是注解处理器,在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定),APT还会编译生成源文件和原来的源文件,将它们一起生成class文件。
简言之:APT可以根据注解,在编译时生成代码。

事实上它是javac的一个工具,命令行运行javac后便可以看到:

android的APT技术

 

 

 

 接下来我们就来实现一个apt的实例,类似于ButterKnife中@BindView注解,基本步骤如下:

1、定义要被处理的注解。

2、定义注解处理器(生成具体的类)。

3、调用处理器生成的代码

 

对应的,我们在工程中需要有这几个模块:

1、app。测试我们的功能

2、apt-annotation。一个Java library module,放置我们自定义注解

3、apt-processor。一个Java library module,注解处理器模块

4、apt-sdk。一个Android library module,通过反射调用apt-processor模块生成的方法,实现view的绑定。

工程目录如下:

android的APT技术

 

 

 

 

 

1、在apt-annotation中自定义注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

 

2、apt-processor中引入依赖,它需要依赖apt-annotation,同时还需要依赖auto-service第三方库,后面创建注解处理器的时候需要用到。

apt-processor/build.gradle文件中:

implementation project(‘:apt-annotation‘)
implementation ‘com.google.auto.service:auto-service:1.0-rc2‘

 

3、在pat-processor中创建注解处理器:

处理器需要继承AbstractProcessor,注意该module是 java module,如果创建的是android module的话那么就会找不到AbstractProcessor

@AutoService(Processor.class)
@SuppressWarnings("unused")
public class BindViewProcessor extends AbstractProcessor {
    private Elements mElementUtils;
    private Map<String, ClassCreatorFactory> mClassCreatorFactoryMap = new HashMap<>();

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        //拿到工具类
        mElementUtils = processingEnvironment.getElementUtils();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        //这个注解处理器是给哪个注解用的
        HashSet<String> supportType = new LinkedHashSet<>();
        supportType.add(BindView.class.getCanonicalName());
        return supportType;
    }

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

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        mClassCreatorFactoryMap.clear();
        //得到所有包含该注解的element集合
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);

        for (Element element : elements) {
            //转换为VariableElement,VariableElement为element的子类
            VariableElement variableElement = (VariableElement) element;
            //可以获取类的信息的element,也是element的子类
            TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
            //获取包名加类名
            String fullClassName = classElement.getQualifiedName().toString();
            
            //保存到集合中
            ClassCreatorFactory factory = mClassCreatorFactoryMap.get(fullClassName);
            if (factory == null) {
                factory = new ClassCreatorFactory(mElementUtils, classElement);
                mClassCreatorFactoryMap.put(fullClassName, factory);
            }
            BindView bindViewAnnotation = variableElement.getAnnotation(BindView.class);
            int id = bindViewAnnotation.value();
            factory.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();
                //写入java代码
                writer.write(factory.generateJavaCode());
                writer.flush();
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return true;
    }
}

需要注意的是代码中不能有中文,否则编译不通过,我这里为了方便注释解释加上了中文。

 

ClassCreatorFactory的代码如下,这个类负责提供需要写入新的类的代码:

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

    ClassCreatorFactory(Elements elementUtils, TypeElement classElement) {
        this.mTypeElement = classElement;
        //PackageElement是element的子类,可以拿到包信息
        PackageElement packageElement = elementUtils.getPackageOf(mTypeElement);
        String packageName = packageElement.getQualifiedName().toString();
        String className = mTypeElement.getSimpleName().toString();
        this.mPackageName = packageName;
        //生成的类的名称
        this.mBindClassName = className + "_ViewBinding";
    }

    public void putElement(int id, VariableElement element) {
        mVariableElementMap.put(id, element);
    }

    public String generateJavaCode() {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("/**\n" + " * Auto Created by apt\n" + "*/\n");
        stringBuilder.append("package ").append(mPackageName).append(";\n");
        stringBuilder.append(‘\n‘);
        stringBuilder.append("public class ").append(mBindClassName);
        stringBuilder.append(" {\n");
        generateBindViewMethods(stringBuilder);

        stringBuilder.append(‘\n‘);
        stringBuilder.append("}\n");
        return stringBuilder.toString();
    }

    private void generateBindViewMethods(StringBuilder stringBuilder) {
        stringBuilder.append("\tpublic void bindView(");
        stringBuilder.append(mTypeElement.getQualifiedName());
        stringBuilder.append(" owner) {\n");
        for (int id : mVariableElementMap.keySet()) {
            VariableElement variableElement = mVariableElementMap.get(id);
            String viewName = variableElement.getSimpleName().toString();
            String viewType = variableElement.asType().toString();
            stringBuilder.append("\t\towner.");
            stringBuilder.append(viewName);
            stringBuilder.append(" = ");
            stringBuilder.append("(");
            stringBuilder.append(viewType);
            stringBuilder.append(")(((android.app.Activity)owner).findViewById( ");
            stringBuilder.append(id);
            stringBuilder.append("));\n");
        }
        stringBuilder.append("  }\n");
    }

    public String getClassFullName() {
        return mPackageName + "." + mBindClassName;
    }

    public TypeElement getTypeElement() {
        return mTypeElement;
    }
}

 

先不谈apt-sdk模块,我们先来看看生成的代码是怎么样的。

在app的gradle中引入:

implementation project(‘:apt-annotation‘)
annotationProcessor project(‘:apt-processor‘)

特别要注意的是apt-processor模块的依赖引进要用 annotationProcessor,否则编译报错

 

两个activity中:

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

    @BindView(R.id.tv_1)
    TextView tv;

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

 

public class Main2Activity extends AppCompatActivity {

    @BindView(R.id.tv_2)
    TextView textView;

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

 

rebuild一下便可以看到在这个目录下有我们生成的文件了。

gradle高版本出现编译后没出现文件的问题,无奈只好降低版本,我使用的版本是gradle  3.1.4 +  gralde_wrap  gradle-4.4-all.zip

 android的APT技术

 

 点进入其中一个可以看到是这样的代码:

/**
 * Auto Created by apt
*/
package com.example.aptsample;

public class MainActivity_ViewBinding {
    public void bindView(com.example.aptsample.MainActivity owner) {
        owner.tv = (android.widget.TextView)(((android.app.Activity)owner).findViewById( 2131165360));
        owner.textView = (android.widget.TextView)(((android.app.Activity)owner).findViewById( 2131165359));
  }

}

所以我们只要调用bindView就能够找到该view了,这也是apt-sdk要做的事情。

 

4、在apt-sdk中创建类,反射调用生成的类中的方法

 

public class DataApi {
    public static void bindView(Activity activity) {
        Class clazz = activity.getClass();
        try {
            Class<?> bindViewClass = Class.forName(clazz.getName() + "_ViewBinding");
            Method method = bindViewClass.getMethod("bindView", activity.getClass());
            method.invoke(bindViewClass.newInstance(),activity);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

 

 

5、app的gradle中引入apt-sdk,然后代码调用DataApi的方法

implementation project(‘:apt-annotation‘)
annotationProcessor project(‘:apt-processor‘)
implementation project(‘:apt-sdk‘)

 

app的MainActivity中实现

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

    @BindView(R.id.tv_1)
    TextView tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DataApi.bindView(this);
        tv.setText("a");
    }
}

这样就大功告成了

源码地址:https://github.com/TZHANHONG/AptAutoBindView

android的APT技术

上一篇:iOS 探究对象的本质


下一篇:asp.net core 使用Mysql和Dapper