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

The growth caused by one sentence of copywriting and the logical code behind it

Sometimes a piece of copy can achieve unexpected ...

Android丨Detailed explanation of ASO in Huawei Market

Today we are going to talk about how the new pack...

NULL and nullptr and nil and Nil and NSNull

[[129295]] The declarations of NULL and nullptr c...

The 10 most disappointing founders of 2015

[[161252]] Editor's note: In this article, St...

Lichens, mosses, these "tiny miracles" of nature - how to distinguish them?

Some people with professional knowledge feel that...

How to leverage the Apple Store to promote your own App?

In addition to delivering a good product, how can...

No creativity in video promotion? This article teaches you!

Since the second quarter of 2018, the education i...

How to get millions of traffic for free in 30 days?

Let me first tell you who this article is suitabl...

Why is criticism more memorable than praise?

© Frederic Cirou/Getty Images Leviathan Press: Re...