Android 打造編譯時註解解析框架

說道註解,居然還有各類分類,得,這記不住,咱們從註解的做用來反推其分類,幫助你們記憶,而後舉例強化你們的記憶,話說註解的做用:html

一、標記一些信息,這麼說可能太抽象,那麼我說,你見過@Override、@SuppressWarnings等,這類註解就是用於標識,能夠用做一些檢驗java

二、運行時動態處理,這個你們見得應該最多,在運行時拿到類的Class對象,而後遍歷其方法、變量,判斷有無註解聲明,而後作一些事情。相似上述三篇博文中的作法。android

三、編譯時動態處理,這個呢?就是咱們今天的主角了,通常這類註解會在編譯的時候,根據註解標識,動態生成一些類或者生成一些xml均可以,在運行時期,這類註解是沒有的~~會依靠動態生成的類作一些操做,由於沒有反射,效率和直接調用方法沒什麼區別~~~json

關於3,你們不明白,沒事,下文會詳談,使用這類註解的項目有:ParcelableGenerator、butterknife 、androidannotaion等。 數組

做用談完了,那麼若是你看到一個註解的聲明你如何去判斷他的做用呢?例如:微信

[java] view plaincopy網絡

  1. @Retention(RetentionPolicy.CLASS)  app

  2. @Target ({ ElementType.FIELD, ElementType.TYPE })  框架

  3. public @interface  InjectView  eclipse

  4. {  

  5.     int value();  

  6. }  


1秒鐘告訴我,它的做用是什麼?哈,你們可能會鬱悶,擦,我咋知道。其實能夠看這個註解上面的@Retention後面的值,設置的爲CLASS,說明就是編譯時動態處理的。

這個值是一個枚舉:有三個:SOURCE、RUNTIME、CLASS , 到這裏,是否是,搜噶,這三個11對應於上面三個做用。

好了,說完了註解的做用以及判斷方式,那麼你們能夠看到除了@Retention還有個@Target,@Target的值呢是一個ElementType[]數組。什麼意思呢?就是標明這個註解能標識哪些東西,好比類、變量、方法、甚至是註解自己(元註解)等。這個在:Android 進階 教你打造 Android 中的 IOC 框架 【ViewInject】 (上)有詳細說明。

好了,到此註解告一段落,你們只要記得註解的做用,以及如何去定義一個註解就好。

接下來進入咱們的主題編譯時註解。

對了,我建立了一個公衆號,會推送一些開源項目、最新博客、視頻等,關於博客涉及到的東西,也會提早給你們通知,能夠關注一下,謝謝,左側欄目,微信掃描便可。

三、編譯時註解

那咱們說一下編寫過程。

一、建立一個類,繼承AbstractProcessor

[java] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. package com.zhy.util.ioc.processor;  

  2.   

  3. import java.util.Set;  

  4.   

  5. import javax.annotation.processing.AbstractProcessor;  

  6. import javax.annotation.processing.RoundEnvironment;  

  7. import javax.annotation.processing.SupportedAnnotationTypes;  

  8. import javax.annotation.processing.SupportedSourceVersion;  

  9. import javax.lang.model.SourceVersion;  

  10. import javax.lang.model.element.TypeElement;  

  11.   

  12. @SupportedAnnotationTypes("com.zhy.util.ioc.annotation.InjectView")  

  13. @SupportedSourceVersion(SourceVersion.RELEASE_6)  

  14. public class ViewInjectProcessorBeta extends AbstractProcessor  

  15. {  

  16.   

  17.     @Override  

  18.     public boolean process(Set<? extends TypeElement> annotations,  

  19.             RoundEnvironment roundEnv)  

  20.     {  

  21.         // TODO Auto-generated method stub  

  22.         return false;  

  23.     }  

  24.   

  25. }  


這個類上能夠添加註解:

@SupportedAnnotationTypes的值爲當前類支持的註解的完整類路徑,支持通配符。

@SupportedSourceVersion 標識該處理器支持的源碼版本

除此之外還有一個@SupportedOptions,這個通常是命令行時候用的,設置一些選項,but,命令行我不熟,所以:略。

注:若是你們找不到AbstractProcessor,記得右鍵build-path add library把jdk加進來。


二、建立resources等文件。

這個對項目的一個結構有着固定的要求,下面我經過一張圖來講:


能夠看到,在咱們的項目中呢,還須要建立一個resources這樣的source folder ,右鍵 new sources folder便可。

而後在裏面建立META-INF/services/javax.annotation.processing.Processor文件,這個文件中去寫咱們處理器的類完整路徑。

通過上述兩部,咱們的編寫環境就OK了。


四、完整例子

下面咱們經過一個例子來給你們演示編譯時動態生成數據,咱們的效果是這樣的,用戶編寫一堆bean,例如User類,咱們經過註解提取屬性動態生成一個json文件,以及一個代理類,注意是編譯時生成。

注:如下爲一個教學示例,無任何使用價值。

那麼咱們依然分爲步驟來作:

一、建立編寫環境


javax.annotation.processing.Processor裏面寫的是:com.zhy.annotationprocess.processor.BeanProcessor

咱們還建立了一個註解:

[java] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. package com.zhy.annotationprocess.annotation;  

  2.   

  3. import java.lang.annotation.ElementType;  

  4. import java.lang.annotation.Retention;  

  5. import java.lang.annotation.RetentionPolicy;  

  6. import java.lang.annotation.Target;  

  7.   

  8. @Target({ ElementType.FIELD, ElementType.TYPE })  

  9. @Retention(RetentionPolicy.CLASS)  

  10. public @interface Seriable  

  11. {  

  12.       

  13. }  


哈,一秒鐘告訴我,哪一類做用的註解。

二、動態生成數據

一、首先明確一下咱們的目標:

咱們有不少bean類,例如:

[java] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. public class User  

  2. {  

  3.     @Seriable  

  4.     private String username;  

  5.     @Seriable  

  6.     private String password;  

  7.   

  8.     private String three;  

  9.     private String four;  

  10. }  


[java] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. @Seriable  

  2. public class Article  

  3. {  

  4.     private String title ;   

  5.     private String content ;   

  6. }  


看到有兩個普通的bean,上面聲明瞭咱們的註解,若是類上聲明註解咱們就將其全部的變量都生成一個json描述文件;若是僅僅是成員變量呢?那咱們只提取聲明的成員變量來動態生成。

相似以下的描述文件:

[html] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. {class:"com.zhy.Article",  

  2.  fields:  

  3.  {  

  4.   content:"java.lang.String",  

  5.   title:"java.lang.String"  

  6.  }  

  7. }  


是否是以爲沒撒用處,其實用處大大滴,之後咱們會驗證。

二、編寫BeanProcessor

[java] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. package com.zhy.annotationprocess.processor;  

  2.   

  3. import java.io.File;  

  4. import java.io.FileWriter;  

  5. import java.io.IOException;  

  6. import java.util.ArrayList;  

  7. import java.util.HashMap;  

  8. import java.util.List;  

  9. import java.util.Map;  

  10. import java.util.Set;  

  11.   

  12. import javax.annotation.processing.AbstractProcessor;  

  13. import javax.annotation.processing.ProcessingEnvironment;  

  14. import javax.annotation.processing.RoundEnvironment;  

  15. import javax.annotation.processing.SupportedAnnotationTypes;  

  16. import javax.annotation.processing.SupportedSourceVersion;  

  17. import javax.lang.model.SourceVersion;  

  18. import javax.lang.model.element.Element;  

  19. import javax.lang.model.element.ElementKind;  

  20. import javax.lang.model.element.TypeElement;  

  21. import javax.lang.model.element.VariableElement;  

  22. import javax.lang.model.util.ElementFilter;  

  23. import javax.lang.model.util.Elements;  

  24.   

  25. import com.zhy.annotationprocess.annotation.Seriable;  

  26.   

  27. @SupportedAnnotationTypes("com.zhy.annotationprocess.annotation.Seriable")  

  28. @SupportedSourceVersion(SourceVersion.RELEASE_6)  

  29. public class BeanProcessor extends AbstractProcessor  

  30. // 元素操做的輔助類  

  31.     Elements elementUtils;  

  32.   

  33.     @Override  

  34.     public synchronized void init(ProcessingEnvironment processingEnv)  

  35.     {  

  36.         super.init(processingEnv);  

  37.         // 元素操做的輔助類  

  38.         elementUtils = processingEnv.getElementUtils();  

  39.     }  

  40.   

  41.     @Override  

  42.     public boolean process(Set<? extends TypeElement> annotations,  

  43.             RoundEnvironment roundEnv)  

  44.     {  

  45.   

  46.         // 得到被該註解聲明的元素  

  47.         Set<? extends Element> elememts = roundEnv  

  48.                 .getElementsAnnotatedWith(Seriable.class);  

  49.         TypeElement classElement = null;// 聲明類元素  

  50.         List<VariableElement> fields = null;// 聲明一個存放成員變量的列表  

  51.         // 存放兩者  

  52.         Map<String, List<VariableElement>> maps = new HashMap<String, List<VariableElement>>();  

  53.         // 遍歷  

  54.         for (Element ele : elememts)  

  55.         {  

  56.             // 判斷該元素是否爲類  

  57.             if (ele.getKind() == ElementKind.CLASS)  

  58.             {  

  59.                 classElement = (TypeElement) ele;  

  60.                 maps.put(classElement.getQualifiedName().toString(),  

  61.                         fields = new ArrayList<VariableElement>());  

  62.   

  63.             } else if (ele.getKind() == ElementKind.FIELD) // 判斷該元素是否爲成員變量  

  64.             {  

  65.                 VariableElement varELe = (VariableElement) ele;  

  66.                 // 獲取該元素封裝類型  

  67.                 TypeElement enclosingElement = (TypeElement) varELe  

  68.                         .getEnclosingElement();  

  69.                 // 拿到key  

  70.                 String key = enclosingElement.getQualifiedName().toString();  

  71.                 fields = maps.get(key);  

  72.                 if (fields == null)  

  73.                 {  

  74.                     maps.put(key, fields = new ArrayList<VariableElement>());  

  75.                 }  

  76.                 fields.add(varELe);  

  77.             }  

  78.         }  

  79.   

  80.         for (String key : maps.keySet())  

  81.         {  

  82.             if (maps.get(key).size() == 0)  

  83.             {  

  84.                 TypeElement typeElement = elementUtils.getTypeElement(key);  

  85.                 List<? extends Element> allMembers = elementUtils  

  86.                         .getAllMembers(typeElement);  

  87.                 if (allMembers.size() > 0)  

  88.                 {  

  89.                     maps.get(key).addAll(ElementFilter.fieldsIn(allMembers));  

  90.                 }  

  91.             }  

  92.         }  

  93.   

  94.         generateCodes(maps);  

  95.   

  96.         return true;  

  97.     }  

  98.   

  99.     private void generateCodes(Map<String, List<VariableElement>> maps)  

  100.     {  

  101.         File dir = new File("f://apt_test");  

  102.         if (!dir.exists())  

  103.             dir.mkdirs();  

  104.         // 遍歷map  

  105.         for (String key : maps.keySet())  

  106.         {  

  107.   

  108.             // 建立文件  

  109.             File file = new File(dir, key.replaceAll("\\.""_") + ".txt");  

  110.             try  

  111.             {  

  112.                 /** 

  113.                  * 編寫json文件內容 

  114.                  */  

  115.                 FileWriter fw = new FileWriter(file);  

  116.                 fw.append("{").append("class:").append("\"" + key + "\"")  

  117.                         .append(",\n ");  

  118.                 fw.append("fields:\n {\n");  

  119.                 List<VariableElement> fields = maps.get(key);  

  120.   

  121.                 for (int i = 0; i < fields.size(); i++)  

  122.                 {  

  123.                     VariableElement field = fields.get(i);  

  124.                     fw.append("  ").append(field.getSimpleName()).append(":")  

  125.                             .append("\"" + field.asType().toString() + "\"");  

  126.                     if (i < fields.size() - 1)  

  127.                     {  

  128.                         fw.append(",");  

  129.                         fw.append("\n");  

  130.                     }  

  131.                 }  

  132.                 fw.append("\n }\n");  

  133.                 fw.append("}");  

  134.                 fw.flush();  

  135.                 fw.close();  

  136.   

  137.             } catch (IOException e)  

  138.             {  

  139.                 e.printStackTrace();  

  140.             }  

  141.   

  142.         }  

  143.     }  

  144.   

  145. }  


代碼略長,可是註釋很清除,我來解釋一下,基本分爲兩個過程:一、找出標識註解的類或成員變量,封裝到maps中;二、遍歷maps爲每一個類建立json文件。咱們把文件輸出到了f://apt_test文件夾中,若是你沒有f盤神馬的,自行修改目錄。

三、使用

到此,咱們寫完了~~那麼如何用呢?

一、導出jar

爲了更好的演示,以及省篇幅,我錄成gif


注意我選擇的一些複選框,和一些默認複選框的選中狀態,我將其放在桌面上~~

二、新建一個android或java項目

將jar拷貝到libs下,若是是java項目,須要本身建立lib文件夾,本身手動引用。

而後就開始編寫bean吧:我這裏就寫了兩個類,一個User,一個Article,上面貼過代碼了。

三、啓用annotation processor

這裏我是eclipse,你們若是是maven項目或者是別的什麼IDE,自行進行網絡搜索,這裏有個Android Studio下的使用,本身點擊哈,其實命令行也能夠。

下面咱們eclipse依然是個gif,否則得截一堆圖片:

假設咱們的jar已經拷貝到項目中了,進行以下操做



操做完成之後,那麼就能夠去f://apt_test中


打開便可看到:

[java] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. {class:"com.zhy.User",  

  2.  fields:  

  3.  {  

  4.   username:"java.lang.String",  

  5.   password:"java.lang.String"  

  6.   

  7.  }  

  8. }  


[java] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. {class:"com.zhy.Article",  

  2.  fields:  

  3.  {  

  4.   content:"java.lang.String",  

  5.   title:"java.lang.String"  

  6.   

  7.  }  

  8. }  


ok,這樣的話,咱們一個簡單的annotation processor的教程就搞定了~~若是想學,必定要去試,各類試,不要怕麻煩,要是簡單誰都會,那還有什麼意義~~

這是一個很是簡單的例子,那麼具體到咱們的項目中如何使用呢?鑑於篇幅,可能只能在下一篇給你們繼續了。不過庫的雛形已經造成:

五、HyViewInject

ok,這就是基於上述的一個庫,主要用於Android的控件的注入,相似butterknife,尚在完善中,歡迎你們使用,fork or star ,咱們一塊兒完善。

sample的效果圖:

第一個Activity中一個TextView和ListView,第二個Activity一個TextView和Fragment,主要測試了Activity、Fragment、Adapter中注入控件。

相關文章
相關標籤/搜索