Android 自定義編譯時註解1 簡單的例子

爲何要寫這一系列的博客呢?java

由於在 Android 開發的過程當中, 泛型,反射,註解這些知識進場會用到,幾乎全部的框架至少都會用到上面的一兩種知識,如 Gson 就用到泛型,反射,註解,Retrofit 也用到泛型,反射,註解 。學好這些知識對咱們進階很是重要,尤爲是閱讀開源框架源碼或者本身開發開源框架。git

java Type 詳解程序員

java 反射機制詳解github

註解使用入門(一)面試

Android 自定義編譯時註解1 - 簡單的例子json

前言

記得去年的時候寫過一篇博客 註解使用入門(一),這篇博客主要介紹了註解的一些基本知識,以及基於運行時註解的 Demo。今天這篇博客主要介紹怎樣編寫編譯時註解的Demo。bash

這篇博客代碼參考了鴻洋的博客: Android 打造編譯時註解解析框架 這只是一個開始微信

註解的重要知識

咱們先複習一下註解的一些重要知識:app

根據註解使用方法和用途,咱們能夠將Annotation分爲三類:

  1. JDK內置系統註解,如 @Override 等
  2. 元註解
  3. 自定義註解,咱們本身實現的自定義註解

元註解:

元註解的做用就是負責註解其餘註解。Java5.0定義了4個標準的meta-annotation類型,它們被用來提供對其它annotation類型做說明。Java5.0定義的元註解:框架

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

元註解 解析說明

  • @Documented 是否會保存到 Javadoc 文檔中

  • @Retention 保留時間,可選值

SOURCE(源碼時),CLASS(編譯時),RUNTIME(運行時)

默認爲 CLASS,SOURCE 大都爲 Mark Annotation,這類 Annotation 大都用來校驗,好比 Override, SuppressWarnings

  • @Target 能夠用來修飾哪些程序元素,如 TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER 等,未標註則表示可修飾全部

ANONOTATION_TYPE(註解類型聲明), PACKAGE(包) TYPE (類,包括enum及接口,註解類型) METHOD (方法) CONSTRUCTOR (構造方法) FIFLD (成員變量) PARAMATER (參數) LOCAL_VARIABLE (局部 變量)

  • @Inherited 是否能夠被繼承,默認爲 false

編譯時註解例子說明

這裏咱們以 AndroidStudio 爲例子講解。假設咱們要把 User 這樣的一個類,在編譯時轉化成相似於 json 這樣鍵值對的形式。大概須要三步。

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

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

第一步:咱們新建一個 java library,搭配好相關的配置,並編寫咱們自定義的 Animation Seriable,以下所示

首先:咱們新建一個 java library:

接着: 編寫咱們的自定義註解

@Documented()
// 表示是基於編譯時註解的
@Retention(RetentionPolicy.CLASS)
// 表示能夠做用於成員變量,類、接口
@Target({ElementType.FIELD, ElementType.TYPE}) 
public @interface Seriable {

}

複製代碼

若是對元註解還步瞭解的話,建議先閱讀我以前寫的博客 註解使用入門(一),這裏再也不講解

最後:在 resources/META-INF/services/javax.annotation.processing.Processor 文件中 添加 咱們自定義註解的全限定路徑 com.example.JsonProcessor。注意若 resources/META-INF/services/javax.annotation.processing.Processor 不存在,須要本身添加。

第二步:編寫咱們的解析器,繼承 AbstractProcessor ,並重寫 process 方法,處理相關邏輯。

@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);
        //  工具輔助類
        mElementUtils = processingEnv.getElementUtils();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        //   第一步,根據咱們自定義的註解拿到 elememts set 集合
        Set<? extends Element> elememts = roundEnv.getElementsAnnotatedWith(Seriable.class);
        TypeElement typeElement;
        VariableElement variableElement;
        Map<String, List<VariableElement>> map = new HashMap<>();
        List<VariableElement> fileds = null;
        //  第二步: 根據 element 的類型作相應的處理,並存進 map 集合
        for (Element element : elememts) {
            ElementKind kind = element.getKind();
            // 判斷該元素是否爲類
            if (kind == ElementKind.CLASS) {
                typeElement = (TypeElement) element;
                //  這裏以類的全限定類名做爲 key,確保惟一
                String qualifiedName = typeElement.getQualifiedName().toString();
                map.put(qualifiedName, fileds = new ArrayList<VariableElement>());
                // 判斷該元素是否爲成員變量
            } else if (kind == ElementKind.FIELD) {
                variableElement = (VariableElement) element;
                //                獲取該元素的封裝類型
                typeElement = (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));
                }
            }
        }
        // 第三步:根據 map 集合數據生成代碼
        generateCodes(map);

        return true;
    }

    // 生成咱們的代碼文件
    private void generateCodes(Map<String, List<VariableElement>> maps) {
        File dir = new File("f://Animation");
        if (!dir.exists())
            dir.mkdirs();
        // 遍歷map
        for (String key : maps.keySet()) {

            // 建立文件
            File file = new File(dir, key.replaceAll("\\.", "_") + ".txt");
            try {
                /**
                 * 編寫json文件內容
                 */
                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();
            }

        }
    }
}

複製代碼

思路解析

  • 第一步,根據咱們自定義的註解拿到 elememts set 集合
  • 第二步:根據 elememt 的類型作相應的處理,並存進 map 集合
  • 第三步:根據 map 集合的數據,生成相應的代碼。

第三步:調用 gradle build 命令生成 jar 包

在 AndroidStudio 中的 Terminal 窗口輸入 gradle build 命令,完成以後將生成 jar 包。咱們就可使用這一個 jar 包了。須要注意的是 咱們須要將 gradle 添加到環境變量中。

第四步,將咱們生成的 jar 包複製到 moudle 目錄下,compile fileTree(dir: 'libs', include: ['*.jar']) , 就可使用了。

好比咱們新建一個 moudle,新建兩個類,以下:

@Seriable
public class Article {
    private String title;
    private String content;
}
複製代碼
public class User {
    @Seriable()
    String name;
    @Seriable()
    String area;
    @Seriable()
    int age;
    int weight;

    @Seriable()
    List<Article> mArticleList;
}
複製代碼

在 moudle 的目錄下執行 gradle build 命令,將能夠在咱們的保存路徑中看到咱們生成的兩個文件,(這個路徑是咱們前面在編寫 JsonProcessor 縮寫的,File dir = new File("f://Animation");)

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

到此,一個簡單的例子講解完畢:


參考博客:

Android 打造編譯時註解解析框架 這只是一個開始

github 地址

相關博客推薦

java Type 詳解

java 反射機制詳解

註解使用入門(一)

Android 自定義編譯時註解1 - 簡單的例子

Android 編譯時註解 —— 語法詳解

帶你讀懂 ButterKnife 的源碼

掃一掃,歡迎關注個人微信公衆號 stormjun94(徐公碼字), 目前是一名程序員,不只分享 Android開發相關知識,同時還分享技術人成長曆程,包括我的總結,職場經驗,面試經驗等,但願能讓你少走一點彎路。

相關文章
相關標籤/搜索