說道註解,居然還有各類分類,得,這記不住,咱們從註解的做用來反推其分類,幫助你們記憶,而後舉例強化你們的記憶,話說註解的做用:html
一、標記一些信息,這麼說可能太抽象,那麼我說,你見過@Override、@SuppressWarnings等,這類註解就是用於標識,能夠用做一些檢驗java
二、運行時動態處理,這個你們見得應該最多,在運行時拿到類的Class對象,而後遍歷其方法、變量,判斷有無註解聲明,而後作一些事情。相似上述三篇博文中的作法。android
三、編譯時動態處理,這個呢?就是咱們今天的主角了,通常這類註解會在編譯的時候,根據註解標識,動態生成一些類或者生成一些xml均可以,在運行時期,這類註解是沒有的~~會依靠動態生成的類作一些操做,由於沒有反射,效率和直接調用方法沒什麼區別~~~json
關於3,你們不明白,沒事,下文會詳談,使用這類註解的項目有:ParcelableGenerator、butterknife 、androidannotaion等。 數組
做用談完了,那麼若是你看到一個註解的聲明你如何去判斷他的做用呢?例如:微信
[java] view plaincopy網絡
@Retention(RetentionPolicy.CLASS) app
@Target ({ ElementType.FIELD, ElementType.TYPE }) 框架
public @interface InjectView eclipse
{
int value();
}
1秒鐘告訴我,它的做用是什麼?哈,你們可能會鬱悶,擦,我咋知道。其實能夠看這個註解上面的@Retention後面的值,設置的爲CLASS,說明就是編譯時動態處理的。
這個值是一個枚舉:有三個:SOURCE、RUNTIME、CLASS , 到這裏,是否是,搜噶,這三個11對應於上面三個做用。
好了,說完了註解的做用以及判斷方式,那麼你們能夠看到除了@Retention還有個@Target,@Target的值呢是一個ElementType[]數組。什麼意思呢?就是標明這個註解能標識哪些東西,好比類、變量、方法、甚至是註解自己(元註解)等。這個在:Android 進階 教你打造 Android 中的 IOC 框架 【ViewInject】 (上)有詳細說明。
好了,到此註解告一段落,你們只要記得註解的做用,以及如何去定義一個註解就好。
接下來進入咱們的主題編譯時註解。
對了,我建立了一個公衆號,會推送一些開源項目、最新博客、視頻等,關於博客涉及到的東西,也會提早給你們通知,能夠關注一下,謝謝,左側欄目,微信掃描便可。
那咱們說一下編寫過程。
一、建立一個類,繼承AbstractProcessor
[java] view plaincopy
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加進來。
二、建立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
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
{
}
哈,一秒鐘告訴我,哪一類做用的註解。
一、首先明確一下咱們的目標:
咱們有不少bean類,例如:
[java] view plaincopy
public class User
{
@Seriable
private String username;
@Seriable
private String password;
private String three;
private String four;
}
[java] view plaincopy
@Seriable
public class Article
{
private String title ;
private String content ;
}
看到有兩個普通的bean,上面聲明瞭咱們的註解,若是類上聲明註解咱們就將其全部的變量都生成一個json描述文件;若是僅僅是成員變量呢?那咱們只提取聲明的成員變量來動態生成。
相似以下的描述文件:
[html] view plaincopy
{class:"com.zhy.Article",
fields:
{
content:"java.lang.String",
title:"java.lang.String"
}
}
是否是以爲沒撒用處,其實用處大大滴,之後咱們會驗證。
二、編寫BeanProcessor
[java] view plaincopy
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();
}
}
}
}
代碼略長,可是註釋很清除,我來解釋一下,基本分爲兩個過程:一、找出標識註解的類或成員變量,封裝到maps中;二、遍歷maps爲每一個類建立json文件。咱們把文件輸出到了f://apt_test文件夾中,若是你沒有f盤神馬的,自行修改目錄。
到此,咱們寫完了~~那麼如何用呢?
爲了更好的演示,以及省篇幅,我錄成gif
注意我選擇的一些複選框,和一些默認複選框的選中狀態,我將其放在桌面上~~
將jar拷貝到libs下,若是是java項目,須要本身建立lib文件夾,本身手動引用。
而後就開始編寫bean吧:我這裏就寫了兩個類,一個User,一個Article,上面貼過代碼了。
這裏我是eclipse,你們若是是maven項目或者是別的什麼IDE,自行進行網絡搜索,這裏有個Android Studio下的使用,本身點擊哈,其實命令行也能夠。
下面咱們eclipse依然是個gif,否則得截一堆圖片:
假設咱們的jar已經拷貝到項目中了,進行以下操做
操做完成之後,那麼就能夠去f://apt_test中
打開便可看到:
[java] view plaincopy
{class:"com.zhy.User",
fields:
{
username:"java.lang.String",
password:"java.lang.String"
}
}
[java] view plaincopy
{class:"com.zhy.Article",
fields:
{
content:"java.lang.String",
title:"java.lang.String"
}
}
ok,這樣的話,咱們一個簡單的annotation processor的教程就搞定了~~若是想學,必定要去試,各類試,不要怕麻煩,要是簡單誰都會,那還有什麼意義~~
這是一個很是簡單的例子,那麼具體到咱們的項目中如何使用呢?鑑於篇幅,可能只能在下一篇給你們繼續了。不過庫的雛形已經造成:
五、HyViewInject
ok,這就是基於上述的一個庫,主要用於Android的控件的注入,相似butterknife,尚在完善中,歡迎你們使用,fork or star ,咱們一塊兒完善。
sample的效果圖:
第一個Activity中一個TextView和ListView,第二個Activity一個TextView和Fragment,主要測試了Activity、Fragment、Adapter中注入控件。