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

preface

Last year, I wrote a blog introduction to using annotations (I), which covered the basics of annotations and a Demo based on runtime annotations. Today’s blog post focuses on how to write a Demo of compile-time annotations.

This blog code references Hongyang’s blog:Android builds a compile-time annotation parsing framework and this is just the beginning

Important knowledge of annotations

Let’s review some important things about annotations:

Annotations can be divided into three categories based on how they are used and what they are for:

  1. JDK built-in system annotations, such as @Override, etc
  2. Yuan notes
  3. Custom annotations, custom annotations that we implemented ourselves

Yuan comments:

The meta-annotation is responsible for the annotation of other annotations. Java5.0 defines four standard meta-annotation types that are used to provide annotations for other annotation types. Meta annotations defined by Java5.0:

  1. @Target
  2. @Retention
  3. @Documented
  4. @Inherited

Meta-annotation parsing description

  • Documented Whether @documented will be saved to a Javadoc document

  • @Retention Retention time (optional)

SOURCE (SOURCE time), CLASS (compile time), RUNTIME (RUNTIME)

The default is CLASS, and the SOURCE is mostly Mark Annotation, which is mostly used for verification, such as Override and SuppressWarnings

  • 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_VARIABLE

  • @Inherited Whether Inherited can be Inherited. The default value is false

Examples of compile-time annotations

Let’s use AndroidStudio as an example. Suppose we want to convert a class like User into a key-value pair like JSON at compile time. It takes about three steps.

public class Person {
    @Seriable()
    String name;
    @Seriable()
    String area;
    @Seriable()
    int age;
    int weight;

    @Seriable()
    List<Article> mArticleList;
}
Copy the code
{class:"xj.jsonlibdemo.Person",
 fields:
 {
  name:"java.lang.String",
  area:"java.lang.String",
  age:"int",
  mArticleList:"java.util.List<xj.jsonlibdemo.Article>"}}Copy the code

Step 1: We create a New Java Library, put together the relevant configuration, and write our own custom Animation Seriable, as shown below

First: let’s create a new Java Library:

Next: Write our custom annotations

@documented () // indicates @retention (retentionPolicy.class) based on compile-time annotations @target ({elementtype. FIELD, elementtype. TYPE}) public @interface Seriable {}Copy the code

If you are still familiar with meta annotations, I suggest you read my blog introduction to using annotations (I), which is not covered here

Finally: the resources/meta-inf/services/javax.mail annotation. Processing. The Processor file add our custom annotations of the fully qualified path com. Example. JsonProcessor. Note if the resources/meta-inf/services/javax.mail annotation. Processing. The Processor does not exist, the need to add.

Step 2: Write our parser, inherit AbstractProcessor, and rewrite the process method to handle the logic.

@SupportedAnnotationTypes({"com.example.Seriable"}) @SupportedSourceVersion(SourceVersion.RELEASE_7) public class JsonProcessor extends AbstractProcessor { private Elements mElementUtils; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); / / tools auxiliary mElementUtils = processingEnv getElementUtils (); } @Override public boolean process(Set<? } // Find elememts (extends TypeElement> Annotations, RoundEnvironment roundEnv)setSet the Set <? extends Element> elememts = roundEnv.getElementsAnnotatedWith(Seriable.class); TypeElementtypeElement; VariableElement variableElement; Map<String, List<VariableElement>> map = new HashMap<>(); List<VariableElement> fileds = null; // 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; // The fully qualified class name of the class is used as the key, ensuring that the only String qualifiedName =typeElement.getQualifiedName().toString(); map.put(qualifiedName, fileds = new ArrayList<VariableElement>()); // Determine if the element is a member variable}else if(kind == ElementKind.FIELD) { variableElement = (VariableElement) element; // Gets the wrapper type of the elementtypeElement = (TypeElement) variableElement.getEnclosingElement();
                String qualifiedName = typeElement.getQualifiedName().toString();
                fileds = map.get(qualifiedName);
                if (fileds == null) {
                    map.put(qualifiedName, fileds = new ArrayList<VariableElement>());
                }
                fileds.add(variableElement);
            }
        }

        Set<String> set = map.keySet();

        for (String key : set) {
            if (map.get(key).size() == 0) {
                typeElement = mElementUtils.getTypeElement(key);
                List<? extends Element> allMembers = mElementUtils.getAllMembers(typeElement);
                if(allMembers.size() > 0) { map.get(key).addAll(ElementFilter.fieldsIn(allMembers)); }} // generateCodes(map);return true; Private void generateCodes(Map<String, List<VariableElement>> maps) {File dir = new File("f://Animation");
        if(! dir.exists()) dir.mkdirs(); / / traverse mapfor(String key: maps.keyset ()) {File File = new File(dir, key.replaceall ())"\ \."."_") + ".txt"); FileWriter fw = new FileWriter(file); try {/** * write json file contents */ FileWriter fw = new FileWriter(file); fw.append("{").append("class:").append("\" " + key + "\" ")
                        .append(",\n ");
                fw.append("fields:\n {\n");
                List<VariableElement> fields = maps.get(key);

                for (int i = 0; i < fields.size(); i++) {
                    VariableElement field = fields.get(i);
                    fw.append("").append(field.getSimpleName()).append(":")
                            .append("\" " + field.asType().toString() + "\" ");
                    if (i < fields.size() - 1) {
                        fw.append(",");
                        fw.append("\n");
                    }
                }
                fw.append("\n }\n");
                fw.append("}"); fw.flush(); fw.close(); } catch (IOException e) { e.printStackTrace(); }}}}Copy the code

Thinking analytical

  • The first step is to get the Elememts set according to our custom annotations
  • Step 2: Perform corresponding processing according to the type of Elememt and save it into the Map set
  • Step 3: Generate the corresponding code based on the map collection data.

Step 3: Invoke the gradle build command to generate the JAR package

Enter the gradle build command in the Terminal window of AndroidStudio. When finished, the JAR package will be generated. We can use this jar package. Note that we need to add Gradle to the environment variable.

Compile fileTree(dir: ‘libs’, include: [‘*.jar’]).

For example, let’s create a new moudle and create two classes like this:

@Seriable
public class Article {
    private String title;
    private String content;
}
Copy the code
public class User {
    @Seriable()
    String name;
    @Seriable()
    String area;
    @Seriable()
    int age;
    int weight;

    @Seriable()
    List<Article> mArticleList;
}
Copy the code

Run the gradle build command in the moudle directory and you will see the two files in our save path. File dir = new File(“f://Animation”);

{class:"xj.jsonlibdemo.Article",
 fields:
 {
  title:"java.lang.String",
  content:"java.lang.String",
  time:"long"}}Copy the code
{class:"xj.jsonlibdemo.Person",
 fields:
 {
  name:"java.lang.String",
  area:"java.lang.String",
  age:"int",
  mArticleList:"java.util.List<xj.jsonlibdemo.Article>"}}Copy the code

At this point, a simple example is explained:


Reference blog:

Android builds a compile-time annotation parsing framework and this is just the beginning

Making the address

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.