深入理解Java註解型別
Java註解在實際應用中很廣泛,目前很多主流的框架也採用了註解來提高效率,其實註解就是Java程式碼中的一個標記,也可以將它理解為物件,它有自己的相關屬性和值,只是不實現相關方法而已。下面我們通過一個例子來分析一下註解。
public class Test { //新增自定義註解 @FunAnno(name="我是方法a") public void fun_a(){ LogUtils.d("執行方法a"); } //新增java內建的註解 @Deprecated @SuppressWarnings("uncheck") public void fun_b(){ } /** * 定義一個註解 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface FunAnno{ String name() default ""; } }
上面程式碼中我們自定義了一個註解@FunAnno,註解用@interface來宣告,@Target(ElementType.METHOD)代表此註解應用在方法上,@Retention(RetentionPolicy.RUNTIME)代表此註解的生命週期儲存到執行時,String name() default ""用來宣告一個String型別的註解元素,我們可以在使用註解的時候對其進行賦值,當然在宣告的時候要用default定義其初始值。然後我們在fun_a方法中使用了@FunAnno註解。@Deprecated和@SuppressWarnings(“uncheck”)都是Java內建的註解,下面我們將會介紹它們的意義。
一:註解的語法
在上面例子中,我們定義註解的時候用到了@Target和@Retention註解,其實這兩個是Java提供的元註解,所謂的元註解就是標記其他註解的註解,我們常見的元註解有:@Target、@Retention、@Documented、@Inherited、@Repeatable(java8新增)。
1. @Target
用來約束註解應用的地方(比如方法、類、欄位等等),其註解元素接收一個列舉陣列ElementType[],代表註解應用範圍
//Target註解原始碼 @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { ElementType[] value(); } //value範圍 public enum ElementType { /**標明該註解可以用於類、介面(包括註解型別)或enum宣告*/ TYPE, /** 標明該註解可以用於欄位(域)宣告,包括enum例項 */ FIELD, /** 標明該註解可以用於方法宣告 */ METHOD, /** 標明該註解可以用於引數宣告 */ PARAMETER, /** 標明註解可以用於建構函式宣告 */ CONSTRUCTOR, /** 標明註解可以用於區域性變數宣告 */ LOCAL_VARIABLE, /** 標明註解可以用於註解宣告(應用於另一個註解上)*/ ANNOTATION_TYPE, /** 標明註解可以用於包宣告 */ PACKAGE, /** * 標明註解可以用於型別引數宣告(1.8新加入) */ TYPE_PARAMETER, /** * 型別使用宣告(1.8新加入) */ TYPE_USE }
當Target未表明任何value時,代表此註解可以應用到任何元素上,還可以採用陣列的方式表明value值,例如:@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})表明註解可以應用的範圍是{}內的所有型別。
該註解用來約束註解的生命週期,其接收三個值,分別是SOURCE(原始碼級別)、CLASS(類檔案級別)、RUNTIME(執行時級別),詳解如下:
-
SOURCE:該型別的註解資訊只會保留在原始碼裡,原始碼經過編譯後,註解資訊會被丟棄,不會保留在編譯好的class檔案裡。
-
CLASS:註解在class檔案中可用,但會被VM丟棄(該型別的註解資訊會保留在原始碼裡和class檔案裡,在執行的時候,不會載入到虛擬機器中),請注意,當註解未定義Retention值時,預設值是CLASS,如Java內建註解@SuppressWarnning等。
-
RUNTIME:註解資訊將在執行期(JVM)也保留,因此可以通過反射機制讀取註解的資訊(原始碼、class檔案和執行的時候都有註解的資訊)。
@Documented 可以讓被修飾的註解上傳javadoc
可以讓註解被繼承,這裡繼承的意思是通過使用@Inherited,可以讓子類Class物件使用getAnnotations()獲取父類被@Inherited修飾的註解,如下:
public class InheritedTest {
/**
* 被Inherited修飾的註解
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AnnoA {
}
/**
* 沒有Inherited修飾的註解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AnnoB {
}
@AnnoA
class A{
}
class B extends A{
}
@AnnoB
class C{
}
class D extends C{
}
public void test(){
// A instanceA=new A();
// D instanceD=new D();
LogUtils.d("使用了Inherited註解的情況:"+ Arrays.toString(B.class.getAnnotations()));
LogUtils.d("沒有使用了Inherited註解的情況:"+ Arrays.toString(D.class.getAnnotations()));
}
}
執行結果如下:
上面我們講解了註解的基本語法結構和常用的元註解分析,下面我們看看註解的元素值及其資料結構型別
二:註解元素及其資料型別
上面我們看到在用@FunAnno註解的時候,傳進了一個值name=“我是方法a”,這個name就是註解元素,其資料型別為String。我們在定義註解的時候,常常都會包含一些元素及其值,方便註解處理器使用。如下:
/**
* 使用註解標記Person類,給註解元素賦值
*/
@Person.PersonInfo(name = "王大錘",age = 18,male = true)
public class Person {
/**
* 定義一個註解,包含有三個註解元素,分別代表名字、年齡、性別
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface PersonInfo{
String name() default "";
int age() default -1;
boolean male() default false;
}
/**
* 註解元素的簡化使用方式,只定義value()即可
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Age{
int value() default -1;
}
/**
* 註解的簡化使用
*/
@Age(20)
class Lily{
}
}
上面程式碼我們展示了註解元素的定義及其使用,注意簡化方式的使用,如果簡化方式中不止一個元素的話, 還是要使用value=“”的方式去賦值元素的。下面是註解元素支援的資料型別:
-
所有基本型別(int,float,boolean,byte,double,char,long,short)
-
String
-
Class
-
enum
-
Annotation 註解巢狀
-
上述型別的陣列
下面我們用例子來展示所有註解元素資料型別的使用:
public class AnnoElement {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AnnoRef{
int value() default -1;
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AnnoElements{
//定義性別列舉
enum Gender{MALE,FEMALE};
//宣告列舉
Gender gender() default Gender.FEMALE;
//宣告string
String name() default "";
//宣告int
int age() default -1;
//宣告class型別
Class<?> Person() default Void.class;
//註解巢狀
AnnoRef ref() default @AnnoRef;
//陣列型別
String[] strs() default {""};
}
/**
* 註解元素的使用
*/
@AnnoElements(gender = AnnoElements.Gender.MALE,name = "java",
age = 100,Person = Person.class,ref = @AnnoRef(10),strs = {"a","b"})
class Test{
}
}
註解元素對預設值有嚴格的要求,物件不能用null表示,所以一般我們都用一些沒有意義的值來表示預設值。像這些註解元素及其對應的值,我們的註解處理器是可以獲取的,從而可以作為我們後面相關處理的依據。
三:Java內建註解
Java內建註解主要有三個,我們分別來看一下:
- @Override:用於標明此方法覆蓋了父類的方法,原始碼如下
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
- Deprecated:用於標明已經過時的方法或類,原始碼如下
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
- @SuppressWarnnings:用於有選擇的關閉編譯器對類、方法、成員變數、變數初始化的警告,其實現原始碼如下:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
其中value值接收如下:
deprecation:使用了不贊成使用的類或方法時的警告;
unchecked:執行了未檢查的轉換時的警告,例如當使用集合時沒有用泛型 (Generics) 來指定集合儲存的型別;
fallthrough:當 Switch 程式塊直接通往下一種情況而沒有 Break 時的警告;
path:在類路徑、原始檔路徑等中有不存在的路徑時的警告;
serial:當在可序列化的類上缺少 serialVersionUID 定義時的警告;
finally:任何 finally 子句不能正常完成時的警告;
all:關於以上所有情況的警告。
四:註解與反射的關係
在反射包的相關類中實現了都實現了AnnotatedElement介面,通過此介面我們可以利用反射技術獲得對應類的相關注解資訊,反射包的Constructor類、Field類、Method類、Package類和Class類都實現了AnnotatedElement介面,下面我們來看看AnnotatedElement中的相關方法:
方法名稱 | 返回值 | 說明 |
getAnnotation(Class<A> annotationClass) | <A extends Annotation> | 如果此元素存在指定型別的註解,那麼返回這些註解,否則返回null |
getAnnotations() | Annotation[] | 返回此元素的所有註解,包括從父類繼承的 |
isAnnotationPresent(Class<? extends Annotation> annotationClass) | boolean | 如果指定型別的註解存在次元素上,則返回true,否則返回false |
getDeclaredAnnotations() |
Annotation[] | 返回次元素上的所有註解,不包括從父類繼承的註解 |
案例演示如下:
public class AnnoElementTest{
@AnnoElementTest.AnnoA
public static class A{
}
//B 繼承A
@AnnoElementTest.AnnoB
public static class B extends A{
}
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AnnoA {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AnnoB {
}
public static void test(){
B instanceB=new B();
Class<?> classB=instanceB.getClass();
//根據指定註解型別獲得註解
AnnoB annoB=classB.getAnnotation(AnnoB.class);
LogUtils.d("根據指定註解獲取註解:"+annoB);
//獲取此元素上的所有註解,包括從父類繼承的
Annotation[] annotations=classB.getAnnotations();
LogUtils.d("獲取所有註解包括繼承的:"+ Arrays.toString(annotations));
//獲取此元素上所有註解,不包括繼承的
Annotation[] annotations1=classB.getDeclaredAnnotations();
LogUtils.d("獲取所有註解不包括繼承的:"+Arrays.toString(annotations1));
//判斷註解AnnoA是否在次元素上
boolean is=classB.isAnnotationPresent(AnnoB.class);
LogUtils.d("是否="+is);
}
}
執行結果如下:
在這裡要獲取到父類繼承的註解,此註解上必須有@Inherited標記,否則獲取不到。
五:執行時註解和編譯時註解
執行時註解,在執行時拿到類的Class物件,然後遍歷其方法、變數,判斷有無註解,然後做一些操作。
編譯時註解,在java的編譯階段,根據註解標識,動態生成一些類或xml檔案,在執行時期,這些註解是沒有的,我們是依靠動態生成的一些類做操作,由於沒有反射, 效率和直接呼叫方法沒什麼區別。
所以我們可以看到,編譯時註解的效率要比執行時註解高,很多框架如ButterKnife、EventBus都是用的編譯時註解。下面我們通過例子去看看,執行時註解和編譯時註解是怎麼的一個實現過程。
1.執行時註解的處理
我們以通過執行時註解去建立一個數據庫表為例展開, 先來看看註解的定義,如下:
/**
* Created by XR_liu on 2018/11/22.
* 執行時註解
*/
public class RAnnotation {
/**
* 約束註解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Contrains{
//是否為主鍵
boolean primaryKey() default false;
//是否允許為null
boolean allowNull() default false;
//是否唯一
boolean isUnique() default false;
}
/**
* 實體類註解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Entity{
String tableName() default "";
}
/**
* 整型欄位
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FeildInteger{
//對應的欄位名
String name() default "";
//巢狀註解
Contrains contrain() default @Contrains;
}
/**
* 字串欄位
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FeildString{
String name() default "";
int varchar() default 30;
Contrains contrain() default @Contrains;
}
}
我們定義的上面幾個註解,都是為建立一個數據庫表服務的, 注意生命週期都應該標註為RUNTIME,只有這樣我們才能在執行時通過反射獲取相關資訊。下面通過定義一個Person類來看註解的使用:
/**
* Created by XR_liu on 2018/11/22.
*/
@RAnnotation.Entity
public class Person {
//主鍵
@RAnnotation.FeildString(contrain = @RAnnotation.Contrains(primaryKey = true))
private String id;
@RAnnotation.FeildString
private String name;
@RAnnotation.FeildInteger
private int age;
//允許為null
@RAnnotation.FeildString(contrain = @RAnnotation.Contrains(allowNull = true))
private String address;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
下面我們就開始自己編寫一個註解處理器,關鍵邏輯是通過Class物件去獲取有註解的欄位,然後再獲取對應註解的相關資訊,然後就可以根據這些資訊去拼接一個SQL語句了,如下:
/**
* 通過一個實體類返回一個建立資料庫表的SQL語句
*
* @return
*/
public static String createTableSql(Class<?> cl) {
String sql = null;
//獲取class物件
Class<?> clazz = cl;
RAnnotation.Entity entity = clazz.getAnnotation(RAnnotation.Entity.class);
String tabelName; //資料庫名稱
if (entity == null || entity.tableName() == "") {
tabelName = clazz.getSimpleName().toUpperCase();
}
tabelName = entity.tableName();
//所有欄位名的集合
List<String> columNames = new ArrayList<>();
//通過反射獲取class的所有欄位
for (Field field : clazz.getDeclaredFields()) {
String columName = null;
//獲取欄位上的所有註解
Annotation[] annotations = field.getDeclaredAnnotations();
if (annotations.length < 1) {
continue; //不屬於表的欄位
}
//整型欄位
if (annotations[0] instanceof RAnnotation.FeildInteger) {
RAnnotation.FeildInteger feildInteger = (RAnnotation.FeildInteger) annotations[0];
//給欄位名賦值
columName = "".equals(feildInteger.name()) ? field.getName().toUpperCase() : feildInteger.name();
//儲存構建SQL語句片段
columNames.add(columName + " INT" + getContrains(feildInteger.contrain()));
}
//string欄位
if (annotations[0] instanceof RAnnotation.FeildString) {
RAnnotation.FeildString feildString = (RAnnotation.FeildString) annotations[0];
columName = "".equals(feildString.name()) ? field.getName().toUpperCase() : feildString.name();
columNames.add(columName + " VARCHAR(" + feildString.varchar() + ")" + getContrains(feildString.contrain()));
}
//構建資料庫表語句
StringBuilder createSql = new StringBuilder("CREATE TABLE " + tabelName + "(");
for (String colum : columNames) {
createSql.append("\n " + colum + ",");
}
sql = createSql.substring(0, createSql.length() - 1) + ")";
}
return sql;
我們來看一下執行結果:
到這裡成功地返回了一個正確的SQL語句!附上原始碼:https://github.com/jiusetian/AndroidStudyData/tree/master/app/src/main/java/com/androidstudydata/annotation
上面是執行時註解的例子,其中關鍵的是利用Class物件去尋找註解的相關資訊,下面我們來看看編譯時註解又是怎麼回事的。
2.編譯時註解
編譯時註解主要利用的註解處理器(Annotation Processor)是javac內建的一個用於編譯時掃描和處理註解的工具,就是在原始碼的編譯階段,通過註解處理器,我們可以獲得檔案中註解的相關內容。常見的用途是,我們在獲得註解相關資料之後,通過去生成有規律的程式碼,解決程式設計過程中的重複工作,大大提高了效率,比如ButterKnife、Dagger2等框架。
下面我們模仿butterknife的實現原理,自己去實現一個類似功能的簡單例子,首先我們在註解實現的Java Module的Gradle檔案中新增如下配置:
compile 'com.google.auto.service:auto-service:1.0-rc2'//谷歌的幫助我們快速實現註解處理器
compile 'com.squareup:javapoet:1.7.0'//用來生成java檔案的
定義一個註解@BindView,如下:
/**
* Created by XR_liu on 2018/11/24.
*/
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value() default -1;
}
當然註解的生命週期是編譯階段,適用範圍為欄位。定義一個注入view例項公共介面,如下
/**
* Created by XR_liu on 2018/11/24.
*/
public interface ViewInject<T> {
//T是指使用註解的類,viewOwner是註解view的持有者
void inject(T t, Object viewOwner);
}
下面我們來看看怎麼去實現這個註解處理器的,這個是關鍵,先看看程式碼:
/**
* Created by XR_liu on 2018/11/24.
*/
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor{
private Elements elementUtils;
private Map<String, CodeCreater> codeCreaterMap = new HashMap<String, CodeCreater>();
@Override
public SourceVersion getSupportedSourceVersion()
{
return SourceVersion.latestSupported();
}
@Override
public synchronized void init(ProcessingEnvironment processingEnv)
{
super.init(processingEnv);
//Elements mElementUtils;跟元素相關的輔助類,幫助我們去獲取一些元素相關的資訊。
elementUtils = processingEnv.getElementUtils();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(BindView.class.getCanonicalName());
return supportTypes;
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
codeCreaterMap.clear();
//element所代表的元素只在編譯期可見,用於儲存元素在編譯期的各種狀態,而Type所代表的元素是執行期可見,用於儲存元素在編譯期的各種狀態
//通過roundEnv.getElementsAnnotatedWith拿到我們通過@BindView註解的元素,這裡返回值,按照我們的預期應該是VariableElement集合,因為我們用於成員變數上。
Set<? extends Element> elesWithBind = roundEnv.getElementsAnnotatedWith(BindView.class); //所有被Bind註解標識的元素,此時的Element是一個通用介面
//一、收集資訊
//接下來for迴圈我們的元素,首先檢查型別是否是VariableElement(代表變數、欄位)
for (Element element : elesWithBind)
{
//檢查element型別
checkAnnotationValid(element, BindView.class);
//field type
VariableElement variableElement = (VariableElement) element; //因為上面檢查過了,所以這裡強轉
//class type,拿到對應的類元素
TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
//full class name,類的全路徑名
String fqClassName = classElement.getQualifiedName().toString();
//如果沒有生成才會去生成一個新的,。
CodeCreater codeCreater = codeCreaterMap.get(fqClassName);
if (codeCreater == null)
{
//如果對應的類還沒有生成代理類,才去建立一個並且儲存
codeCreater = new CodeCreater(elementUtils, classElement);
codeCreaterMap.put(fqClassName, codeCreater);
}
//接下來,會將與該類對應的且被@BindView宣告的VariableElement加入到codeCreater中去,key為我們宣告時填寫的id,即View的id
BindView bindAnnotation = variableElement.getAnnotation(BindView.class); //獲取對應的註解物件
int id = bindAnnotation.value();
//如果這個欄位屬於某個類,這裡就會儲存到同一個codeCreater中,因為不同類的時候,這裡的codeCreater是不同的
codeCreater.injectVariables.put(id , variableElement);
}
//二、生成代理類
for (String key : codeCreaterMap.keySet())
{
CodeCreater codeCreater = codeCreaterMap.get(key);
try
{
JavaFileObject jfo = processingEnv.getFiler().createSourceFile(
codeCreater.getCreaterClassFullName(),
codeCreater.getTypeElement());
Writer writer = jfo.openWriter();
//寫入自動生成的程式碼
writer.write(codeCreater.generateJavaCode());
writer.flush();
writer.close();
} catch (IOException e)
{
error(codeCreater.getTypeElement(),
"Unable to write injector for type %s: %s",
codeCreater.getTypeElement(), e.getMessage());
}
}
return true;
}
//要field欄位和非private修飾才有效
private boolean checkAnnotationValid(Element annotatedElement, Class clazz)
{
if (annotatedElement.getKind() != ElementKind.FIELD)
{
error(annotatedElement, "%s must be declared on field.", clazz.getSimpleName());
return false;
}
//是否為私有的欄位
if (annotatedElement.getModifiers().contains(PRIVATE))
{
error(annotatedElement, "%s() must can not be private.", annotatedElement.getSimpleName());
return false;
}
return true;
}
private void error(Element element, String message, Object... args)
{
if (args.length > 0)
{
message = String.format(message, args);
}
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, message, element);
}
}
上面通過繼承系統的AbstractProcessor來自己實現註解處理器,我們主要重寫process方法,上面涉及到了一個很重要的介面Element(元素),這個介面有幾個實現,代表欄位元素、類元素、方法元素等等。就是通過這些元素,我們才能獲得元素對應的註解資訊。其中CodeCreater類是一個程式碼生成器,通過它我們可以為每一個使用註解的activity或view自動生成一個對應的ViewInject類,這個類就是給被BindView註解標識的元素賦值的。我們來看看程式碼:
public class CodeCreater {
private String packageName;
private String createrClassName;
private TypeElement typeElement;
//儲存findViewById要用到的id值和對應的變數元素
public Map<Integer, VariableElement> injectVariables = new HashMap<>();
public static final String SUFFIX = "ViewInject";
public CodeCreater(Elements elementUtils, TypeElement classElement) {
this.typeElement = classElement;
//獲取註解元素類的全包名
PackageElement packageElement = elementUtils.getPackageOf(classElement);
String packageName = packageElement.getQualifiedName().toString();
//classname
String className = classElement.getQualifiedName().toString().substring(packageName.length() + 1);
this.packageName = packageName;
this.createrClassName = className + "$$" +SUFFIX;
}
public String generateJavaCode() {
StringBuilder builder = new StringBuilder();
builder.append("/* Generated code. Do not modify!*/\n");
builder.append("package ").append(packageName).append(";\n\n"); //包路徑
builder.append("import com.lib_java.compileAnnotation.*;\n"); //匯入包路徑
builder.append('\n');
//類的宣告並且繼承ViewInject<T>介面,其中T是使用註解的那個類
builder.append("public class ").append(createrClassName).append(" implements " + CodeCreater.SUFFIX +
"<" + typeElement.getQualifiedName() + ">");
builder.append(" {\n\n");
generateMethods(builder);
builder.append('\n');
builder.append("}\n");
return builder.toString();
}
private void generateMethods(StringBuilder builder) {
//參加inject方法,實際上是實現ViewInject介面的方法
builder.append(" @Override\n ");
builder.append(" public void inject(" + typeElement.getQualifiedName() + " master, Object viewOwner ) {\n");
for (int id : injectVariables.keySet()) {
VariableElement element = injectVariables.get(id); //id對應的變數
String name = element.getSimpleName().toString(); //變數名
String type = element.asType().toString(); //變數型別
//master代表使用註解的那個類的全限定名,所以master.name代表被註解的那個變數,其實也就是對應的activity物件
builder.append(" if(viewOwner instanceof android.app.Activity){\n"); //如果master是activity
builder.append(" master." + name).append(" = ");
builder.append("(" + type + ")(((android.app.Activity)viewOwner).findViewById( " + id + "));\n");
builder.append("\n }else{\n");
builder.append(" master." + name).append(" = ");
builder.append("(" + type + ")(((android.view.View)viewOwner).findViewById( " + id + "));\n"); //master是View物件
builder.append("\n }\n");
}
builder.append(" }\n");
}
public String getCreaterClassFullName() {
return packageName + "." + createrClassName;
}
public TypeElement getTypeElement() {
return typeElement;
}
}
我們可以看到,主要作用就是通過字串的拼接去生成一個類,這個類的名字為使用者的類名+“$$ViewInject”。什麼註解處理器也完成了,下面我們就暴露一個使用介面ViewInjector類,用這個類來完成最後的繫結工作,如下:
public class ViewInjector
{
private static final String SUFFIX = "$$ViewInject";
public static void injectView(Activity activity)
{
//找到自動生成的代理類
ViewInject proxyActivity = findProxyActivity(activity);
proxyActivity.inject(activity, activity); //執行注入方法,兩個引數都是對應的activity物件
}
//object是持有被註解欄位的物件,view是指我們要findViewById那個view物件, 也可以是activity物件
public static void injectView(Object object, View view)
{
ViewInject proxyActivity = findProxyActivity(object);
proxyActivity.inject(object, view);
}
private static ViewInject findProxyActivity(Object activity)
{
try
{
Class clazz = activity.getClass();
Class injectorClazz = Class.forName(clazz.getName() + SUFFIX);
return (ViewInject) injectorClazz.newInstance();
} catch (ClassNotFoundException e)
{
e.printStackTrace();
} catch (InstantiationException e)
{
e.printStackTrace();
} catch (IllegalAccessException e)
{
e.printStackTrace();
}
throw new RuntimeException(String.format("can not find %s , something when compiler.", activity.getClass().getSimpleName() + SUFFIX));
}
}
下面我們來測試一下
public class MainActivity extends AppCompatActivity {
@BindView(R.id.bindView)
Button BindBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewInjector.injectView(this); //呼叫注入
BindBtn.setText("成功綁定了view");
}
最後測試是成功通過的,最後我們來看看自動生成的那個MainActivity$$ViewInject是怎麼樣的,如下
/* Generated code. Do not modify!*/
package com.androidstudydata;
import com.lib_java.compileAnnotation.*;
public class MainActivity$$ViewInject implements ViewInject<com.androidstudydata.MainActivity> {
@Override
public void inject(com.androidstudydata.MainActivity master, Object viewOwner ) {
if(viewOwner instanceof android.app.Activity){
master.BindBtn = (android.widget.Button)(((android.app.Activity)viewOwner).findViewById( 2131230762));
}else{
master.BindBtn = (android.widget.Button)(((android.view.View)viewOwner).findViewById( 2131230762));
}
}
}
可以看到,其實到最後還是通過findViewById方法去給view賦值的,只是我們通過編譯時的註解處理器,將這些程式碼自動生成了,這樣就節省了我們很多時間,提高了效率。
好了,關於註解的知識就講到這裡了!附上原始碼: