Android AOP(1): APT Annotation Processing Tool Annotation Processing Processor

Keywords: Java Android Google ButterKnife

introduce

APT(Annotation Processing Tool), an annotation processor, is a tool for processing annotations, specifically a tool for javac, which scans and processes annotations at compile time. The annotation processor takes Java code (or compiled bytecode) as input and generates. java files as output.
Simply put, at compile time, annotations are used to generate. java files.

Advantage
The advantage of using APT is that it is convenient, simple and can save a lot of repetitive code.
Students who have used ButterKnife, Dagger, EventBus and other annotation frameworks can feel that using these frameworks can save a lot of code, just write some annotations.
In fact, they just generated some code through annotations. Through learning APT, you will find that they are very strong.~~~

Using steps

1. apt-annotation

@Retention(RetentionPolicy.CLASS) //Runtime annotations
@Target(ElementType.FIELD)         //Represents the scope of annotations for class members (constructors, methods, member variables)
public @interface AttachView {
    int value();
}

2. apt-processor (Annotation Processor)

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.google.auto.service:auto-service:1.0-rc2'//Google helps us quickly implement annotation processors
    compile project(':apt-annotation')//java lib for self-defined annotations
    compile 'com.squareup:javapoet:1.7.0'//Used to generate java files to avoid the embarrassment of string splicing
}

Create AttachProcessor

@AutoService(Processor.class)
public class ActivityInjectProcesser extends AbstractProcessor {
    private Filer mFiler; //File-related ancillary classes
    private Elements mElementUtils; //Element-related auxiliary classes Many elements
    private Messager mMessager; //Log-related ancillary classes

    private Map<String, AnnotatedClass> mAnnotatedClassMap;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        mFiler = processingEnv.getFiler();
        mElementUtils = processingEnv.getElementUtils();
        mMessager = processingEnv.getMessager();
        mAnnotatedClassMap = new TreeMap<>();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        mAnnotatedClassMap.clear();

        try {
            processActivityCheck(roundEnv);
        } catch (Exception e) {
            e.printStackTrace();
            error(e.getMessage());
        }

        for (AnnotatedClass annotatedClass : mAnnotatedClassMap.values()) {
            try {
                annotatedClass.generateActivityFile().writeTo(mFiler);
            } catch (Exception e) {
                error("Generate file failed, reason: %s", e.getMessage());
            }
        }
        return true;
    }


    private void processActivityCheck(RoundEnvironment roundEnv) throws IllegalArgumentException, ClassNotFoundException {
        //check ruleslass forName(String className
        for (Element element : roundEnv.getElementsAnnotatedWith((Class<? extends Annotation>) Class.forName(TypeUtil.ANNOTATION_PATH))) {
            if (element.getKind() == ElementKind.CLASS) {
                getAnnotatedClass(element);
            } else
                error("ActivityInject only can use  in ElementKind.CLASS");
        }
    }

    private AnnotatedClass getAnnotatedClass(Element element) {
        // tipe . can not use chines  so  ....
        // get TypeElement  element is class's --->class  TypeElement typeElement = (TypeElement) element
        //  get TypeElement  element is method's ---> TypeElement typeElement = (TypeElement) element.getEnclosingElement();
        TypeElement typeElement = (TypeElement) element;
        String fullName = typeElement.getQualifiedName().toString();
        AnnotatedClass annotatedClass = mAnnotatedClassMap.get(fullName);
        if (annotatedClass == null) {
            annotatedClass = new AnnotatedClass(typeElement, mElementUtils, mMessager);
            mAnnotatedClassMap.put(fullName, annotatedClass);
        }
        return annotatedClass;
    }


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


    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        types.add(TypeUtil.ANNOTATION_PATH);
        return types;
    }

    private void error(String msg, Object... args) {
        mMessager.printMessage(Diagnostic.Kind.ERROR, String.format(msg, args));
    }

    private void log(String msg, Object... args) {
        mMessager.printMessage(Diagnostic.Kind.NOTE, String.format(msg, args));
    }
}
public class AnnotatedClass {


    private TypeElement mTypeElement;//activity  //fragmemt
    private Elements mElements;
    private Messager mMessager;//Log printing

    public AnnotatedClass(TypeElement typeElement, Elements elements, Messager messager) {
        mTypeElement = typeElement;
        mElements = elements;
        this.mMessager = messager;
    }


    public JavaFile generateActivityFile() {
        // build inject method
        MethodSpec.Builder injectMethod = MethodSpec.methodBuilder(TypeUtil.METHOD_NAME)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(TypeName.get(mTypeElement.asType()), "activity", Modifier.FINAL);
        injectMethod.addStatement("android.widget.Toast.makeText" +
                "(activity, $S,android.widget.Toast.LENGTH_SHORT).show();", "from build");
        //generaClass
        TypeSpec injectClass = TypeSpec.classBuilder(mTypeElement.getSimpleName() + "$$InjectActivity")
                .addModifiers(Modifier.PUBLIC)
                .addMethod(injectMethod.build())
                .build();
        String packgeName = mElements.getPackageOf(mTypeElement).getQualifiedName().toString();
        return JavaFile.builder(packgeName, injectClass).build();
    }

}
public class TypeUtil {
    public static final String METHOD_NAME = "inject";
    public static final String ANNOTATION_PATH = "com.example.apt_annotation.AttachView";
}

3. apt-library tool class

public class InjectActivity {
    private static final ArrayMap<String, Object> injectMap = new ArrayMap<>();

    public static void inject(AppCompatActivity activity) {
        String className = activity.getClass().getName();
        try {
            Object inject = injectMap.get(className);

            if (inject == null) {
                Class<?> aClass = Class.forName(className + "$$InjectActivity");
                inject = aClass.newInstance();
                injectMap.put(className, inject);
            }
            Method m1 = inject.getClass().getDeclaredMethod("inject", activity.getClass());
            m1.invoke(inject, activity);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Using the tool class, reflection calls the inject method toast of the following class

public class MainActivity$$InjectActivity {
  public void inject(final MainActivity activity) {
    android.widget.Toast.makeText(activity, "from build",android.widget.Toast.LENGTH_SHORT).show();;
  }
}
 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_file_list);
        InjectActivity.inject(this);//Call the class generated by build
        ยทยทยท

Annotation implements the unified Java Toast method as above.

Posted by jayshadow on Wed, 06 Feb 2019 04:39:17 -0800