当前位置:网站首页>Basic chapter: take you through notes

Basic chapter: take you through notes

2022-07-07 09:49:00 hudawei996

It says Android Development : From modularization to componentization ( One ), Many friends came to ask why there was no Demo ah ? The reason why I didn't put it immediately demo The reason is that there are many technical points left unfinished .

Today, let's review the flavor carefully Java among Annotation, That is what we often call annotation .

This paper is carried out in the following order : Metadata -> Yuan notes -> Runtime annotations -> Compile time annotation processor .


  • What is metadata (metadata)

Metadata from metadata Translated , The so-called metadata is “ Data about data ”, More commonly, it's data that describes data , Descriptive information about data and information resources . Like a text file , There's a creation time , founder , File size and other data , This can be understood as metadata .

stay java in , Metadata exists in the form of tags java In the code , Its existence does not affect the compilation and execution of program code , Usually, it is used to generate other files or the runtime knows the description information of the code to be run .java In the middle of javadoc and annotation All belong to metadata .


  • 1. What is annotation (Annotation)?

The annotation is from java 5.0 Start to join , Can be used to label packages , class , Method , Variable etc. . Like the ones that we see all the time @Override, Or Android In the source @hide,@systemApi,@privateApi etc.

about @Override, Most people know it, but they don't know why , Today I'm going to talk about Annotation The secret behind , To the body .

  • 1.1. Yuan notes

Meta annotations are definitions Notes to notes , yes java Provides us with the basic annotations for defining annotations . stay java.lang.annotation In the package, we can see that there are several meta annotations at present :

@Retention
@Target
@Inherited
@Documented
@interface

Next we'll gather @Override Notes to explain 5 The use of basic annotations .

  • 1.1.1. @interface

@interface yes java Keywords used to declare annotation classes in . Use this annotation to indicate that it will automatically inherit java.lang.annotation.Annotation class , The process is left to the compiler .

So if we want to define an annotation, we just need to do the following , With @Override Note for example

public @interface Override {
}

We need to pay attention to : When defining annotations , Cannot inherit other annotations or interfaces .

  • 1.1.2. @Retention

@Retention: This annotation is used to define the annotation retention policy , When does the defined annotation class exist ( Source phase or After compiling or Operation phase ). The annotation accepts the following parameters :RetentionPolicy.SOURCE,RetentionPolicy.CLASS,RetentionPolicy.RUNTIME, Its specific use and meaning are as follows :

Annotation retention policy meaning
@Retention(RetentionPolicy.SOURCE)  Annotations are reserved only in the source code ,class There is no... In the file
@Retention(RetentionPolicy.CLASS)  Annotations in source and class It's all in the file , But the runtime does not exist , That is, the runtime cannot get , This policy is also the default retention policy
@Retention(RetentionPolicy.RUNTIME)  Annotation in source code ,class It exists in the file and can be obtained by reflection mechanism at runtime

Take a look at @Override Retention policy for annotations :

@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

This shows that @Override Annotations exist only in the source phase ,javac Remove the annotation during compilation .

  • 1.1.3. @Target

This annotation is used to define the purpose of the annotation , Where can annotations be used , For example, it is used for methods or fields , The annotation accepts the following parameters :

Target of action meaning
@Target(ElementType.TYPE) For interfaces ( Annotations are essentially interfaces ), class , enumeration
@Target(ElementType.FIELD) For fields , Enumeration constants
@Target(ElementType.METHOD) For method
@Target(ElementType.PARAMETER) Used for method parameters
@Target(ElementType.CONSTRUCTOR) Used to construct parameters
@Target(ElementType.LOCAL_VARIABLE) For local variables
@Target(ElementType.ANNOTATION_TYPE) Used for annotations
@Target(ElementType.PACKAGE) For package

With @Override For example , It is not difficult to see that its goal is method :

@Target(ElementType.METHOD)
public @interface Override {
}

Up to now , adopt @interface,@Retention,@Target You can define an annotation completely , Look at @Override Complete definition :

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
  • 1.1.4. @Inherited

By default , Our custom annotation used on the parent class will not be inherited by the child class . If you want the subclass to inherit the comments of the parent , That is, the annotation also works in subclasses , You need to set... When you customize annotations @Inherited. Generally, this annotation is less used .

  • 1.1.5. @Documented

This annotation is used to describe other types of annotation Should be javadoc Documentation , Appear in the api doc in .
For example, using this annotation @Target It will appear in api In the description .

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

    ElementType[] value();
}

 Picture description here

With the help of @Interface,@Target,@Retention,@Inherited,@Documented These five meta annotations , We can customize the annotation , The first three annotations are necessary for any annotation .

Do you think the following will be directly about how to customize annotations ? No , You are wrong , Let's talk java A few notes that come with you .


  • 2. System notes

java The designer has customized several commonly used annotations for us , We call it system annotation , Mainly these three :

System notes meaning
@Override Used to decorate methods , Indicates that this method overrides the parent method
@Deprecated Used to decorate methods , Indicates that this method is outdated
@SuppressWarnnings This annotation is used to tell the compiler to ignore certain compilation warnings

If you already fully know the purpose of these three , Skip this section , Look straight down .

  • 2.1. @Override

It is used as a dimensioning method , Note that the marked method overrides the method of the parent class , Its function is similar to assertion . If you use this annotation on a method that does not override the parent method ,java The compiler will prompt with a compilation error :

 Picture description here

  • 2.2. @Deprecated

When a type or member uses this annotation, it means
The compiler does not recommend that developers use marked elements . in addition , This annotation has ” Transitivity ”, Methods that override the annotation tag in subclasses , Although the method in the subclass does not use the annotation , But the compiler still alarms .

public class SimpleCalculator {

    @Deprecated
    public int add(int x, int y) {
        return x+y;
    }
}

public class MultiplCalculator extends SimpleCalculator {
    //  rewrite SimpleCalculator Medium method , But don't use @Deprecated
    public int add(int x, int y) {
        return  Math.abs(x)+Math.abs(y);
    }
}

//test code
public class Main {

    public static void main(String[] args) {
        new SimpleCalculator().add(3, 4);
        new MultiplCalculator().add(3,5);
    }
}

  For things like new SimpleCalculator().add(3,4) This kind of direct call ,Idea Will prompt directly , The second kind is not a direct prompt :

 Picture description here

But during compilation , The compiler will warn :

 Picture description here   We need to pay attention to @Deprecated and @deprecated The difference between the two , The former is javac Identify and deal with , The latter is by javadoc Tool identification and handling . Therefore, when we need to mark a method in the source code as obsolete, we should use @Deprecated, If it needs to be explained in the document, use @deprecated, So it can be so :

public class SimpleCalculator {
    /**
     * @param x
     * @param y
     * @return
     * 
     * @deprecated deprecated As of version 1.1,
     * replace by <code>SimpleCalculator.add(double x,double y)</code>
     */
    @Deprecated
    public int add(int x, int y) {
        return x+y;
    }

    public double add(double x,double y) {
        return x+y;
    }

}
  • 2.3. @SuppressWarnning

Annotations are used to selectively close compiler pair classes , Method , Member variables are warnings about variable initialization . This annotation accepts the following parameters : 

Parameters meaning
deprecated Use obsolete classes , Method , Variable
unchecked Warnings when unchecked notifications are executed , For example, using a collection is to use generics to specify the type of collection when it is saved
fallthrough Use switch, But no break when
path Class path , There is a nonexistent path in the source file path
serial Missing on serializable class serialVersionUID Warning when defining
finally whatever finally Warning when words cannot be completed normally
all Warnings for all of the above

  • 3. Custom annotation

After understanding the system annotations , Now we can define annotations by ourselves , Pass above @Override Example , It is not difficult to see that the format of defining annotations is as follows :

public @interface  Annotation name  { Definable body }

A definition body is a collection of methods , Each method actually declares a configuration parameter . Method as the name of the configuration parameter , The return value type of a method is the type of the configuration parameter . It's not the same as the normal way , Can pass default Keyword to declare the default values of configuration parameters .

We need to pay attention to :

1. Only... Can be used here public Or by default defalt Two permission modifiers
2. The type of configuration parameter can only use basic type (byte,boolean,char,short,int,long,float,double) and String,Enum,Class,annotation
3. For annotations with only one configuration parameter , Parameter name is suggested to be set value, The method is called value
4. Once the configuration parameters are set , Its parameter value must have definite value , Or specify... When using annotations , Or use it when defining annotations default Set the default value for it , For parameter values of non basic types , It cannot be null.

image @Override such , Annotations without member definitions are called tag annotations .

Now let's define an annotation @UserMeta, This annotation is useless at present , Just to demonstrate :

@Documented
@Target(ElementType.CONSTRUCTOR)
@Retention(RetentionPolicy.RUNTIME)
public @interface UserMeta {
    public int id() default 0;

    public String name() default "";

    public int age() default ;
}

With rice , I can't eat without chopsticks ( Walk away with your fingers ), Let's see how to handle annotations . 

  • Annotation processor

We've learned how to define annotations above , To make annotations work , We need to write the corresponding annotation processor for annotations . According to the nature of annotations , Annotation processor Can be divided into :1 Runtime annotation processing   and 2 Compile time annotation processor . Runtime processor need With the help of reflection mechanism Realization , and Compile time processor You need to With the help of APT To achieve .

Whether it's a runtime annotation processor or a compile time annotation processor , The main task is to read annotations and process specific annotations , From this point of view, the annotation processor is very easy to understand .

Let's first look at how to write a runtime annotation processor .

  • 3.1. Runtime annotation processor

be familiar with java The students of reflection mechanism must be right java.lang.reflect The bag is very familiar with , All of the api Both support reading runtime Annotation The ability of , That is, the attribute is @Retention(RetentionPolicy.RUNTIME) Annotations .

stay java.lang.reflect Medium AnnotatedElement The interface is the interface of all program elements (Class,Method) The parent interface , We can get the... Of a class by reflection AnnotatedElement object , And then you can access it through the methods provided by the object Annotation Information , The common methods are as follows :

Method meaning
<T extends Annotation> T getAnnotation(Class<T> annotationClass) Return the annotation of the specified type that exists on this element
Annotation[] getAnnotations() Returns all annotations that exist on the element
default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) Returns the annotation of the specified type of the element
default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) Returns all comments that exist directly on the element
default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) Returns a comment of a type that exists directly on the element
Annotation[] getDeclaredAnnotations() Returns all comments that exist directly on the element

Writing runtime annotations generally requires understanding the above knowledge points , Now let's do a small experiment .

  • 3.1.1. A simple example

First, we use a simple example to introduce how to write a runtime annotation processor : There is one in our system User Entity class :

public class User {
    private int id;
    private int age;
    private String name;

    @UserMeta(id=1,name="dong",age = 10)
    public User() {
    }


    public User(int id, int age, String name) {
        this.id = id;
        this.age = age;
        this.name = name;
    }

  //... Omit setter and getter Method 

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

We hope to pass @UserMeta(id=1,name="dong",age = 10)( This annotation we mentioned above ) To set User The default value of the instance .

The custom annotation classes are as follows :

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.CONSTRUCTOR)
public @interface UserMeta {
    public int id() default 0;

    public String name() default "";

    public int age() default 0;
}

This annotation class acts on the constructor , And exist at runtime , In this way, we can get annotations through reflection at runtime and then User Instance set value , See how to handle this annotation .

Runtime annotation processor :

public class AnnotationProcessor {

    public static void init(Object object) {

        if (!(object instanceof User)) {
            throw new IllegalArgumentException("[" + object.getClass().getSimpleName() + "] isn't type of User");
        }

        Constructor[] constructors = object.getClass().getDeclaredConstructors();
        for (Constructor constructor : constructors) {
            if (constructor.isAnnotationPresent(UserMeta.class)) {
                UserMeta userFill = (UserMeta) constructor.getAnnotation(UserMeta.class);
                int age = userFill.age();
                int id = userFill.id();
                String name = userFill.name();
                ((User) object).setAge(age);
                ((User) object).setId(id);
                ((User) object).setName(name);
            }
        }
    }
}

Test code :

public class Main {

    public static void main(String[] args) {
        User user = new User();
        AnnotationProcessor.init(user);
        System.out.println(user.toString());
    }
}

Run test code , Then we get the result we want :

User{id=1, age=10, name=’dong’}

Here we get it through reflection User Constructor of class declaration , And check whether @UserMeta annotation . Then get the parameter value from the annotation and assign it to User object .

As mentioned above , The essence of runtime annotation processor is to get annotation information through reflection , Then do the rest . Compiling a runtime annotation processor is as simple as that . Runtime annotations are usually used for parameter configuration class modules .

  • 3.1.2. Do it yourself ButterKnife

To engage in Android For development partners ,ButterKnife It can be said to be a powerful weapon , It can greatly reduce our writing findViewById(XXX). Now? , We will use the runtime annotation processor just learned to write a simplified version ButterKnife.

Custom annotation :

// This annotation is used to configure layout resources 
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
    int value();// When there is only one return , You can use value Make a name , In this way, you don't need to use the name to mark when using 
}

// This annotation is used to configure the control ID
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
    int id();
    boolean clickable() default  false;
}

  Custom runtime annotations :

public class ButterKnife {

    //view Control 
    public static void initViews(Object object, View sourceView){
        // Get the member variables declared by this class 
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields){
            // Get the ViewInject annotation 
            ViewInject viewInject = field.getAnnotation(ViewInject.class);
            if(viewInject != null){
                int viewId = viewInject.id();// obtain id Parameter values 
                boolean clickable = viewInject.clickable();// obtain clickable Parameter values 
                if(viewId != -1){
                    try {
                        field.setAccessible(true);
                        field.set(object, sourceView.findViewById(viewId));
                        if(clickable == true){
                            sourceView.findViewById(viewId).setOnClickListener((View.OnClickListener) (object));
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    // Layout resources 
    public static void initLayout(Activity activity){
        Class<? extends Activity> activityClass =  activity.getClass();
        ContentView contentView = activityClass.getAnnotation(ContentView.class);
        if(contentView != null){
            int layoutId = contentView.value();
            try {
                // Reflection execution setContentView() Method 
                Method method = activityClass.getMethod("setContentView", int.class);
                method.invoke(activity, layoutId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


    public static void init(Activity activity) {
        initLayout(activity);
        initViews(activity,activity.getWindow().getDecorView());
    }
}

Test code :

@ContentView(id=R.layout.activity_main)
public class MainActivity extends Activity implements View.OnClickListener {

    @ViewInject(id=R.id.tvDis,clickable = true)
    private TextView tvDis;

    @ViewInject(id=R.id.btnNew,clickable =true)
    private Button btnNew;

    @ViewInject(id =R.id.btnScreenShot,clickable = true)
    private Button btnScreenShot;

    @ViewInject(id =R.id.imgContainer)
    private ImageView imgContainer;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        AnnotationUtil.inJect(this);

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.tvDis:
                break;
            case R.id.btnNew:
                break;
            case R.id.btnScreenShot:
                break;
        }
    }
}

A simple ButterKnife And that's what happened , Is it very simple . Let's move on to the most important point of this article : Compile time annotation processor .

  • 3.2. Compile time annotation processor       

Unlike the runtime annotation processor , Write compile time annotation processor (Annotation Processor Tool).

APT Used to scan and process annotation information at compile time . A specific annotation processor can be java Source files or compiled class File as input , And then output some other files , It can be .java file , It can also be .class file , But usually what we output is .java file .( Be careful : It's not modifying the source file ). If the output is .java file , these .java File round other source files are javac compile .

You may wonder , At what stage does the annotation processor intervene ? ok , In fact javac Before you start compiling , That's why we're usually willing to export .java The reason for the document .

Annotations were first made in java 5 introduce , It mainly includes apt and com.sum.mirror In the package mirror api, here apt and javac They are independent . from java 6 Start , Annotation processors are formally standardized ,apt Tools are also directly integrated into javac among .

Let's return to the topic of how to write compile time annotation processors , Compiling a compile time annotation is mainly divided into two steps :                1. Inherit AbstractProcessor, Implement your own annotation processor
        2. Register processor , And beat it into jar package
It looks simple, doesn't it ? Let's take a look at the relevant knowledge points slowly .

  • 3.2.1. Custom annotation handler

Let's start with a standard annotation processor format :

public class MyAnnotationProcessor extends AbstractProcessor {

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return super.getSupportedAnnotationTypes();
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return super.getSupportedSourceVersion();
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        return false;
    }
}

  Let's simply understand 5 The function of two methods

Method effect
init(ProcessingEnvironment processingEnv) This method is automatically called by the annotation processor , among ProcessingEnvironment Class provides many useful tool classes :Filter,Types,Elements,Messager etc.
getSupportedAnnotationTypes() The set of strings returned by this method indicates that the processor is used to process those annotations
getSupportedSourceVersion() This method is used to specify the supported java edition , Generally speaking, we support the latest version , So go straight back SourceVersion.latestSupported() that will do
process(Set annotations, RoundEnvironment roundEnv) This method is the main place for annotation processor to process annotations , We need to write code here to scan and process annotations , And the resulting java file . What needs to be deepened is RoundEnvironment class , This is used to find the annotations used on the program elements

To write an annotation processor, you first need to ProcessingEnvironment and RoundEnvironment very familiar . Next, let's take a look at these two classes . So let's look at this first ProcessingEnvironment class :

public interface ProcessingEnvironment {

    Map<String,String> getOptions();

    //Messager Used to report errors , Warnings and other reminders 
    Messager getMessager();

    //Filter To create a new source file ,class Documents and supporting documents 
    Filer getFiler();

    //Elements Contains information for manipulating Element The tool method of 
    Elements getElementUtils();

     //Types Contains information for manipulating TypeMirror The tool method of 
    Types getTypeUtils();

    SourceVersion getSourceVersion();

    Locale getLocale();
}

Let's focus on Element,Types and Filer.Element( Elements ) What is it? ?

  • Element

element Represents a static , Language level components . And any structured document can be seen as composed of different element The structure of the composition , such as XML,JSON etc. . Here we use XML For example :

<root>
  <child>
    <subchild>.....</subchild>
  </child>
</root>

This paragraph xml It contains three elements :<root>,<child>,<subchild>, By now you have understood what elements are . about java In terms of source files , It's also a structured document :

package com.closedevice;             //PackageElement

public class Main{                  //TypeElement
    private int x;                  //VariableElement

    private Main(){                 //ExecuteableElement

    }

    private void print(String msg){ // The parameter part String msg by TypeElement

    }

}

about java In terms of source files ,Element Represents program elements : package , class , Methods are all program elements . In addition, if you are interested in web page parsing tools jsoup be familiar with , You will feel like operating here element It's very easy , About jsoup Not covered in this article .

Next, let's look at various Element Diagram of the relationship between , In order to have a general understanding :

 Picture description here

Elements meaning
VariableElement Representing one Field , Enumeration constants , Method or parameters of construction method , Local variables and Elements such as exception parameters
PackageElement Represents package elements
TypeElement Represents a class or interface element
ExecutableElement Code method , Constructors , Elements such as initialization code blocks of classes or interfaces , It also includes annotation type elements
  •  TypeMirror

We also need to focus on these three categories :
DeclaredType Represents the declaration type : Class type or interface type , Of course, parametric types are also included , such as Set<String>, It also includes primitive types

TypeElement Represents a class or interface element , and DeclaredType Represents a class type or interface type .

TypeMirror representative java Types in language .Types Including basic types , Declaration type ( Class types and interface types ), Array , Type variables and null types . It also represents the general configuration type parameter , The type of signature and executable file returned .TypeMirror The most important thing about classes is  getKind()  Method , This method returns TypeKind type , For your convenience , The source code is attached here :

public enum TypeKind {
    BOOLEAN,BYTE,SHORT,INT,LONG,CHAR,FLOAT,DOUBLE,VOID,NONE,NULL,ARRAY,DECLARED,ERROR,  TYPEVAR,WILDCARD,PACKAGE,EXECUTABLE,OTHER,UNION,INTERSECTION;

    public boolean isPrimitive() {
        switch(this) {
        case BOOLEAN:
        case BYTE:
        case SHORT:
        case INT:
        case LONG:
        case CHAR:
        case FLOAT:
        case DOUBLE:
            return true;

        default:
            return false;
        }
    }
}

Simply speaking ,Element For source code ,TypeElement Represents the type element in the source code , Such as class . Although we can from TypeElement Get the class name ,TypeElement Does not contain information about the class itself , For example, its parent class , To get this information, you need to use TypeMirror, Can pass Element Medium asType() Get the corresponding TypeMirror.

And let's see RoundEnvironment, This class is relatively simple , A stroke of ink leads to :

public interface RoundEnvironment {

    boolean processingOver();

     // Whether the annotation processor generated an error in the last round 
    boolean errorRaised();

     // Returns the root element generated by the annotation processor in the last round 
    Set<? extends Element> getRootElements();

   // Returns a collection containing elements of the specified annotation type 
    Set<? extends Element> getElementsAnnotatedWith(TypeElement a);

    // Returns a collection containing elements of the specified annotation type 
    Set<? extends Element> getElementsAnnotatedWith(Class<? extends Annotation> a);
}
  • Filer

Filer Used to create new files in the annotation processor . The specific usage will be demonstrated in the following examples . In addition, due to Filer It's really troublesome to use , We'll use it later javapoet Simplify our operations .

Okay , About AbstractProcessor We have read some important knowledge points in . Suppose you have written an annotation processor by now , below , What to do ?

  • Package and register .

How can a custom processor take effect ? In order to make java The compiler may find a custom annotation processor, which we need to register and package : Custom processor Need to be Make it one jar, And need to be in jar Bag META-INF/services Create a fixed file in the path javax.annotation.processing.Processor, stay javax.annotation.processing.Processor In file You need to fill in the Full pathname , Yes Several processors Need Fill in a few .

from java 6 after , We just need to play jar Prevent... To the project buildpath Then you can ,javac In the process of running, it will automatically check javax.annotation.processing.Processor Registered annotation processor , And register it . and java 5 It needs to be used alone apt Tools ,java 5 It must be used less , Just skip .

Up to now , Some concepts related to annotation processors have been introduced in general , Finally, we need to get an annotation processor code jar package .

Next , To practice .

  • Simple example

Use a simple example , To demonstrate how to Gradle To create a compile time annotation processor , For convenience , Here is the direct help Android studio. Of course, you can also use maven structure .

First create AnnotationTest engineering , Create apt moudle. We need to pay attention to ,AbstractProcessor Is in javax In bag , and android The package does not exist in the core library , So choose to create moudle You need to choose java Library:

 Picture description here

At this time, the project structure is as follows :

 Picture description here   Next, we are apt Create annotation and processor subpackage , among annotation It is used to store our customized annotations , and processor It is used to store our customized annotation processor .

Let's have a simple one first , Customize @Print annotation : The final function of this annotation is to output the annotated elements :

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})  
@Retention(RetentionPolicy.CLASS)                                  
public @interface Print {                                     
}

Next, write an annotation processor for it :

public class PrintProcessor extends AbstractProcessor {

    private Messager mMessager;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mMessager = processingEnvironment.getMessager();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (TypeElement te : annotations) {
            for (Element e : roundEnv.getElementsAnnotatedWith(te)) {//find special annotationed element
                print(e.toString());//print element
            }
        }
        return true;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {

        return SourceVersion.latestSupported();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        LinkedHashSet<String> annotations = new LinkedHashSet<>();
        annotations.add(Print.class.getCanonicalName());
        return super.getSupportedAnnotationTypes();
    }

    private void print(String msg) {
        mMessager.printMessage(Diagnostic.Kind.NOTE, msg);
    }
}

Now we have finished a simple annotation . In the compilation phase , The compiler will output the information of the annotated element . Because we are Gradle In the environment , Therefore, this information will be in Gradle Console Lower output .

Next, let's write a slightly difficult annotation @Code: This annotation will generate a class in the specified format , Let's first look at the definition of this annotation :

@Retention(CLASS)
@Target(METHOD)
public @interface Code {
    public String author();
    public String date() default "";
}

Next , We need to write annotation processors for it , Simple code , Look directly at :

public class CodeProcessor extends AbstractProcessor {

    private final String SUFFIX = "$WrmRequestInfo";

    private Messager mMessager;
    private Filer mFiler;
    private Types mTypeUtils;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mMessager = processingEnvironment.getMessager();
        mFiler = processingEnvironment.getFiler();
        mTypeUtils = processingEnvironment.getTypeUtils();

    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        LinkedHashSet<String> annotations = new LinkedHashSet<>();
        annotations.add(Code.class.getCanonicalName());
        return annotations;
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        for (Element e : roundEnvironment.getElementsAnnotatedWith(Code.class)) {//find special annotationed element
            Code ca = e.getAnnotation(Code.class);
            TypeElement clazz = (TypeElement) e.getEnclosingElement();
            try {
                generateCode(e, ca, clazz);
            } catch (IOException x) {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
                        x.toString());
                return false
            }
        }
        return true;
    }

    //generate 
    private void generateCode(Element e, Code ca, TypeElement clazz) throws IOException {
        JavaFileObject f = mFiler.createSourceFile(clazz.getQualifiedName() + SUFFIX);
        mMessager.printMessage(Diagnostic.Kind.NOTE, "Creating " + f.toUri());
        Writer w = f.openWriter();
        try {
            String pack = clazz.getQualifiedName().toString();
            PrintWriter pw = new PrintWriter(w);
            pw.println("package " + pack.substring(0, pack.lastIndexOf('.')) + ";"); //create package element
            pw.println("\n class " + clazz.getSimpleName() + "Autogenerate {");//create class element
            pw.println("\n    protected " + clazz.getSimpleName() + "Autogenerate() {}");//create class construction
            pw.println("    protected final void message() {");//create method
            pw.println("\n//" + e);
            pw.println("//" + ca);
            pw.println("\n        System.out.println(\"author:" + ca.author() + "\");");
            pw.println("\n        System.out.println(\"date:" + ca.date() + "\");");
            pw.println("    }");
            pw.println("}");
            pw.flush();
        } finally {
            w.close();
        }
    }

}

The core content is  generateCode()  In the method , This method utilizes the above mentioned Filer To write the source file . You'll find that , Here is mainly the process of creating character splicing , It's too much trouble .

Up to now , We have written two annotations and their corresponding processors . Now we only need to configure it .

stay resources Create under resource folder META-INF.services, Then create a file named javax.annotation.processing.Processor The file of , Configure the annotation processor that needs to be enabled in this file , That is, write the full path of the processor , Just write a few processors , Branch write Yao , For example, here we are :

com.closedevice.processor.PrintProcessor
com.closedevice.processor.CodeProcessor

Now we are ready for packing , At this time, the project structure is as follows : 

 Picture description here

Next we need to change apt moudle become involved jar package . No matter what platform you are on , Finally hit jar Even if the bag is half done . For the convenience of demonstration , Direct visualization :

 Picture description here

Take a look at apt.jar Structure : 

 Picture description here

  The following will apt.jar Copy the file to the master moudle app Under the libs In the folder , Start using it . We are simply in MainActivity.java Use :

public class MainActivity extends AppCompatActivity {

    @Override
    @Print
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        process();
    }

    @Code(author = "closedevice",date="20161225")
    private void process() {

    }


}

Respectively in onCreate() and process() Methods using our annotations , Now compile app modular , During the compilation process, you can Gradle Console See the output information , If nothing else , You can see the information :

 Picture description here

In addition to app moudle Of build/intermediates/classes/debug/com/closedevice/annotationtest You can see the automatically generated MainActivityAutogenerate.class 了 . Of course, you can also directly view the source code files generated during the compilation stage com/closedevice/annotationtest/MainActivity$WrmRequestInfo.java

 Picture description here

Take another look at the automatically generated source code :

package com.closedevice.annotationtest;

 class MainActivityAutogenerate {

    protected MainActivityAutogenerate() {}
    protected final void message() {

//process()
//@com.closedevice.annotation.Code(date=20161225, author=closedevice)

        System.out.println("author:closedevice");

        System.out.println("date:20161225");
    }
}

  Deploy the project to our simulator , No accident , You will see the following log information :

 Picture description here

That's it , A simple compile time annotation processor is implemented . Above, we use the runtime annotation processor to do a simple ButterKnife, But really ButterKnife Is to use compilation is to use APT To achieve , Limited to space , There will be no demonstration in this section


summary

This paper introduces the runtime annotation processor and compile time annotation processor , But about APT The content of the article is by no means clear , I will introduce it step by step later APT Knowledge about .

Example Demo Here

原网站

版权声明
本文为[hudawei996]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/188/202207070704553266.html