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
- App module, an Android module, is the main module of the Demo, the content is the demo part MainActivity
- The Annotation module, a Java Library module, is used to place annotations
- The compile module, a Java Library module, is where the annotation processor is mainly implemented
- 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.