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

Douyin Promotion: The most comprehensive guide to increasing Douyin followers

As the holy land of short video traffic, Douyin i...

OLED: Screen burn-in is not dead, but large screens are hard to come by

The rise of OLED technology has brought new break...

Coke addicts: Dopamine makes you addicted, sugar makes you old

Fried chicken and cola, the more you eat, the hap...

Basic numerology course

Brief introduction of Bazi basic numerology cours...

Don't buy these 3 kinds of toothpaste, or they may cause cancer

Expert of this article: Huang Lu, Director of Hel...

What do the two world records of 64-bit and 7.5mm Skyworth GLED Air mean?

January 2 (Reporter Zhang Xiaodong) 64-bit is a n...

Dapeng Education-AI Case Improvement Course

Dapeng Education-AI Case Enhancement Course Resou...

95% of entrepreneurs, CEOs and CPs don’t know about SEM promotion

"For any company, SEO and SEM will be import...

Four marketing techniques for jewelry industry on Xiaohongshu

Some time ago, a friend of mine on Xiaohongshu to...