爲何要寫這一系列的博客呢?java
由於在 Android 開發的過程當中, 泛型,反射,註解這些知識進場會用到,幾乎全部的框架至少都會用到上面的一兩種知識,如 Gson 就用到泛型,反射,註解,Retrofit 也用到泛型,反射,註解 。學好這些知識對咱們進階很是重要,尤爲是閱讀開源框架源碼或者本身開發開源框架。git
java Type 詳解程序員
java 反射機制詳解github
記得去年的時候寫過一篇博客 註解使用入門(一),這篇博客主要介紹了註解的一些基本知識,以及基於運行時註解的 Demo。今天這篇博客主要介紹怎樣編寫編譯時註解的Demo。bash
這篇博客代碼參考了鴻洋的博客: Android 打造編譯時註解解析框架 這只是一個開始微信
咱們先複習一下註解的一些重要知識:app
元註解的做用就是負責註解其餘註解。Java5.0定義了4個標準的meta-annotation類型,它們被用來提供對其它annotation類型做說明。Java5.0定義的元註解:框架
元註解 解析說明
@Documented 是否會保存到 Javadoc 文檔中
@Retention 保留時間,可選值
SOURCE(源碼時),CLASS(編譯時),RUNTIME(運行時)
默認爲 CLASS,SOURCE 大都爲 Mark Annotation,這類 Annotation 大都用來校驗,好比 Override, SuppressWarnings
ANONOTATION_TYPE(註解類型聲明), PACKAGE(包) TYPE (類,包括enum及接口,註解類型) METHOD (方法) CONSTRUCTOR (構造方法) FIFLD (成員變量) PARAMATER (參數) LOCAL_VARIABLE (局部 變量)
這裏咱們以 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:
接着: 編寫咱們的自定義註解
@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 不存在,須要本身添加。
@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();
}
}
}
}
複製代碼
思路解析
在 AndroidStudio 中的 Terminal 窗口輸入 gradle build 命令,完成以後將生成 jar 包。咱們就可使用這一個 jar 包了。須要注意的是 咱們須要將 gradle 添加到環境變量中。
好比咱們新建一個 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>"
}
}
複製代碼
到此,一個簡單的例子講解完畢:
參考博客:
掃一掃,歡迎關注個人微信公衆號 stormjun94(徐公碼字), 目前是一名程序員,不只分享 Android開發相關知識,同時還分享技術人成長曆程,包括我的總結,職場經驗,面試經驗等,但願能讓你少走一點彎路。