1、概述
記得很久以前,寫過幾篇部落格,容我列舉一下:
Android 進階 教你打造 Android 中的 IOC 框架 【ViewInject】 (上)
Android 進階 教你打造 Android 中的 IOC 框架 【ViewInject】 (下)
Android 框架煉成 教你如何寫元件間通訊框架EventBus
大家可以關注下這些部落格的評論,不管咋樣,大家對於效能的考慮還是很多呢,一看到這類的框架,不解析還好,只要解析出來是註解和反射,必然的一個問題就是:這樣會不會影響效能呀?嗯,肯定會有效能的損耗,那麼我就再考慮有沒有更好的實現方式呢?既可以實現注入,還能保證效能無損耗呢?好訊息來說,是有噠,對沒錯,上述部落格實現方式,包括xutils , afinal 目前的注入使用的都是執行時註解,當然了還有一類註解叫做編譯時註解。
2、註解
說道註解,竟然還有各種分類,得,這記不住,我們從註解的作用來反推其分類,幫助大家記憶,然後舉例強化大家的記憶,話說註解的作用:
1、標記一些資訊,這麼說可能太抽象,那麼我說,你見過@Override、@SuppressWarnings等,這類註解就是用於標識,可以用作一些檢驗
2、執行時動態處理,這個大家見得應該最多,在執行時拿到類的Class物件,然後遍歷其方法、變數,判斷有無註解宣告,然後做一些事情。類似上述三篇博文中的做法。
3、編譯時動態處理,這個呢?就是我們今天的主角了,一般這類註解會在編譯的時候,根據註解標識,動態生成一些類或者生成一些xml都可以,在執行時期,這類註解是沒有的~~會依靠動態生成的類做一些操作,因為沒有反射,效率和直接呼叫方法沒什麼區別~~~
關於3,大家不明白,沒事,下文會詳談,使用這類註解的專案有:ParcelableGenerator、butterknife 、androidannotaion等。
作用談完了,那麼如果你看到一個註解的宣告你如何去判斷他的作用呢?例如:
1 2 3 4 5 6 |
@Retention(RetentionPolicy.CLASS) @Target({ ElementType.FIELD, ElementType.TYPE }) public @interface InjectView { int value(); } |
1秒鐘告訴我,它的作用是什麼?哈,大家可能會鬱悶,擦,我咋知道。其實可以看這個註解上面的@Retention後面的值,設定的為CLASS,說明就是編譯時動態處理的。
這個值是一個列舉:有三個:SOURCE、RUNTIME、CLASS , 到這裡,是不是,搜噶,這三個11對應於上面三個作用。
好了,說完了註解的作用以及判斷方式,那麼大家可以看到除了@Retention還有個@Target,@Target的值呢是一個ElementType[]陣列。什麼意思呢?就是標明這個註解能標識哪些東西,比如類、變數、方法、甚至是註解本身(元註解)等。這個在:Android 進階 教你打造 Android 中的 IOC 框架 【ViewInject】 (上)有詳細說明。
好了,到此註解告一段落,大家只要記得註解的作用,以及如何去定義一個註解就好。
接下來進入我們的主題編譯時註解。
對了,我建立了一個公眾號,會推送一些開源專案、最新部落格、視訊等,關於部落格涉及到的東西,也會提前給大家通知,可以關注一下,謝謝,左側欄目,微信掃描即可。
3、編譯時註解
那我們說一下編寫過程。
1、建立一個類,繼承AbstractProcessor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
package com.zhy.util.ioc.processor; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.TypeElement; @SupportedAnnotationTypes("com.zhy.util.ioc.annotation.InjectView") @SupportedSourceVersion(SourceVersion.RELEASE_6) public class ViewInjectProcessorBeta extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { // TODO Auto-generated method stub return false; } } |
這個類上可以新增註解:
@SupportedAnnotationTypes的值為當前類支援的註解的完整類路徑,支援萬用字元。
@SupportedSourceVersion 標識該處理器支援的原始碼版本
除此以外還有一個@SupportedOptions,這個一般是命令列時候用的,設定一些選項,but,命令列我不熟,因此:略。
注:如果大家找不到AbstractProcessor,記得右鍵build-path add library把jdk加進來。
2、建立resources等檔案。
這個對專案的一個結構有著固定的要求,下面我通過一張圖來說:
可以看到,在我們的專案中呢,還需要建立一個resources這樣的source folder ,右鍵 new sources folder即可。
然後在裡面建立META-INF/services/javax.annotation.processing.Processor檔案,這個檔案中去寫我們處理器的類完整路徑。
經過上述兩部,我們的編寫環境就OK了。
4、完整例子
下面我們通過一個例子來給大家演示編譯時動態生成資料,我們的效果是這樣的,使用者編寫一堆bean,例如User類,我們通過註解提取屬性動態生成一個json檔案,以及一個代理類,注意是編譯時生成。
注:以下為一個教學示例,無任何使用價值。
那麼我們依然分為步驟來做:
1、建立編寫環境
javax.annotation.processing.Processor裡面寫的是:com.zhy.annotationprocess.processor.BeanProcessor
我們還建立了一個註解:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package com.zhy.annotationprocess.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ ElementType.FIELD, ElementType.TYPE }) @Retention(RetentionPolicy.CLASS) public @interface Seriable { } |
哈,一秒鐘告訴我,哪一類作用的註解。
2、動態生成資料
1、首先明確一下我們的目標:
我們有很多bean類,例如:
1 2 3 4 5 6 7 8 9 10 |
public class User { @Seriable private String username; @Seriable private String password; private String three; private String four; } |
1 2 3 4 5 6 |
@Seriable public class Article { private String title ; private String content ; } |
看到有兩個普通的bean,上面宣告瞭我們的註解,如果類上宣告註解我們就將其所有的變數都生成一個json描述檔案;如果僅僅是成員變數呢?那我們只提取宣告的成員變數來動態生成。
類似如下的描述檔案:
1 2 3 4 5 6 7 |
{class:"com.zhy.Article", fields: { content:"java.lang.String", title:"java.lang.String" } } |
是不是覺得沒撒用處,其實用處大大滴,以後我們會驗證。
2、編寫BeanProcessor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
package com.zhy.annotationprocess.processor; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; import com.zhy.annotationprocess.annotation.Seriable; @SupportedAnnotationTypes("com.zhy.annotationprocess.annotation.Seriable") @SupportedSourceVersion(SourceVersion.RELEASE_6) public class BeanProcessor extends AbstractProcessor { // 元素操作的輔助類 Elements elementUtils; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); // 元素操作的輔助類 elementUtils = processingEnv.getElementUtils(); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { // 獲得被該註解宣告的元素 Set<? extends Element> elememts = roundEnv .getElementsAnnotatedWith(Seriable.class); TypeElement classElement = null;// 宣告類元素 List<VariableElement> fields = null;// 宣告一個存放成員變數的列表 // 存放二者 Map<String, List<VariableElement>> maps = new HashMap<String, List<VariableElement>>(); // 遍歷 for (Element ele : elememts) { // 判斷該元素是否為類 if (ele.getKind() == ElementKind.CLASS) { classElement = (TypeElement) ele; maps.put(classElement.getQualifiedName().toString(), fields = new ArrayList<VariableElement>()); } else if (ele.getKind() == ElementKind.FIELD) // 判斷該元素是否為成員變數 { VariableElement varELe = (VariableElement) ele; // 獲取該元素封裝型別 TypeElement enclosingElement = (TypeElement) varELe .getEnclosingElement(); // 拿到key String key = enclosingElement.getQualifiedName().toString(); fields = maps.get(key); if (fields == null) { maps.put(key, fields = new ArrayList<VariableElement>()); } fields.add(varELe); } } for (String key : maps.keySet()) { if (maps.get(key).size() == 0) { TypeElement typeElement = elementUtils.getTypeElement(key); List<? extends Element> allMembers = elementUtils .getAllMembers(typeElement); if (allMembers.size() > 0) { maps.get(key).addAll(ElementFilter.fieldsIn(allMembers)); } } } generateCodes(maps); return true; } private void generateCodes(Map<String, List<VariableElement>> maps) { File dir = new File("f://apt_test"); 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(); } } } } |
程式碼略長,但是註釋很清除,我來解釋一下,基本分為兩個過程:1、找出標識註解的類或成員變數,封裝到maps中;2、遍歷maps為每個類建立json檔案。我們把檔案輸出到了f://apt_test資料夾中,如果你沒有f盤神馬的,自行修改目錄。
3、使用
到此,我們寫完了~~那麼如何用呢?
1、匯出jar
為了更好的演示,以及省篇幅,我錄成gif
注意我選擇的一些核取方塊,和一些預設核取方塊的選中狀態,我將其放在桌面上~~
2、新建一個android或java專案
將jar拷貝到libs下,如果是java專案,需要自己建立lib資料夾,自己手動引用。
然後就開始編寫bean吧:我這裡就寫了兩個類,一個User,一個Article,上面貼過程式碼了。
3、啟用annotation processor
這裡我是eclipse,大家如果是maven專案或者是別的什麼IDE,自行進行網路搜尋,這裡有個Android Studio下的使用,自己點選哈,其實命令列也可以。
下面我們eclipse依然是個gif,不然得截一堆圖片:
假設我們的jar已經拷貝到專案中了,進行如下操作
操作完成以後,那麼就可以去f://apt_test中
開啟即可看到:
1 2 3 4 5 6 7 8 |
{class:"com.zhy.User", fields: { username:"java.lang.String", password:"java.lang.String" } } |
1 2 3 4 5 6 7 8 |
{class:"com.zhy.Article", fields: { content:"java.lang.String", title:"java.lang.String" } } |
ok,這樣的話,我們一個簡單的annotation processor的教程就搞定了~~如果想學,一定要去試,各種試,不要怕麻煩,要是簡單誰都會,那還有什麼意義~~
這是一個非常簡單的例子,那麼具體到我們的專案中如何使用呢?鑑於篇幅,可能只能在下一篇給大家繼續了。不過庫的雛形已經形成:
5、HyViewInject
ok,這就是基於上述的一個庫,主要用於Android的控制元件的注入,類似butterknife,尚在完善中,歡迎大家使用,fork or star ,我們一起完善。
sample的效果圖:
第一個Activity中一個TextView和ListView,第二個Activity一個TextView和Fragment,主要測試了Activity、Fragment、Adapter中注入控制元件。