在前面的文章中,講解了註解和編譯時註解等一些列相關的內容,爲了更加全面和真是的瞭解Android
編譯時註解在實戰項目中的使用,本文采起實現主流框架butterknife
注入view
去全面認識編譯時註解。javascript
註解專欄-博客html
先來張圖壓壓驚,實現效果butterknife
的view
綁定java
仿照butterknife
實現了@BindView
註解,經過WzgJector.bind
方法綁定當前MainActivity
,總體和butterknife
使用徹底如出一轍,這裏爲了區分簡單的把butterknife
更名了WzgJector
android
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv_msg)
TextView tvMsg;
@BindView(R.id.tv_other)
TextView tvOther;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
WzgJector.bind(this);
if(tvMsg!=null){
tvMsg.setText("我已經成功初始化了");
}
if(tvOther!=null){
tvOther.setText("我就來看看而已");
}
}
}複製代碼
實現的思路和Android
編譯時註解-初認識實現原理大體同樣,因此這裏不重複闡述重複的步驟,重點講解提高的技術點,因此須要在瞭解基本編譯時註解的前提下繼續下面的學習git
Android
編譯時註解-初認識github
這裏使用了java
和android
自帶的註解,初始一個BindView
註解,同時指定了@Target
爲FIELD
,註解BindView
帶有一個初始的int
參數及時使用時的view-id
api
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}複製代碼
對java
和android
自帶的註解不太清楚的同窗可參考下面兩篇文章app
Android-註解詳解ide
Element
詳解Element
有了註解,必然須要有一個對應的註解處理器去處理註解,可是在處理註解的時候須要充分的瞭解註解處理器中的process
方法及時核心的編譯代碼,而process
方法的核心即是Element
對象,因此在講解註解處理器前,須要對Element
有全面的認識,方能事半功倍。
因爲Element
的知識內容的複雜性,這裏重點講解核心內容,基本使用徹底是足夠了
源碼:
public interface Element extends AnnotatedConstruct {
TypeMirror asType();
ElementKind getKind();
Set<Modifier> getModifiers();
Name getSimpleName();
Element getEnclosingElement();
List<? extends Element> getEnclosedElements();
boolean equals(Object var1);
int hashCode();
List<? extends AnnotationMirror> getAnnotationMirrors();
<A extends Annotation> A getAnnotation(Class<A> var1);
<R, P> R accept(ElementVisitor<R, P> var1, P var2);
}複製代碼
可看出其實Element
是定義的一個接口,定義了外部調用暴露出的接口
方法 | 解釋 |
---|---|
asType | 返回此元素定義的類型 |
getKind | 返回此元素的種類:包、類、接口、方法、字段...,以下枚舉值 |
getModifiers | 返回此元素的修飾符,以下枚舉值 |
getSimpleName | 返回此元素的簡單名稱,好比activity名 |
getEnclosingElement | 返回封裝此元素的最裏層元素,若是此元素的聲明在詞法上直接封裝在另外一個元素的聲明中,則返回那個封裝元素; 若是此元素是頂層類型,則返回它的包若是此元素是一個包,則返回 null; 若是此元素是一個泛型參數,則返回 null. |
getAnnotation | 返回此元素針對指定類型的註解(若是存在這樣的註解),不然返回 null。註解能夠是繼承的,也能夠是直接存在於此元素上的 |
getKind
方法其中getKind
方法比較特殊,getKind()
方法來獲取具體的類型,方法返回一個枚舉值TypeKind
源碼:
public enum TypeKind {
/** The primitive type {@code boolean}. */
BOOLEAN,
/** The primitive type {@code byte}. */
BYTE,
/** The primitive type {@code short}. */
SHORT,
/** The primitive type {@code int}. */
INT,
/** The primitive type {@code long}. */
LONG,
/** The primitive type {@code char}. */
CHAR,
/** The primitive type {@code float}. */
FLOAT,
/** The primitive type {@code double}. */
DOUBLE,
/** The pseudo-type corresponding to the keyword {@code void}. */
VOID,
/** A pseudo-type used where no actual type is appropriate. */
NONE,
/** The null type. */
NULL,
/** An array type. */
ARRAY,
/** A class or interface type. */
DECLARED,
/** A class or interface type that could not be resolved. */
ERROR,
/** A type variable. */
TYPEVAR,
/** A wildcard type argument. */
WILDCARD,
/** A pseudo-type corresponding to a package element. */
PACKAGE,
/** A method, constructor, or initializer. */
EXECUTABLE,
/** An implementation-reserved type. This is not the type you are looking for. */
OTHER,
/** A union type. */
UNION,
/** An intersection type. */
INTERSECTION;
}複製代碼
Element
子類Element
有五個直接子接口,它們分別表明一種特定類型的元素
Tables | Are |
---|---|
TypeElement | 一個類或接口程序元素 |
VariableElement | 一個字段、enum 常量、方法或構造方法參數、局部變量或異常參數 |
ExecutableElement | 某個類或接口的方法、構造方法或初始化程序(靜態或實例),包括註解類型元素 |
PackageElement | 一個包程序元素 |
TypeParameterElement | 通常類、接口、方法或構造方法元素的泛型參數 |
五個子類各有各的用處而且有各類獨立的方法,在使用的時候能夠強制將Element
對象轉換成其中的任一一種,可是前提是知足條件的轉換,否則會拋出異常。
其中最核心的兩個子分別是TypeElement
和VariableElement
TypeElement
詳解TypeElement
定義的一個類或接口程序元素,至關於當前註解所在的class
對象,及時本案例使用代碼中的MainActivity
源碼以下:
public interface TypeElement extends Element, Parameterizable, QualifiedNameable {
List<? extends Element> getEnclosedElements();
NestingKind getNestingKind();
Name getQualifiedName();
Name getSimpleName();
TypeMirror getSuperclass();
List<? extends TypeMirror> getInterfaces();
List<? extends TypeParameterElement> getTypeParameters();
Element getEnclosingElement();
}複製代碼
這裏講解主要的方法的含義
方法 | 解釋 |
---|---|
getNestingKind | 返回此類型元素的嵌套種類 |
getQualifiedName | 返回此類型元素的徹底限定名稱。更準確地說,返回規範 名稱。對於沒有規範名稱的局部類和匿名類,返回一個空名稱. |
getSuperclass | 返回此類型元素的直接超類。若是此類型元素表示一個接口或者類 java.lang.Object,則返回一個種類爲 NONE 的 NoType |
getInterfaces | 返回直接由此類實現或直接由此接口擴展的接口類型 |
getTypeParameters | 按照聲明順序返回此類型元素的形式類型參數 |
VariableElement
詳解源碼:
public interface VariableElement extends Element {
Object getConstantValue();
Name getSimpleName();
Element getEnclosingElement();
}複製代碼
這裏VariableElement
除了擁有Element
的方法之外還有如下兩個方法
方法 | 解釋 |
---|---|
getConstantValue | 變量初始化的值 |
getEnclosingElement | 獲取相關類信息 |
註解處理器須要兩個步驟的處理:
1.收集先關的信息
2.生成處理類
對Element
有了全面的瞭解事後,註解處理器即可很輕鬆的學習了,先來看看簡單版本的BindView
處理
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);
//1、收集信息
for (Element element : elements) {
/*檢查類型*/
if (!(element instanceof VariableElement)) {
return false;
}
VariableElement variableElement = (VariableElement) element;
/*獲取類信息*/
TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
/*類的絕對路徑*/
String qualifiedName = typeElement.getQualifiedName().toString();
/*類名*/
String clsName = typeElement.getSimpleName().toString();
/*獲取包名*/
String packageName = processingEnv.getElementUtils().getPackageOf(typeElement).getQualifiedName().toString();
BindView annotation = variableElement.getAnnotation(BindView.class);
int id = annotation.value();
/*參數名*/
String name = variableElement.getSimpleName().toString();
/*參數對象類*/
String type = variableElement.asType().toString();
ClassName InterfaceName = ClassName.bestGuess("com.example.annotation.api.ViewInjector");
ClassName host = ClassName.bestGuess(qualifiedName);
MethodSpec main = MethodSpec.methodBuilder("inject")
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.addAnnotation(Override.class)
.addParameter(host, "host")
.addParameter(Object.class, "object")
.addCode(""
+ " if(object instanceof android.app.Activity){\n"
+ " host." + name + " = (" + type + ")(((android.app.Activity)object).findViewById(" + id + "));\n"
+ " }\n"
+ "else{\n"
+ " host." + name + " = (" + type + ")(((android.view.View)object).findViewById(" + id + "));\n"
+ "}\n")
.build();
TypeSpec helloWorld = TypeSpec.classBuilder(clsName + "ViewInjector")
.addModifiers(Modifier.PUBLIC)
.addMethod(main)
.addSuperinterface(ParameterizedTypeName.get(InterfaceName, host))
.build();
try {
// 生成 com.example.HelloWorld.java
JavaFile javaFile = JavaFile.builder(packageName, helloWorld)
.addFileComment(" This codes are generated automatically. Do not modify!")
.build();
// 生成文件
javaFile.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}複製代碼
大致的思路,先判斷Element
類型,若是是VariableElement
則繼續獲取相關的包名(這裏必須在app
包名一致,否則獲取不到android
類)類對象信息,以及@BindView
註解修飾的參數數據;最後將全部須要的數據經過javapoet
和Filer
自動編譯建立一個java文件
最後獲得的生成類:
package com.wzgiceman.viewinjector;
import com.example.ViewInjector;
import java.lang.Object;
import java.lang.Override;
public class MainActivityViewInjector implements ViewInjector<MainActivity> {
@Override
public void inject(MainActivity host, Object object) {
if(object instanceof android.app.Activity){
host.tvMsg = (android.widget.TextView)(((android.app.Activity)object).findViewById(2131492945));
}
else{
host.tvMsg = (android.widget.TextView)(((android.view.View)object).findViewById(2131492945));
}
}
}複製代碼
上面的簡單處理器中,只是單純的判斷一個註解狀況,在信息收集的處理上簡化了,致使當前處理器只能同時處理當前相同類中的莫一個註解這裏只初始化了tvMsg
對象,tvOther
並無初始化,固然這是不符合實際需求的,下面來優化收集和處理方案。
優化方案其實就是多了一步信息的記錄的工做
首先建立一個類信息對象,其中包含了一下的屬性,其中varMap
即是記錄當前類中全部註解相關的信息
public class VariMsg {
/*包名*/
private String pk;
/*類名*/
private String clsName;
/*註解對象*/
private HashMap<Integer,VariableElement> varMap;
}複製代碼
BindViewProcessors
1.初始一個map記錄VariMsg
對象,由於process方法可能會屢次調用,因此須要每次都clear
一遍
Map<String, VariMsg> veMap = new HashMap<>();複製代碼
2.記錄信息
經過veMap
記錄全部的相關信息,而且每次須要判斷是否重複,剔除重複的數據。
for (Element element : elements) {
/*檢查類型*/
if (!(element instanceof VariableElement)) {
return false;
}
VariableElement variableElement = (VariableElement) element;
/*獲取類信息*/
TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
/*類的絕對路徑*/
String qualifiedName = typeElement.getQualifiedName().toString();
/*類名*/
String clsName = typeElement.getSimpleName().toString();
/*獲取包名*/
String packageName = processingEnv.getElementUtils().getPackageOf(typeElement).getQualifiedName().toString();
BindView annotation = variableElement.getAnnotation(BindView.class);
int id = annotation.value();
VariMsg variMsg = veMap.get(qualifiedName);
if (variMsg == null) {
variMsg = new VariMsg(packageName, clsName);
variMsg.getVarMap().put(id, variableElement);
veMap.put(qualifiedName, variMsg);
} else {
variMsg.getVarMap().put(id, variableElement);
}
}複製代碼
3.經過javapoet
去生成java
類文件
這裏主要是javapoet
的運用,詳細用法可去javapoet
查看
System.out.println("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
for (String key : veMap.keySet()) {
ClassName InterfaceName = ClassName.bestGuess("com.example.ViewInjector");
ClassName host = ClassName.bestGuess(key);
VariMsg variMsg = veMap.get(key);
StringBuilder builder = new StringBuilder();
builder.append(" if(object instanceof android.app.Activity){\n");
builder.append(code(variMsg.getVarMap(), "android.app.Activity"));
builder.append("}\n");
builder.append("else{\n");
builder.append(code(variMsg.getVarMap(), "android.view.View"));
builder.append("}\n");
MethodSpec main = MethodSpec.methodBuilder("inject")
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.addAnnotation(Override.class)
.addParameter(host, "host")
.addParameter(Object.class, "object")
.addCode(builder.toString())
.build();
TypeSpec helloWorld = TypeSpec.classBuilder(variMsg.getClsName() + "ViewInjector")
.addModifiers(Modifier.PUBLIC)
.addMethod(main)
.addSuperinterface(ParameterizedTypeName.get(InterfaceName, host))
.build();
try {
JavaFile javaFile = JavaFile.builder(variMsg.getPk(), helloWorld)
.addFileComment(" This codes are generated automatically. Do not modify!")
.build();
javaFile.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
System.out.println("e--->" + e.getMessage());
}
}
System.out.println("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
return true;
}
/** * 根據註解對象生成code方法體 * * @param map * @param pk * @return */
private String code(Map<Integer, VariableElement> map, String pk) {
StringBuilder builder = new StringBuilder();
for (Integer id : map.keySet()) {
VariableElement variableElement = map.get(id);
String name = variableElement.getSimpleName().toString();
String type = variableElement.asType().toString();
builder.append("host." + name + " = (" + type + ")(((" + pk + ")object).findViewById(" + id + "));\n");
}
return builder.toString();
}複製代碼
到這裏註解處理器最終版本就生成成功了,看下最後生成的代碼類
package com.wzgiceman.viewinjector;
import com.example.ViewInjector;
import java.lang.Object;
import java.lang.Override;
public class MainActivityViewInjector implements ViewInjector<MainActivity> {
@Override
public void inject(MainActivity host, Object object) {
if(object instanceof android.app.Activity){
host.tvMsg = (android.widget.TextView)(((android.app.Activity)object).findViewById(2131492945));
host.tvOther = (android.widget.TextView)(((android.app.Activity)object).findViewById(2131492946));
}
else{
host.tvMsg = (android.widget.TextView)(((android.view.View)object).findViewById(2131492945));
host.tvOther = (android.widget.TextView)(((android.view.View)object).findViewById(2131492946));
}
}
}複製代碼
api
api
模塊主要定義的是給外部提供的使用方法,這裏使用方法即是WzgJector.bind(this)
方法,相同於Butter Knife
中的ButterKnife.bind(this);
public class MainActivity extends AppCompatActivity {
xxxxxx
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
WzgJector.bind(this);
xxxx
}
}複製代碼
建立一個app module
ViewInjector
暴露的外部方法,主要在編譯自動生成的註解處理類中使用
/** * 接口 * Created by WZG on 2017/1/11. */
public interface ViewInjector<M> {
void inject(M m, Object object);
}複製代碼
WzgJector
提供了兩個方法,一種是activity
綁定,一種是view
或者fragment
綁定,綁定完之後,經過反射獲得相關注解編譯處理類及時ViewInjector
子類對象,調用inject(M m, Object object)
方法完成初始過程。
public class WzgJector {
public static void bind(Object activity) {
bind(activity, activity);
}
public static void bind(Object host, Object root) {
Class<?> clazz = host.getClass();
String proxyClassFullName = clazz.getName() + "ViewInjector";
try {
Class<?> proxyClazz = Class.forName(proxyClassFullName);
ViewInjector viewInjector = (ViewInjector) proxyClazz.newInstance();
viewInjector.inject(host, root);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}複製代碼
到這裏其實會發現,編譯時註解並非徹底不使用反射,可是它避免了關鍵性重複性代碼的屢次反射使用,繼而提高了編譯的性能。
到這裏butterknife
的@BindView
實現原理基本就是如此,因爲時間緣由@OnClick
就再也不講解了。其實原理同樣,小夥伴們能夠本身安裝本文思路添加@OnClick
的處理。