Understanding and Use of Android Annotations

Keywords: Mobile Java Android ButterKnife Google

Why annotations

As an Android developer, first look at some familiar code:

setContentView(R.layout.activity_main);

Toolbar toolbar = findViewById(R.id.toolbar);

new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.xxx, parent, false)));

Non-ellipsible code like this, which appears in large numbers everywhere, is copied over and over again, with little or no change during the copy process.It is called boilerplate code in computer programming.Similar templates are often used, and the direct consequence is that programmers write more code and do less work.This is obviously intolerable.

from Refactoring Improves Design of Existing Code Chapter 3: Bad taste of code In, we have learnt that programs become better when multiple identical program structures are combined into one.The most intuitive way is to use a tool class to extract common method calls everywhere.This allows less code and more efficiency.Similarly, use annotations to solve boilerplate code problems in Android.

The purpose of the quotation above is to intuitively explain why annotations are needed?The terminology is as follows:

>Whenever you create descriptor-like classes and interfaces, once they contain repetitive work, you can consider simplifying and automating the process.

>If you want to set many constants or parameters for your application, xml is a good choice because it does not connect to specific code.If you want to declare a method as a service, it's better to use Annotation because annotations and methods need to be tightly coupled and developers must be aware of this.

Annotate how it works

Example:

package java.lang;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

This is the source code for JDK, so you can say that there is almost nothing in it. Usually when we override parent or interface methods, using @Override, the compiler knows to call our overridden methods.This means that the compiler handles the program in some places according to this comment.

Let's look at the definition of the note:

>The metadata of the source code, which is the data describing the data.

You can see that annotations are used to describe data and, based on our inference on @Override, there should also be code that handles annotated classes, methods, variables, and parameters.

So when you customize annotations, you define them and implement corresponding business logic based on them.

annotation type

See the implementation of the annotation class java.lang.Override.You can see that there are also comments on the custom annotations, which are called meta-annotations.

meta annotation

Documented

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE})
public @interface Documented {
}

No value, simple tag comment to identify whether the comment information is included in the java document

Retention

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE})
public @interface Retention {
  RetentionPolicy value();
}
public enum RetentionPolicy {
  SOURCE,
  CLASS,
  RUNTIME;

  private RetentionPolicy() {
  }
}

Identify when the note is used and the life cycle of the note.There are three values: SOURCE,CLASS,RUNTIME

  • RetentionPolicy.SOURCE: Validity period is in source phase, discarded during compilation phase, and these comments will not make any sense or be written to byte code after compilation.
  • RetentionPolicy.CLASS: Valid until byte code file.Discarded when the class loads, which is the default for annotations.
  • RetentionPolicy.RUNTIME: Always valid and will remain at runtime.Reflection can therefore be used to read the information for this annotation, which is often used by custom annotations.

Target

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE})
public @interface Target {
  ElementType[] value();
}
public enum ElementType {
  TYPE,
  FIELD,
  METHOD,
  PARAMETER,
  CONSTRUCTOR,
  LOCAL_VARIABLE,
  ANNOTATION_TYPE,
  PACKAGE,
  TYPE_PARAMETER,
  TYPE_USE;

  private ElementType() {
  }
}

Indicates where the comment is used.If not specified, the comment can be placed anywhere.

  • ElementType.TYPE: Used to describe classes, interfaces (including annotation types), Enum
  • ElementType.FIELD: Used to describe instance variables (including constants for enumerations)
  • ElementType.METHOD: Used to describe methods
  • ElementType.PARAMETER: Used to describe parameters
  • ElementType.CONSTRUCTOR: Used for construction methods
  • ElementType.LOCAL_VARIABLE: Used to describe local variables
  • ElementType.ANNOTATION_TYPE: Used to describe annotations
  • ElementType.PACKAGE: Used to describe packages
  • ElementType.TYPE_PARAMETER:since 1.8 indicates that the comment can be written in a declaration statement for a type variable
  • ElementType.TYPE_USE:since 1.8 indicates that the comment can be written in any statement of use type

Inherited

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

Indicates that the annotation type is automatically inherited. If the user queries the current class for this meta-annotation type, but the declaration of the current class does not contain this meta-annotation type, the parent class is automatically queried until the annotation is found or the top class is reached.

Built-in Notes

@Override

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
  • Applicable to Method
  • Keep in Source Stage Only
  • Represents an override of a superclass method, and if a method with this tag does not appear in the superclass, the compiler will fail

@Deprecated

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.METHOD, ElementType.PACKAGE, ElementType.PARAMETER, ElementType.TYPE})
public @interface Deprecated {
}
  • Annotation information is contained in a java document
  • For all places
  • Always valid
  • Represents an outdated api

@SuppressWarnings

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR, ElementType.LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
  String[] value();
}
  • For all places
  • Keep in Source Stage Only
  • Indicates suppression warning

Custom Notes

with butterknife take as an example

annotations

@Target(METHOD)
@Retention(RUNTIME)
@ListenerClass(
    targetType = "android.view.View",
    setter = "setOnClickListener",
    type = "butterknife.internal.DebouncingOnClickListener",
    method = @ListenerMethod(
        name = "doClick",
        parameters = "android.view.View"
    )
)
public @interface OnClick {
  /** View IDs to which the method will be bound. */
  @IdRes int[] value() default { View.NO_ID };
}
  • Applicable to Method
  • Always valid
  • Custom meta-annotation identifying the api scope used by the annotation

Custom APT

Annotation Processor Tool Here we can use reflection for our own implementation or inherit javax.annotation.processing.AbstractProcessor

public class MyProcessor extends AbstractProcessor {
 
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
    }
 
    @Override
    public Set<string> getSupportedAnnotationTypes() {
        return null;
    }
 
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
 
    @Override
    public boolean process(Set<!--? extends TypeElement--> annotations, RoundEnvironment roundEnv) {
        return true;
    }
}

getSupportedAnnotationTypes: What annotations are processed by the processor.

butterknife implementation:

 @Override public Set<string> getSupportedAnnotationTypes() {
    Set<string> types = new LinkedHashSet&lt;&gt;();
    for (Class<!--? extends Annotation--> annotation : getSupportedAnnotations()) {
      types.add(annotation.getCanonicalName());
    }
    return types;
  }

  private Set<class<? extends annotation>&gt; getSupportedAnnotations() {
    Set<class<? extends annotation>&gt; annotations = new LinkedHashSet&lt;&gt;();

    annotations.add(BindAnim.class);
    annotations.add(BindArray.class);
    annotations.add(BindBitmap.class);
    annotations.add(BindBool.class);
    annotations.add(BindColor.class);
    annotations.add(BindDimen.class);
    annotations.add(BindDrawable.class);
    annotations.add(BindFloat.class);
    annotations.add(BindFont.class);
    annotations.add(BindInt.class);
    annotations.add(BindString.class);
    annotations.add(BindView.class);
    annotations.add(BindViews.class);
    annotations.addAll(LISTENERS);

    return annotations;
  }

getSupportedSourceVersion: A supported version of java that generally does not need to be overridden.

Process (Set<!!--? Extends TypeElement--> annotations, RoundEnvironment roundEnv): Scan and process annotations and generate Java code.Code generation java source is painful and uses javapoet

butterknife's BindView implementation:

 // Process each @BindView element.
    for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
      // we don't SuperficialValidation.validateElement(element)
      // so that an unresolved View type can be generated by later processing rounds
      try {
        parseBindView(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindView.class, e);
      }
    }

Register with javac

@AutoService(Processor.class) 
public class MyProcessor extends AbstractProcessor { ... }
implementation 'com.google.auto.service:auto-service:1.0-rc5'

Register a custom annotation processor with javac, and then javac will invoke our custom processing method at compile time.

call

General method:

submit.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        // TODO ...
    }
})

lamda:

submit.setOnClickListener(v -&gt; {
    // TODO ...
})

butterknife:

@OnClick(R.id.submit) void submit() {
    // TODO ...
  }

Brain Map

actual combat

There are many examples of AnnotationProcessorTool on the Web, especially copying butterknife.Let's tap on an example to make a better impression.

Prerequisites:

  1. Annotations without corresponding processors will have no effect
  2. Annotation processor generates java source instead of byte code
  3. It would be better if you read "Understanding the java Virtual Machine Part 4: Program Compilation and Code Optimization". It won't hurt if you haven't read it yet
  4. Using the AutooService library from JavaPoet and google

Possible problems

Android Studio Enables Annotation Process Tool

On my Adnroid version 3.4, you need to set it up to be enabled as follows:

File -> Close Projects enter:

Find Settings in the Project Defaults hierarchy in Configure, as shown in the following figure:

Check Enable annotation processing.

@AutoService(Processor.class) did not generate source code

We can either write our own or run the source code on the web. If the build is successful, the.java file specified in the annotation processor will appear under the directory. \app\build\generatedsource\apt\debug.It is also possible to build without errors, but generate nothing.

There are generally two ways to register an annotation processor with javac for a general online tutorial.Handwritten javax.annotation.processing.Processor file and use the com.google.auto.service:auto-service:1.0-rc5 library.

No source code was generated, that is, javac did not execute the logic in our annotation processor.

In my practice, I decided that I had enabled the annotation processor and used the com.google.auto.service:auto-service:1.0-rc5 library to generate no source code. Handwritten files were generated successfully, which took me a morning to find out the reason. It was not worth it, interrupted the continuous learning, and hit me hard.

Javapoet

This is a useful library for generating Java source files. It is recommended that you follow the online tutorials for about an hour, tap on examples to improve your intuition, and then use them smoothly.

Debugging APT

This self-search, remote debugging combined with Android Studio's fast build always produces Connection refused which makes people feel uncomfortable. Every time I debug, I have to either Clean or change the code or Invalidate Caches, which makes me miserable, so I actually look directly at the resulting java file. </https:></class<?></class<?></string></string></string></https:></https:>

Posted by majik92 on Thu, 19 Sep 2019 18:51:04 -0700