What are annotations

Annotations are a type of metadata that can be added to Java code. Classes, methods, variables, parameters, and packages can all be decorated with annotations. Annotations have no direct effect on the code they modify.

1. What are meta-annotations

The annotation class used to annotate annotation types is called meta-annotations. Four standard meta-annotations are provided in JDK1.5.

@Target: Identifies the scope of an annotation and the scope of objects that the annotated annotation class can modify. @Retention: Identifies the Retention period of the annotation and how long the annotated annotation can remain in the class it modifies. Describes whether to preserve annotation information when using the Javadoc tool to generate help documents for a class

2. The value and meaning of the @target meta-annotation

@Target({ElementType.FIELD, ElementType.METHOD})
public @interface Event {
}
Copy the code

@target describes the scope of the annotation and carries an enumeration to indicate where the annotation it modifies can be used. For example, @Event in the above example only works on properties and methods

The values and meanings of ElementType are as follows:

Public enum ElementType {// for class TYPE, // for attribute FIELD, // for METHOD, // for PARAMETER, // for CONSTRUCTOR, // LOCAL_VARIABLE, // ANNOTATION_TYPE, // PACKAGE, private ElementType(){ }}Copy the code

Note: Each annotation can be associated with n elementTypes. When not specified, annotations can be used anywhere.

3. The value and meaning of meta annotation @Retention

@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Event {
}
Copy the code

@Retention describes the Retention of annotations. In the example above, @Event is a runtime annotation that exists in source code, bytecode, and runtime

The values and meanings of RetentionPolicy are as follows:

Public enum RetentionPolicy {// public enum RetentionPolicy {// public enum RetentionPolicy {// Public enum RetentionPolicy {// Public enum RetentionPolicy {// Public enum RetentionPolicy; RUNTIME exists at all times; private RetentionPolicy(){... }}Copy the code

Note: Each annotation can be associated with only one RetentionPolicy. If no value is specified, the default value is retentionPolicy.class

4. Introduction of other meta-notes

Documented: The Annotation of a class and method does not appear in Javadoc by default. If you qualify this annotation with @documented, it indicates that it can appear in Javadoc.

@inheried: When the class using the annotation has a subclass, the annotation still exists in the subclass. Get the same annotation as the parent class by reflecting its subclass

5. Customize the parameters of the annotations

public @interface Person { public String name(); // Default value int age() default 18; int[] array(); }Copy the code

The types of arguments annotations can carry are: basic data types, String, Class, Annotation, and enum

Use of annotations

Some of the more common uses of annotations are

A. Dynamic check during compilation. For example, the value of a parameter can only be certain int values, such as color. You can use compile-time annotations to check the parameters at compile time

B. Generate code dynamically at compile time, using an annotation processor to generate class files at compile time. Such as ButterKnife implementation

C. Dynamic injection at runtime, implementing IOC with annotations. Many frameworks change the original configuration file XML into annotations using IOC injection.

Compile-time annotations -Apt annotations used by the processor

The following is a case study. Example goal: Implement annotation-bound controls with the following effect

public class MainActivity extends AppCompatActivity { @BindView(R.id.tv) public TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); Toast.makeText(this, tv.getText().toString(), Toast.LENGTH_SHORT).show(); }}Copy the code

1. Engineering structure

  1. App module, an Android module, is the main module of the Demo, the content is the demo part MainActivity
  2. The Annotation module, a Java Library module, is used to place annotations
  3. The compile module, a Java Library module, is where the annotation processor is mainly implemented
  4. The Library module, an Android Library module, implements annotation-bound controls with code generated by the annotation processor

The content of APP module is consistent with the above goals, and I believe it can be understood. The other modules are described one by one

2. Annotation module

The annotation module holds the annotation, which in this case is @bindView. This annotation is compile-time code (@Retention(retentionPolicy.class); Because it is applied to attributes, the annotation targets @target (elementType.field); And @bindView has an argument representing the control ID of type int. This leads to the following annotation statement

@target (elementtype.field) @retention (retentionPolicy.class) public @interface BindView { Control id int value(); }Copy the code

3. Compile module

Build. Gradle: build. Gradle: build. Gradle: build

apply plugin: 'java-library' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) // Annotation processor library compileOnly 'com. Google. Auto. Services: auto - service: 1.0 - rc6' annotationProcessor 'com. Google. Auto. Services: auto - service: 1.0 - rc6' / / javapoet used to generate the Java class implementation 'com. Squareup: javapoet: 1.10.0' implementation project(':annotation') } sourceCompatibility = "7" targetCompatibility = "7"Copy the code

Annotation:

Step 1: Scan the annotated properties and their corresponding activities in the code and store them in the map

Java1.7 @supportedSourceversion (SourceVersion.release_7) / / declare annotation processor support annotation @ SupportedAnnotationTypes (" com. Sq. An annotation. BindView ") public class ButterKnifeProcessor extends AbstractProcessor {private Messager mMessager; Private Map<TypeElement, List<VariableElement>> mTargetMap; @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); mMessager = processingEnvironment.getMessager(); } @Override public boolean process(Set<? extends TypeElement> set, Set<Element> views = (Set<Element>) roundEnvironment.getElementsAnnotatedWith(BindView.class); if (views ! MTargetMap = new HashMap<>(); mTargetMap = new HashMap<>(); for (Element view : Views) {if (view instanceof VariableElement) {// Activity TypeElement activityElement = (TypeElement) View.getenClosingElement (); if (mTargetMap.get(activityElement) == null) { ArrayList targetList = new ArrayList<VariableElement>(); targetList.add(view); mTargetMap.put(activityElement, targetList); } else { mTargetMap.get(activityElement).add((VariableElement) view); If (mtargetmap.size () > 0) {for (map.entry <TypeElement, List<VariableElement>> Entry: mTargetMap.entrySet()) { String activityName = entry.getKey().getSimpleName().toString(); PrintMessage (diagno.kind. NOTE," Activity class name: "+ activityName); For (VariableElement View: entry.getValue()) {mmessager.printMessage (diagno.kind.note, "Annotated attributes are: " + view.getSimpleName().toString()); } // generateCode generateCode(entry.getkey (), entry.getvalue ()) for each activity; } } } return false; }}Copy the code

Note: The activity class name is MainActivity. Note: The annotated property is TV

Step 2: Generate code Because you are binding controls to your activity, generate code like this:

public class MainActivity$ViewBinder implements ViewBinder<MainActivity> {
  @Override
  public void bind(final MainActivity target) {
    target.tv = target.findViewById(2131165359);
  }
}
Copy the code

ViewBinder is the interface whose code is stored in the Library module as follows:

public interface ViewBinder<T> {
    void bind(T target);
}
Copy the code

Before generating code, it is often necessary to first understand what the generated code will look like, and to have a template before writing the generated logic.

Let’s start with the generateCode() method content

private void generateCode(TypeElement activityElement, List<VariableElement> viewElements) {// used to get the representation of the activity ClassName in javapoet ClassName ClassName = ClassName.get(activityElement); / / the generated class implements the interface TypeElement viewBinderType = mElementUtils. GetTypeElement (" com. Sq. Library. ViewBinder "); / / implementation of the interface in the javapoet said ParameterizedTypeName typeName = ParameterizedTypeName. Get (ClassName. Get (viewBinderType), className); ParameterSpec = parameterSpec. builder(className, "target", Modifier.FINAL).build(); // Method declaration: public void bind(final MainActivity target) MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind") .addAnnotation(Override.class) .addModifiers(Modifier.PUBLIC) .addParameter(parameterSpec); // For (VariableElement viewElement: ViewElements) {/ / get the property name String fieldName. = viewElement getSimpleName (), toString (); / / get the @ BindView comments the value of the int annotationValue = viewElement. GetAnnotation (BindView. Class). The value (); //target.tv = target.findViewById(R.id.tv); String methodContent = "$N." + fieldName + " = $N.findViewById($L)"; / / add method content methodBuilder. AddStatement (methodContent, "target", "target", annotationValue); } try {javafile.builder (classname.packagename (), TypeSpec.classBuilder(className.simpleName() + "$ViewBinder") .addSuperinterface(typeName) .addModifiers(Modifier.PUBLIC) .addMethod(methodBuilder.build()) .build()) .build() .writeTo(mFiler); } catch (IOException e) { e.printStackTrace(); }}Copy the code

4. Library module

The function of the library module is to provide the app module with the code generated by the Compile module. ViewBinder interface implemented by MainActivity butterknife. bind(this) and MainActivity&MainActivityViewBinder generated by compile module The code for the ButterKnife class is as follows:

Public class ButterKnife {public static void bind(Activity Activity) {try {// Find the Activity ViewBinder, Call the bind method and pass activity as an argument to Class viewBinderClass = class.forname (activity.getClass().getName() + "$ViewBinder"); ViewBinder viewBinder = (ViewBinder) viewBinderClass.newInstance(); viewBinder.bind(activity); } catch (Exception e) { e.printStackTrace(); }}}Copy the code

5. Compile module is used for APP module

Build. gradle is configured as follows:

apply plugin: 'com.android.application' android {compileSdkVersion 29 buildToolsVersion "29.0.2" defaultConfig {applicationId "Com.sq. aptdemo" minSdkVersion 19 targetSdkVersion 29 versionCode 1 versionName "1.0"} buildTypes {release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } dependencies { implementation fileTree(dir: 'libs', include: [' *. Jar ']) implementation 'androidx. Appcompat: appcompat: 1.1.0' implementation 'androidx. Constraintlayout: constraintlayout: 1.1.3' implementation project (' ': an annotation) implementation Project (':library') // annotationProcessor project(":compile")}Copy the code

How to trigger?Make Module ‘app’ triggers compilation, causing the compile Module to start executing.

View the generated code:After running the APP module, it runs normally and the control is bound to the ID successfully.

At this point, use APT annotation processor to generate code to complete the development of control injection.

2. Runtime annotations for control injection

The objectives of the case are as follows:

public class MainActivity extends AppCompatActivity { @BindView(R.id.tv) TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Injectutils.bind (this); Toast.makeText(this, tv.getText().toString(), Toast.LENGTH_SHORT).show(); }}Copy the code

As you can see, there is no difference between generating code using and using compile-time annotations, except that the property TextView can be a private member. Because injection is implemented using reflection.

1. Engineering structure

Fewer compile modules than using compile-time annotations. Here are some of them.

2. Annotation module

Annotation module content is still to store annotations, here to demonstrate, using only a BindView annotation

@target (elementtype.field) @retention (retentionPolicy.runtime) public @interface BindView {int value(); }Copy the code

3. Library module

Public class InjectUtils {public static void bind(Activity target) {// Get the Activity's class activityClass = target.getClass(); / / get all attributes to the activity Field [] fields. = activityClass getDeclaredFields (); if (fields ! For (Field Field: fields) {field.setaccessible (true); BindView annotation = field.getAnnotation(BindView.class); if (annotation ! Int id = annotation. Value (); View targetView = target.findViewById(id); Try {// Set the property to the corresponding view, complete binding field. Set (target, targetView); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } } }Copy the code

At this point, control injection is complete. You can see that it is actually relatively easy to implement control injection with runtime annotations, but it is less efficient because it uses reflection.