APT, Annotation Processing Tool, is an annotation processor, is a tool used to process annotations and is commonly used inCompile timeScan and process annotations, and finally generate a Java file that processes the annotation logic. APT technology is used in many current frameworks, such as ButterKnife, ARouter, GreenDAO and other frameworks, which have the shadow of APT technology.
APT function
Use APT to process compile-time annotations at compile time and generate additional Java files, with the following effects:
-
It can achieve the effect of reducing duplicate code handwriting.
For example, ButterKnife, we can directly use annotations to reduce findviewbyid codes, and it is enough to just indicate which id it is through annotations.
Functional package. Encapsulate the main functional logic, leaving only annotation calls.
Compared with using Java reflection to handle runtime annotations, using APT has better performance.
Basic compilation process of Android
When compiling the code in Android, you need to go through the process of: Java—>class—> dex, and the code finally generates a dex file and is inserted into the APK package.
APT is involved at the beginning of compilation and is used to handle compile-time annotations.
AOP (Aspect Oridnted Programming) is to modify the code or add logic by directly modifying the .class file before generating the dex file after compilation. It is commonly used in code monitoring, code modification, and code analysis.
Basic use of APT
The basic usage process mainly includes the following steps:
- Create custom annotations
- Create annotation processors and process Java file generation logic
- Encapsulate an API for external calls
- Called in the project
This time, taking the imitation of ButterKnife binding View as an example, the code of findviewbyId is omitted, and the calling code is as follows:
public class AnnotationTestActivity extends AppCompatActivity { @BindView(.tv_annotation_test1) TextView tvAnnotationTest1; @BindView(.tv_annotation_test2) TextView tvAnnotationTest2; @Override protected void onCreate(Bundle savedInstanceState) { (savedInstanceState); setContentView(.activity_annotation_test); (this); ("Test text"); (new () { @Override public void onClick(View v) { (,"Control 1:"+.tv_annotation_test1); } }); ("Another text"); (new () { @Override public void onClick(View v) { (,"Control 2:"+.tv_annotation_test2); } }); } }
1. Custom annotations
We've created a new oneJava lib, namedannotationTest
, used to host custom annotations, the code is as follows:
@Retention() @Target() public @interface BindView { int value(); }
2. Annotation processor
Create an additional new oneJava lib, namedprocessorTest
, used to carry annotation processing and Java file generation logic.
It mainly includes the following steps:
- Add annotation processor
- Annotation processor registration
- Add java code generation logic
It should be noted that the current annotation processor lib needs to introduce annotation lib -annotationTest
, in the current ModuleConfiguration in the file:
implementation project(':annotationTest')//Rely on the annotation module just created
Annotation processor
The annotation processor code is as follows:
public class BindViewProcessor extends AbstractProcessor { private Messager mMessager; private Elements mElementUtils; private Map<String, ClassCreatorProxy> mProxyMap = new HashMap<>(); @Override public synchronized void init(ProcessingEnvironment processingEnv) { (processingEnv); mMessager = (); mElementUtils = (); } @Override public Set<String> getSupportedAnnotationTypes() { HashSet<String> supportTypes = new LinkedHashSet<>(); (()); return supportTypes; } @Override public SourceVersion getSupportedSourceVersion() { return (); } @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { (, "processing..."); (); //Get all comments Set<? extends Element> elements = (); for (Element element : elements) { VariableElement variableElement = (VariableElement) element; TypeElement classElement = (TypeElement) (); String fullClassName = ().toString(); ClassCreatorProxy proxy = (fullClassName); if (proxy == null) { proxy = new ClassCreatorProxy(mElementUtils, classElement); (fullClassName, proxy); } BindView bindAnnotation = (); int id = (); (id, variableElement); } //Create a java file by traversing mProxyMap for (String key : ()) { ClassCreatorProxy proxyInfo = (key); JavaFile javaFile = ((),proxyInfo.generateJavaCode2()).build(); try { //Generate Java files (()); } catch (IOException e) { (, " --> create " + () + "error"); } } (, "process finish ..."); return true; } }
For the sake of simplicity of code demonstration, there is no format verification (such as verification of the type of annotation modifications and other information). If you actually use APT technology, please be sure to verify the usage rules of the annotation.
Annotation processor registration
Custom annotation processors must be registered before they can be used, that is, they need to add automatic and proactive annotations to the annotation processor.
We can use Google autoService to automatically register the annotation processor. First, we need to use the module where the annotation processor is located.File add autoService package import:
//google autoService implementation ":auto-service:1.0-rc4" annotationProcessor ":auto-service:1.0-rc4"
Then add the automatically registered annotation to the annotation processor to achieve the automatic registration effect:
@AutoService() public class BindViewProcessor extends AbstractProcessor { ... }
Java code generation
There are many ways to generate Java code, such as string splicing, JavaPoet, etc.
If you want to use JavaPoet, you need to use the current JavaPoetModuleofIntroduced in the file:
//javaPoet implementation ":javapoet:1.13.0"
The detailed code is as follows:
package ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; 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) { = classElement; PackageElement packageElement = (mTypeElement); String packageName = ().toString(); String className = ().toString(); = packageName; = className + "_ViewBinding"; } public void putElement(int id, VariableElement element) { (id, element); } /** * Create Java code String stitching method * @return */ public String generateJavaCode() { StringBuilder builder = new StringBuilder(); ("package ").append(mPackageName).append(";\n\n"); ("import ..*;\n"); ('\n'); ("public class ").append(mBindingClassName); (" {\n"); generateMethods(builder); ('\n'); ("}\n"); return (); } /** * Add Method string stitching method * @param builder */ private void generateMethods(StringBuilder builder) { ("public void bind(" + () + " host ) {\n"); for (int id : ()) { VariableElement element = (id); String name = ().toString(); String type = ().toString(); ("host." + name).append(" = "); ("(" + type + ")((()host).findViewById( " + id + "));\n"); } (" }\n"); } public String getProxyClassFullName() { return mPackageName + "." + mBindingClassName; } public TypeElement getTypeElement() { return mTypeElement; } /** * Create Java code javapoet * @return */ public TypeSpec generateJavaCode2() { TypeSpec bindingClass = TypeSpec //class name setting .classBuilder(mBindingClassName) //The class is public .addModifiers() .addMethod(generateMethods2()) .build(); return bindingClass; } /** * Join Method javapoet */ private MethodSpec generateMethods2() { ClassName host = (().toString()); methodBuilder = MethodSpec //Method Name .methodBuilder("bind") //The method is public .addModifiers() //Return value .returns() //Method Parameters .addParameter(host, "host"); for (int id : ()) { VariableElement element = (id); String name = ().toString(); String type = ().toString(); ("host." + name + " = " + "(" + type + ")((()host).findViewById( " + id + "));\n"); } return (); } public String getPackageName() { return mPackageName; } }
3. External call
After completing the above two steps, the actual work of APT can be run normally. Let’s implement the following calling method to achieve the calling effect of imitating butterknife.
First we need to create a new oneAndroid libModule, namedapt_lib。
The current external lib needs to reference the annotation processor lib—processorTest
, in the current ModuleThe following configuration is performed in the file:
implementation project(path: ':processorTest')
Create a new external call method, the code is as follows:
public class BindViewTools { public static void bind(Activity activity) { Class clazz = (); try { Class bindViewClass = (() + "_ViewBinding"); Method method = ("bind", ()); ((), activity); } catch (ClassNotFoundException e) { (); } catch (IllegalAccessException e) { (); } catch (InstantiationException e) { (); } catch (NoSuchMethodException e) { (); } catch (InvocationTargetException e) { (); } } }
Its main function is to generate corresponding auxiliary classes for the annotated-bound Activity to achieve the effect of apt being called.
4. Call
After following the steps below, we are actually able to call normally@BindView
This annotation is here.
We are in our main Module—app
Call APT, of course, the APT needs to introduce the previous lib project, which can beappofThe following configuration is performed in the file:
implementation project(':annotationTest') // Add dependency module implementation project(':apt_lib') // Add dependency module implementation project(':processorTest') // Add dependency module annotationProcessor project(':processorTest')
It should be noted thatannotationProcessorThis annotation processing tool is used in Java. If you are using the kotlin language, you need to use kapt.
At this time, we directly call the code we call at the beginning, and it can run normally after compilation and execution.
When we view the projectbuild
When you are in a folder, you can find the files we generated before in the following path:
I saw the code inside as follows:
package ; public class AnnotationTestActivity_ViewBinding { public void bind(AnnotationTestActivity host) { host.tvAnnotationTest1 = ()((()host).findViewById( 2131231102)); host.tvAnnotationTest2 = ()((()host).findViewById( 2131231103)); } }
In this way, we complete a simple example of APT.
Summarize
This article mainly provides a brief explanation of APT technology in Android. APT technology can directly generate Java logical code using compiled technology, which is more suitable in scenarios with high code repetition.
This is the article about the usage example of annotated processor APT in Android. For more related content on annotated processor APT in Android, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!