Detailed explanation of APT application (hand-in-hand teaching you to write ButterKnife tool)

Detailed explanation of APT application (hand-in-hand teaching you to write ButterKnife tool)

[[410458]]

This article is reprinted from the WeChat public account "Android Development Programming", the author is Android Development Programming. Please contact the Android Development Programming public account for reprinting this article.

1. What is APT? What is it for? Learn with doubts

  • APT (Annotation Processing Tool) is an annotation processor, which is a tool for processing annotations. To be more precise, it is a tool of javac, which is used to scan and process annotations during compilation. The annotation processor takes Java code (or compiled bytecode) as input and generates .java files as output;
  • Simply put, at compile time, .java files are generated through annotations;
  • The advantage of using APT is that it is convenient and simple, and can reduce a lot of repeated code. Students who have used annotation frameworks such as ButterKnife, Dagger, and EventBus can feel that using these frameworks can reduce a lot of code. Just write some annotations. They just help generate some efficient code through annotations.

2. APT application - write an annotation based on ButterKnife

Implement a function through APT and implement View binding by annotating View variables

1. Create several Libraries to declare

  1. Android Library: aptlibs Normally write Android lib
  2. Java or Kotlin Library: aptlib-anno (specially for annotations written by us)
  3. Java or Kotlin Library: aptlib-processor (write the logic of dynamically generated files)
  4. aptlibs
  5. plugins {
  6. id 'com.android.library'  
  7. id 'kotlin-android'  
  8. }
  9. aptlib-anno
  10. plugins {
  11. id 'java-library'  
  12. }
  13. aptlib-processor
  14. plugins {
  15. id 'java-library'  
  16. }

This should be remembered clearly. Many bloggers probably have never written apt and cannot distinguish between AndroidLib and javaLib.

apt is originally provided by java, and the Android library does not allow inheritance of AbstractProcessor

2. Define annotations-custom annotations

Remember to create it under the aptlib-anno repository

  1. @Retention(RetentionPolicy.CLASS)
  2. @Target(ElementType.FIELD)
  3. public @interface BindView {
  4. int value();
  5. }

The runtime annotation BindView is defined, where value() is used to obtain the id of the corresponding View;

  • @Retention(RetentionPolicy.CLASS): indicates compile-time annotation
  • @Target(ElementType.FIELD): indicates that the annotation scope is class members (constructors, methods, member variables)
  • @Retention: defines how long to retain
  • RetentionPoicy.SOURCE, RetentionPoicy.CLASS, RetentionPoicy.RUNTIME
  • @Target: defines the scope of the modified object
  • TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, etc.

3. Define annotation processor-dynamically generate associated files

aptlib-processor library

First add dependencies under this lib

  1. dependencies {
  2. implementation 'com.google.auto.service:auto-service:1.0-rc2'   
  3. implementation project( ':aptlib-anno' )
  4. }

Create BindViewProcessor

  1. @AutoService(Processor.class)
  2. public class BindViewProcessor extends AbstractProcessor {
  3. private Messager mMessager;
  4. private Elements mElementUtils;
  5. private Map<String, ClassCreatorProxy> mProxyMap = new HashMap<>();
  6. @Override
  7. public synchronized void init(ProcessingEnvironment processingEnv) {
  8. super.init(processingEnv);
  9. mMessager = processingEnv.getMessager();
  10. mElementUtils = processingEnv.getElementUtils();
  11. }
  12. @Override
  13. public   Set <String> getSupportedAnnotationTypes() {
  14. HashSet<String> supportTypes = new LinkedHashSet<>();
  15. supportTypes.add ( BindView.class.getCanonicalName ());
  16. return supportTypes;
  17. }
  18. @Override
  19. public SourceVersion getSupportedSourceVersion() {
  20. return SourceVersion.latestSupported();
  21. }
  22. @Override
  23. public boolean process( Set <? extends TypeElement> set , RoundEnvironment roundEnv) {
  24. mMessager.printMessage(Diagnostic.Kind.NOTE, "processing..." );
  25. mProxyMap.clear();
  26. //Get all annotations
  27. Set <? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
  28. for (Element element : elements) {
  29. VariableElement variableElement = (VariableElement) element;
  30. TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
  31. String fullClassName = classElement.getQualifiedName().toString();
  32. ClassCreatorProxy proxy = mProxyMap.get(fullClassName);
  33. if (proxy == null ) {
  34. proxy = new ClassCreatorProxy(mElementUtils, classElement);
  35. mProxyMap.put(fullClassName, proxy);
  36. }
  37. BindView bindAnnotation = variableElement.getAnnotation(BindView.class);
  38. int id = bindAnnotation.value();
  39. proxy.putElement(id, variableElement);
  40. }
  41. //Create java files by traversing mProxyMap
  42. for (String key : mProxyMap.keySet()) {
  43. ClassCreatorProxy proxyInfo = mProxyMap.get( key );
  44. try {
  45. mMessager.printMessage(Diagnostic.Kind.NOTE, " --> create " + proxyInfo.getProxyClassFullName());
  46. JavaFileObject jfo = processingEnv.getFiler().createSourceFile(proxyInfo.getProxyClassFullName(), proxyInfo.getTypeElement());
  47. Writer writer = jfo.openWriter();
  48. writer.write(proxyInfo.generateJavaCode());
  49. writer.flush();
  50. writer.close () ;
  51. } catch (IOException e) {
  52. mMessager.printMessage(Diagnostic.Kind.NOTE, " --> create " + proxyInfo.getProxyClassFullName() + "error" );
  53. }
  54. }
  55. mMessager.printMessage(Diagnostic.Kind.NOTE, "process finish ..." );
  56. return   true ;
  57. }
  58. }
  1. public class ClassCreatorProxy {
  2. private String mBindingClassName;
  3. private String mPackageName;
  4. private TypeElement mTypeElement;
  5. private Map< Integer , VariableElement> mVariableElementMap = new HashMap<>();
  6. public ClassCreatorProxy(Elements elementUtils, TypeElement classElement) {
  7. this.mTypeElement = classElement;
  8. PackageElement packageElement = elementUtils.getPackageOf(mTypeElement);
  9. String packageName = packageElement.getQualifiedName().toString();
  10. String className = mTypeElement.getSimpleName().toString();
  11. this.mPackageName = packageName;
  12. this.mBindingClassName = className + "_ViewBinding" ;
  13. }
  14. public void putElement( int id, VariableElement element) {
  15. mVariableElementMap.put(id, element);
  16. }
  17. /**
  18. * Create Java code
  19. * @return  
  20. */
  21. public String generateJavaCode() {
  22. StringBuilder builder = new StringBuilder();
  23. builder.append( "package " ).append(mPackageName).append( ";\n\n" );
  24. builder.append( "import com.example.gavin.apt_library.*;\n" );
  25. builder.append( '\n' );
  26. builder.append( "public class " ).append(mBindingClassName);
  27. builder.append( " {\n" );
  28. generateMethods(builder);
  29. builder.append( '\n' );
  30. builder.append( "}\n" );
  31. return builder.toString();
  32. }
  33. /**
  34. * Add Method
  35. * @param builder
  36. */
  37. private void generateMethods(StringBuilder builder) {
  38. builder.append( "public void bind(" + mTypeElement.getQualifiedName() + " host ) {\n" );
  39. for ( int id : mVariableElementMap.keySet()) {
  40. VariableElement element = mVariableElementMap.get(id);
  41. String name = element.getSimpleName().toString();
  42. String type = element.asType().toString();
  43. builder.append( "host." + name ).append( " = " );
  44. builder.append( "(" + type + ")(((android.app.Activity)host).findViewById( " + id + "));\n" );
  45. }
  46. builder.append( " }\n" );
  47. }
  48. public String getProxyClassFullName()
  49. {
  50. return mPackageName + "." + mBindingClassName;
  51. }
  52. public TypeElement getTypeElement()
  53. {
  54. return mTypeElement;
  55. }
  56. }
  • init: Initialization. You can get ProcessingEnviroment, which provides many useful tool classes Elements, Types and Filer
  • getSupportedAnnotationTypes: Specifies which annotation this annotation processor is registered for. Here it is the annotation BindView
  • getSupportedSourceVersion: specifies the Java version to use, usually returns SourceVersion.latestSupported()
  • process: You can write code here to scan, evaluate, and process annotations and generate Java files
  • Auto-service library: a library required for automatic code generation

4. Write the tool class BindViewTools

Write binding classes in the aptlib project

  1. public class BindViewTools {
  2. public   static void bind(Activity activity) {
  3. Class clazz = activity.getClass();
  4. try {
  5. Class bindViewClass = Class.forName(clazz.getName() + "_ViewBinding" );
  6. Method method = bindViewClass.getMethod( "bind" , activity.getClass());
  7. method.invoke(bindViewClass.newInstance(), activity);
  8. } catch (Exception e) {
  9. e.printStackTrace();
  10. }
  11. }
  12. }

5. Introduced into the main project app

  1. implementation project(path: ':aptlib' )
  2. annotationProcessor project(path: ':aptlib-process' )

In MainActivity, add the BindView annotation in front of View and pass in the id

  1. public class MainActivity extends AppCompatActivity {
  2. @BindView(R.id.tv)
  3. TextView mTextView;
  4. @BindView(R.id.btn)
  5. Button mButton;
  6. @Override
  7. protected void onCreate(Bundle savedInstanceState) {
  8. super.onCreate(savedInstanceState);
  9. setContentView(R.layout.activity_main);
  10. BindViewTools.bind(this);
  11. mTextView.setText( "bind TextView success" );
  12. mButton.setText( "bind Button success" );
  13. }
  14. }

Summarize

1. APT technology is actually custom annotations and annotation processors, which generate Java files during compilation, similar to IOC control inversion, and can be easily decoupled;

2. If you can also implement many different projects, such as routing framework, etc., you will also write some apt projects later.

<<:  Google Play Services will stop supporting the "Jelly Bean" platform

>>:  WeChat macOS version 3.1.6 Beta has been adapted to Apple's M1 chip

Recommend

5 tips to solve 80% of the troubles of bringing goods to Douyin

The method of selling goods through live streamin...

Baidu searched for 18 details of ocpc

Baidu Big Search ocpc was launched in the second ...

Case analysis: Acquisition and maintenance of seed users!

Recently, I was fortunate enough to become one of...

How to identify user pain points? Use these 10 templates!

For every operator , discovering the correlation ...

Can you eat the soil on the moon? What would happen if you ate it?

In June this year, an American auction house is a...

Snapdragon 820 vs. Exynos 8890: Which one is better?

Preface: Apple A9 and HiSilicon 950 are not within...

3 factors that affect product promotion and conversion (Part 2)

Many adjustments seem simple, but the leverage ef...

China Mobile to open 4G to virtual operators

[[121346]] Xu Gang, vice president of China Mobil...