1 a brief introduction

APT (Annotation Processing Tool) is a Tool for Processing annotations. It detects the annotations in source code files and uses the Annotation processor to process them. When processing annotations, the Annotation processor can generate additional source files and other files according to annotations in source files. APT will also compile the generated source files and original source files and generate class files together. Simply put: Automatically generate code without affecting performance! Common ButterKnife, Dagger and other tools use APT technology.

Example 2

We realized the charm of APT technology by implementing the function like ButterKnife to automatically generate findviewById. APT workflow

  1. Custom annotation
  2. Defining an Annotation processor (AbstractProcess)
  3. Java code generation in the processor (javaPoet is possible)
  4. Registration Handler (AutoService)

Create the apt-Annotation module to define the BindView annotation

@Retention(RetentionPolicy.CLASS) // Compile time annotations
@Target(ElementType.FIELD) // The annotation scope is a class member
public @interface BindView {
    int value(a);
}
Copy the code

Create the apt-Processor module to define the annotation processor, generate the required code, and register the processor

// BindViewProcessor.java
@AutoService(Processor.class) // Register the handler
public class BindViewProcessor extends AbstractProcessor {

    private Elements mElementUtils;
    private Map<String, ClassCreatorProxy> mProxyMap = new HashMap<>();

    ProcessingEnvironment provides a number of useful utility classes Elements, Types, and Filer
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        mElementUtils = processingEnv.getElementUtils();
    }

    /** * specifies which annotation handler is registered for, in this case BindView */
    @Override
    public Set<String> getSupportedAnnotationTypes(a) {
        HashSet<String> supportTypes = new LinkedHashSet<>();
        supportTypes.add(BindView.class.getCanonicalName());
        return supportTypes;
    }

    / * * * specified using Java version, usually returns SourceVersion. Here latestSupported () * /
    @Override
    public SourceVersion getSupportedSourceVersion(a) {
        return SourceVersion.latestSupported();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        // Generate Java files from annotations
        mProxyMap.clear();
        // Get all the annotations
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
        for (Element element : elements) {
            VariableElement variableElement = (VariableElement) element;
            TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
            String fullClassName = classElement.getQualifiedName().toString();
            ClassCreatorProxy proxy = mProxyMap.get(fullClassName);
            if (proxy == null) {
                proxy = new ClassCreatorProxy(mElementUtils, classElement);
                mProxyMap.put(fullClassName, proxy);
            }
            BindView bindAnnotation = variableElement.getAnnotation(BindView.class);
            int id = bindAnnotation.value();
            proxy.putElement(id, variableElement);
        }
        for (String key : mProxyMap.keySet()) {
            ClassCreatorProxy proxyInfo = mProxyMap.get(key);
            JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(), proxyInfo.generateJavaCode()).build();
            try {
                // Generate the file
                javaFile.writeTo(processingEnv.getFiler());
            } catch(IOException e) { e.printStackTrace(); }}return true; }}// ClassCreatorProxy.java
// JavaPoet generates Java code
public class ClassCreatorProxy {
    private String mBindingClassName;
    private String mPackageName;
    private TypeElement mTypeElement;
    private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>();

    public ClassCreatorProxy(Elements elementUtils, TypeElement classElement) {
        this.mTypeElement = classElement;
        PackageElement packageElement = elementUtils.getPackageOf(mTypeElement);
        String packageName = packageElement.getQualifiedName().toString();
        String className = mTypeElement.getSimpleName().toString();
        this.mPackageName = packageName;
        this.mBindingClassName = className + "_ViewBinding";
    }

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

    /** * Create Java code */
    public TypeSpec generateJavaCode(a) {
        return TypeSpec.classBuilder(mBindingClassName)
                .addModifiers(Modifier.PUBLIC)
                .addMethod(generateMethods())
                .build();

    }

    /** * add Method */
    private MethodSpec generateMethods(a) {
        ClassName host = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());
        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
                .addModifiers(Modifier.PUBLIC)
                .returns(void.class)
                .addParameter(host, "host");

        for (int id : mVariableElementMap.keySet()) {
            VariableElement element = mVariableElementMap.get(id);
            String name = element.getSimpleName().toString();
            String type = element.asType().toString();
            methodBuilder.addCode("host." + name + "=" + "(" + type + ")(((android.app.Activity)host).findViewById( " + id + "));");
        }
        return methodBuilder.build();
    }

    public String getPackageName(a) {
        returnmPackageName; }}Copy the code

Create library (apt-library) reflection execute bind

public static void bind(Activity activity) {
    Class clazz = activity.getClass();
    try {
        Class bindViewClass = Class.forName(clazz.getName() + "_ViewBinding");
        Method method = bindViewClass.getMethod("bind", activity.getClass());
        method.invoke(bindViewClass.newInstance(), activity);
    } catch(ClassNotFoundException | IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) { e.printStackTrace(); }}Copy the code

Import apt related three packages in app main module, using BindView annotation

public class TestActivity extends AppCompatActivity {

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

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        BindViewTools.bind(this);
        mTextView.setText("hello world"); }}Copy the code

Compile the results



Can be found in/build/generated/ap_generated_sourcesBelow to find the generated Java file.



Write in the last

Through the above simple example of using APT to generate code, presumably we have an intuitive understanding of APT. For example, the popular ButterKnife frame in the market uses APT technology, which can be studied. This technique, coupled with the Javapoet framework, makes it possible to write many “elegant” frameworks.