Why write this series of blogs?
Because in Android development, generics, reflection, annotations come into play, and almost every framework uses at least one or two of them. Gson uses generics, Reflection, annotations, Retrofit uses generics, reflection, annotations. Learning these knowledge is very important for us to advance, especially reading the source code of open source framework or developing open source framework.
Java Type,
Java reflection mechanism in detail
Introduction to Annotations (PART 1)
Android custom compile-time annotations 1 – a simple example
Android compile-time annotations – syntax details
Take you through the source code for ButterKnife
After the previous introduction, I believe you have a certain understanding of annotations.
Annotations can be divided into three categories based on how they are used and what they are for:
- JDK built-in system annotations (e.g. @SuppressWarnings(“deprecation”), @Override, etc.)
- Documented (@documented, @Retention(), @target (), @documented)
- Custom annotations (self-implemented annotations)
Yuan notes
Meta-annotation parsing description
-
Documented Whether @documented will be saved to a Javadoc document
-
@retention Retention time (optional). The default value is CLASS
SOURCE (SOURCE time), CLASS (compile time), RUNTIME (RUNTIME)
-
The @target can be used to modify program elements such as TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER, etc
ANONOTATION_TYPE(annotation TYPE declaration), PACKAGE (PACKAGE) TYPE(class, including enum and interface, CONSTRUCTOR FIFLD (member variable) PARAMATER LOCAL_VARIABLECopy the code
- @Inherited Whether Inherited can be Inherited. The default value is false
Notice that annotations are not Inherited, @Inherited means Inherited when we apply annotations to class A, class B inherits A, so B can scan the annotations of A.
Inheritance of annotations “(dependency inversion?)
I’m not talking about inheritance through @Inherited annotations.
This “inheritance” is an annotation technique that feels like dependency inversion and comes from the ButterKnife source code.
Let’s look at the code.
@Target(METHOD)
@Retention(CLASS)
@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. */
int[] value() default { View.NO_ID };
}
Copy the code
This is the OnClick annotation for ButterKnife. The special thing is that @onclick modifies the @listenerClass annotation and sets some properties that belong only to @onclick.
So what does that do?
Anything that decorates @onclick automatically decorates @listenerClass. Like @onclick is a subclass of @listenerClass. ButterKnife has many listener annotations @onItemClick, @onLongclick, and so on.
So when you do code generation, you don’t need to think about each listener annotation individually, you just need to deal with @ListenerClass. Like @interface OnItemClick.
@Target(METHOD)
@Retention(CLASS)
@ListenerClass(
targetType = "android.widget.AdapterView
",
setter = "setOnItemClickListener".type = "android.widget.AdapterView.OnItemClickListener",
method = @ListenerMethod(
name = "onItemClick",
parameters = {
"android.widget.AdapterView
"."android.view.View"."int"."long"
}
)
)
public @interface OnItemClick {
/** View IDs to which the method will be bound. */
@IdRes int[] value() default { View.NO_ID };
}
Copy the code
Custom annotations
A simple example of custom annotations
@documented () // indicates @retention (retentionPolicy.class) based on compile-time annotations @target ({elementtype. FIELD, elementtype. TYPE}) public @interface Seriable {}Copy the code
Specify default values
@documented () // indicates @retention (retentionPolicy.class) based on compile-time annotations @target ({elementtype.field, elementtype.type}) public @interface Seriable {int id(); String name() default"test"; } // use @seriable (id = 1) //name has default value can not write class Test{}Copy the code
For information on how to customize an annotation, see this blog post, Android Custom Compile-time Annotations 1 – a simple example
Processor class Processor
After you customize the annotations, you need to write the Processor class to handle the annotations. Processor A class derived from AbstractProcessor. We mainly need to deal with a series of methods.
-
public Set getSupportedAnnotationTypes()
-
public abstract boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv);
GetSupportedAnnotationTypes method
Rewrite getSupportedAnnotationTypes methods: tell the Processor which annotations to deal with. Returns a Set containing the package name + class name of the custom annotation.
The proposed project is written as follows:
@Override
public Set<String> getSupportedAnnotationTypes() { Set<String> types = new LinkedHashSet<>(); / / need full name of the class types. The add (Seriable. Class. GetCanonicalName ()); types.add(Println.class.getCanonicalName());return types;
}
Copy the code
Alternatively, if the number of annotations is small, this can be done in another way:
@supportedannotationTypes ({SupportedAnnotationTypes({SupportedAnnotationTypes));"com.example.Seriable"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class JsonProcessor extends AbstractProcessor {
}
Copy the code
The process method
The process method, the most critical of all, is used to process information about annotations, such as extracting annotation information, storing it in a map collection, or generating code.
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return false;
}
Copy the code
public boolean process(Set<? } // Find elememts (extends TypeElement> Annotations, RoundEnvironment roundEnv)setSet the Set <? extends Element> elememts = roundEnv.getElementsAnnotatedWith(Seriable.class); TypeElementtypeElement; VariableElement variableElement; // Step 2: do the corresponding processing according to the type of element and save it into the map collectionfor(Element element : elememts) { ElementKind kind = element.getKind(); // Determine if the element is a classif (kind == ElementKind.CLASS) {
typeElement = (TypeElement) element; // Determine if the element is a member variable}else if(kind == ElementKind.FIELD) { variableElement = (VariableElement) element; }}return true;
}
Copy the code
Element
Element, although any Element that is annotated is waiting to be processed as Element, its specific type is related to what we marked with @target.
Here’s the official explanation:
Represents a program element such as a package, class, or method.Each element represents a static, language-level construct (and not, for example, a runtime construct of the virtual machine)
Represents program elements such as packages, classes, or methods. Each element represents a static language-level construct (for example, not a virtual machine built at runtime)
Such as:
// Find elememts according to our custom annotationssetSet the Set <? extends Element> elememts = roundEnv.getElementsAnnotatedWith(Seriable.class); TypeElementtypeElement; VariableElement variableElement; // Step 2: do the corresponding processing according to the type of element and save it into the map collectionfor(Element element : elememts) { ElementKind kind = element.getKind(); // Determine if the element is a classif (kind == ElementKind.CLASS) {
typeElement = (TypeElement) element; // Determine if the element is a member variable}else if(kind == ElementKind.FIELD) { variableElement = (VariableElement) element; }}Copy the code
Element of the subclass
The subclasses of Element are:
- ExecutableElement
A method, constructor, or initializer (static or instance) that represents a class or interface, including annotation type elements.
Corresponding @ Target (ElementType METHOD) @ Target (ElementType. CONSTRUCTOR)
- PackageElement;
Represents a package element. Provides access to information about packages and their members.
Corresponding @ Target (ElementType. PACKAGE)
- TypeElement;
Represents a class or interface program element. Provides access to information about types and members.
Corresponding @ Target (ElementType TYPE)
Note: An enumeration type is a class, while an annotation type is an interface.
- TypeParameterElement;
A type parameter representing a generic class, interface, method, or constructor element.
Corresponding @ Target (ElementType PARAMETER)
- VariableElement;
Represents a field, enum constant, method or constructor parameter, local variable, or exception parameter.
Corresponding @ Target (ElementType LOCAL_VARIABLE)
Determine which subclass Element belongs to
We can do this with element.getkind (); This method returns the ElementKind type, which is used to determine the category of an Element.
ElementKind kind = element.getKind(); // Determine if the element is a classif (kind == ElementKind.CLASS) {
typeElement = (TypeElement) element;
} else if (kind == ElementKind.FIELD) {
variableElement = (VariableElement) element;
}
Copy the code
We need to pay attention to the difference between Type and Element.
Type is for generics and Element is for annotations.
digression
The annotation syntax is here for now, and will be updated to this blog as new ideas come to mind. In the next post, I will analyze the ButterKnife source code.
Related blog recommendation
Java Type,
Java reflection mechanism in detail
Introduction to Annotations (PART 1)
Android custom compile-time annotations 1 – a simple example
Android compile-time annotations – syntax details
Take you through the source code for ButterKnife
Welcome to follow my wechat public account Stormjun94. Currently, I am a programmer. I not only share relevant knowledge of Android development, but also share the growth process of technical people, including personal summary, workplace experience, interview experience, etc., hoping to make you take a little detours.