1.什么是注解
2.注解的分類
3.編譯時注解的原理
4.APT
5.創(chuàng)建項目及依賴
6.編碼實現(xiàn)
7.總結(jié)
我們首先了解一下什么是注解以及注解的核心原理,在掌握原理的前提下自己動手實現(xiàn)一個注解框架齐邦。通過代碼的編寫能夠?qū)utterknife底層實現(xiàn)有更加清楚的認(rèn)識炕吸。
注解
注解在Java文檔中定義如下:
An annotation is a form of metadata, that can be added to Java source code. Classes, methods, variables, parameters and packages may be annotated. Annotations have no direct effect on the operation of the code they annotate.
翻譯一下,大概的意思是:
注解是一種元數(shù)據(jù), 可以添加到j(luò)ava代碼中. 類背亥、方法秒际、變量、參數(shù)狡汉、包都可以被注解娄徊,注解對注解的代碼沒有直接影響。
注解的分類
- <b>運行時注解:</b>指的是運行階段利用反射盾戴,動態(tài)獲取被標(biāo)記的方法寄锐、變量等,如EvenBus。
- <b>編譯時注解:</b>指的是程序在編譯階段會根據(jù)注解進(jìn)行一些額外的處理锐峭,如ButterKnife中鼠。
運行時注解和編譯時注解,都可以理解為通過注解標(biāo)識沿癞,然后進(jìn)行相應(yīng)處理援雇。兩者的區(qū)別是:前者是運行時執(zhí)行的,反射的使用會降低性能椎扬;后者是編譯階段執(zhí)行的惫搏,通過生成輔助類實現(xiàn)效果。
運行時注解由于性能問題被一些人所詬病,所以本文主要講解編譯時注解的原理蚕涤,并實現(xiàn)自己的Butterknife框架筐赔。
編譯時注解的原理
編譯時注解的核心原理依賴APT(Annotation Processing Tools)實現(xiàn):
編譯時Annotation解析的基本原理是,在某些代碼元素上(如類型揖铜、函數(shù)茴丰、字段等)添加注解,在編譯時javac編譯器會檢查AbstractProcessor的子類天吓,并且調(diào)用該類型的process函數(shù)贿肩,然后將添加了注解的所有元素都傳遞到process函數(shù)中,使得開發(fā)人員可以在編譯器進(jìn)行相應(yīng)的處理龄寞,例如汰规,根據(jù)注解生成新的Java類,這也就是ButterKnife Dragger等開源庫的基本原理
那么APT又是什么呢物邑?
APT(Annotation Processing Tool)是一種處理注解的工具,它對源代碼文件進(jìn)行檢測找出其中的Annotation溜哮,使用Annotation進(jìn)行額外的處理。 Annotation處理器在處理Annotation時可以根據(jù)源文件中的Annotation生成額外的源文件和其它的文件(文件具體內(nèi)容由Annotation處理器的編寫者決定),APT還會編譯生成的源文件和原來的源文件色解,將它們一起生成class文件茂嗓。
下面以Butterknife為例:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv_main)
TextView tvMain;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
}
這是Butterknife最簡單的用法,我們只需要加上一個注解 @BindView并指定對應(yīng)的Id就可以了冒签,從而避免了findViewById(),那么它底層是怎么實現(xiàn)的呢在抛?本篇文章重點不是介紹Butterknife的實現(xiàn)原理,所以這里只是簡單的說一下它底層的實現(xiàn)萧恕。這里我們只寫了一個MainActivity.java文件刚梭,編譯后我們查看一下class文件,我們會發(fā)現(xiàn)在MainActivity中多了一個內(nèi)部類ViewBinder票唆,其實Butterknife就是在這個內(nèi)部類中關(guān)聯(lián)對應(yīng)控件的朴读,下面以偽代碼的形式簡單說明一個它底層實現(xiàn)的原理。
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv_main)
TextView tvMain;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
/*
* 當(dāng)bind()方法被調(diào)用之后走趋,tvMain就有對應(yīng)的值了
* */
private static class ViewBinder{
public static void bind(MainActivity activity){
activity.tvMain = (TextView) activity.findViewById(R.id.tv_main);
}
}
}
大概的原理是這樣的:在編譯的時候如果某個類中使用了注解衅金,Butterknife就會在其中“添加”一個內(nèi)部類,在內(nèi)部類中實現(xiàn)控件的關(guān)聯(lián)。我們知道編譯java源文件的工具是javac氮唯,其實在javac中有一個注解處理工具(依賴APT)用來編譯時掃描和處理的注解的工具鉴吹。我們可以為特定的注解,注冊你自己的注解處理器惩琉,來實現(xiàn)自己的處理邏輯豆励。
上面我們了解了基本原理,接下來我們實戰(zhàn)演練
我們的項目結(jié)構(gòu)如上圖所示:每個庫都有自己的實現(xiàn)功能瞒渠,最中通過我們的項目依賴相應(yīng)的庫來使用良蒸。
創(chuàng)建App
我們新建一個工程,因為我們要處理注解需要用到APT,所以在app中需要使用apt的插件
<b>github:</b>https://github.com/Aexyn/android-apt
關(guān)聯(lián)APT插件:
Step1: 在我們工程目錄下的build.gradle文件中添加如下代碼:
Step2: 在我們項目目錄下的build.gradle文件中添加如下代碼:
創(chuàng)建Java庫(定義注解)
創(chuàng)建Android庫
<b>關(guān)聯(lián)Java庫(inject-annotion)</b>
創(chuàng)建Java庫(處理注解庫)
我們需要在編譯的時候根據(jù)注解創(chuàng)建新的類并添加到源文件中伍玖,所有需要引用幾個依賴嫩痰。并且要關(guān)聯(lián)上一個Java庫
- com.google.auto.service:auto-service:谷歌提供的Java 生成源代碼庫
- com.squareup:javapoet:提供了各種 API 讓你用各種姿勢去生成 Java 代碼文件
- com.google.auto:auto-common:生成代碼的庫
<b>全部創(chuàng)建完畢后,我們的工程目錄如下:</b>
關(guān)聯(lián)庫
讓我們的項目(app)去關(guān)聯(lián)注解庫
編寫代碼
1.定義注解:inject-annotion
/**
* @Retention(RetentionPolicy.CLASS):編譯時被保留,在class文件中存在,但JVM將會忽略
* @Target(ElementType.FIELD) :出現(xiàn)的位置(字段窍箍、枚舉的常量)
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
int value();
}
2.定義方法:inject
-
<b>InjectView.java</b>
public class InjectView { public static void bind(Activity activity) { String clsName=activity.getClass().getName(); try { //獲取內(nèi)部類 Class<?> viewBidClass=Class.forName(clsName+"$$ViewBinder"); //創(chuàng)建內(nèi)部類的實例 ViewBinder viewBinder= (ViewBinder) viewBidClass.newInstance(); viewBinder.bind(activity);//綁定頁面 } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } }
-
<b>ViewBinder.java</b>
public interface ViewBinder <T>{ void bind(T tartget); }
3.處理注解:inject-compiler
-
<b>FieldViewBinding.java</b>
/** * 注解信息封裝類 */ public class FieldViewBinding { private String name;// 字段的名字 textview private TypeMirror type ;// 字段的類型 --->TextView private int resId;// 對應(yīng)的id R.id.textiew public FieldViewBinding(String name, TypeMirror type, int resId) { this.name = name; this.type = type; this.resId = resId; } public String getName() { return name; } public TypeMirror getType() { return type; } public int getResId() { return resId; } }
-
<b>BindViewProcessor.java</b>
/** * 注解處理類 */ @AutoService(Processor.class) public class BindViewProcessor extends AbstractProcessor { private Elements elementUtils; private Types typeUtils; private Filer filer; @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); elementUtils = processingEnvironment.getElementUtils(); typeUtils = processingEnvironment.getTypeUtils(); filer = processingEnvironment.getFiler(); } /* 設(shè)置處理那些注解 */ @Override public Set<String> getSupportedAnnotationTypes() { Set<String> types = new LinkedHashSet<>(); types.add(BindView.class.getCanonicalName()); return types; } /* 設(shè)置支持的JDk版本 */ @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { Map<TypeElement, List<FieldViewBinding>> targetMap = new HashMap<>(); for (Element element : roundEnvironment.getElementsAnnotatedWith(BindView.class)) { TypeElement enClosingElement = (TypeElement) element.getEnclosingElement(); List<FieldViewBinding> list = targetMap.get(enClosingElement); if (list == null) { list = new ArrayList<>(); targetMap.put(enClosingElement, list); } int id = element.getAnnotation(BindView.class).value(); String fieldName = element.getSimpleName().toString(); TypeMirror typeMirror = element.asType(); FieldViewBinding fieldViewBinding = new FieldViewBinding(fieldName, typeMirror, id); list.add(fieldViewBinding); } for (Map.Entry<TypeElement, List<FieldViewBinding>> item : targetMap.entrySet()) { List<FieldViewBinding> list = item.getValue(); if (list == null || list.size() == 0) { continue; } TypeElement enClosingElement = item.getKey(); String packageName = getPackageName(enClosingElement); String complite = getClassName(enClosingElement, packageName); ClassName className = ClassName.bestGuess(complite); ClassName viewBinder = ClassName.get("com.example.inject", "ViewBinder"); TypeSpec.Builder result = TypeSpec.classBuilder(complite + "$$ViewBinder") .addModifiers(Modifier.PUBLIC) .addTypeVariable(TypeVariableName.get("T", className)) .addSuperinterface(ParameterizedTypeName.get(viewBinder, className)); MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind") .addModifiers(Modifier.PUBLIC) .returns(TypeName.VOID) .addAnnotation(Override.class) .addParameter(className, "target", Modifier.FINAL); for (int i = 0; i < list.size(); i++) { FieldViewBinding fieldViewBinding = list.get(i); String pacckageNameString = fieldViewBinding.getType().toString(); ClassName viewClass = ClassName.bestGuess(pacckageNameString); methodBuilder.addStatement ("target.$L=($T)target.findViewById($L)", fieldViewBinding.getName() , viewClass, fieldViewBinding.getResId()); } result.addMethod(methodBuilder.build()); try { JavaFile.builder(packageName, result.build()) .addFileComment("auto create make") .build().writeTo(filer); } catch (IOException e) { e.printStackTrace(); } } return false; } /* 獲取類名 */ private String getClassName(TypeElement enClosingElement, String packageName) { int packageLength = packageName.length() + 1; return enClosingElement.getQualifiedName().toString().substring(packageLength).replace(".", "$"); } /* 獲取包名 */ private String getPackageName(TypeElement enClosingElement) { return elementUtils.getPackageOf(enClosingElement).getQualifiedName().toString(); } }
測試
public class MainActivity extends AppCompatActivity {
@BindView(R.id.text)
TextView textview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
InjectView.bind(this);
if(textview == null){
Toast.makeText(this,"注解處理失敗",Toast.LENGTH_SHORT).show();
}else{
textview.setText("世界你好串纺!");
}
}
}
用法跟Butterknife一樣,頁面上有一個TextView,使用注解關(guān)聯(lián)椰棘,如果關(guān)聯(lián)失敗造垛,彈出提示信息。否則設(shè)置顯示為“世界你好晰搀!”。
總結(jié)
通過上述代碼的編寫办斑,我們能更加對Butterknife的底層實現(xiàn)有更清楚的認(rèn)識外恕,雖然只是實現(xiàn)了綁定View。在編譯時javac編譯器會檢查AbstractProcessor的子類乡翅,并且調(diào)用該類型的process函數(shù)鳞疲,然后將添加了注解的所有元素都傳遞到process函數(shù)中,我們需要繼承該類重寫此方法我們就能獲取我們想要處理的注解蠕蚜。在里面做具體的綁定邏輯尚洽。
AutoService注解處理器是Google開發(fā)的,用來生成META-INF/services/javax.annotation.processing.Processor文件的靶累。我們可以在注解處理器中使用注解腺毫。非常方便。