ButterKnife 8.4.0 源碼分析(一)
前言
本文是根據(jù)ButterKnife的歷史版本 8.4.0進(jìn)行分析的。
ButterKnife 用到了編譯時技術(shù)(APT Annotation Processing Tool),再講解源碼之前我們先看看這部分內(nèi)容。
編譯時技術(shù)(APT技術(shù))
講解編譯時技術(shù)前,我們需要先了解下代碼的生命周期嗓奢。
如圖所示,代碼的生命周期分為源碼期、編譯期含衔、運(yùn)行期。
- 源碼期 當(dāng)我們在寫以.java結(jié)尾的文件的時期二庵。
- 編譯期 則指的是是指把源碼交給編譯器編譯成計算機(jī)可以執(zhí)行的文件的過程抱慌。在Java中也就是把Java代碼編成class文件的過程.。
- 運(yùn)行期 則指的是java代碼的運(yùn)行過程眨猎。如我們的app運(yùn)行在手機(jī)中的時期抑进。
APT,就是Annotation Processing Tool 的簡稱睡陪,就是可以在代碼編譯期間對注解進(jìn)行處理寺渗,并且生成java文件,減少手動的代碼輸入兰迫。比如在ButterKnife中信殊,我們通過自定義注解處理器來對@BindView注解以及注解的的元素進(jìn)行處理,最終生成XXXActivity$$ViewBinder.class文件汁果,來加少我們使用者findViewById等手動代碼的輸入涡拘。
Java注解處理器
注解處理器(Annotation Processor)是javac的一個工具,它用來在編譯時掃描和處理注解(Annotation)据德。你可以對自定義注解鳄乏,并注冊相應(yīng)的注解處理器。一個注解的注解處理器棘利,以Java代碼(或者編譯過的字節(jié)碼)作為輸入橱野,生成文件(通常是.java
文件)作為輸出。這具體的含義什么呢善玫?你可以生成Java代碼水援!這些生成的Java代碼是在生成的.java文件中,但你不能修改已經(jīng)存在的Java類茅郎,例如向已有的類中添加方法蜗元。這些生成的Java文件,會同其他普通的手動編寫的Java源代碼一樣被javac編譯系冗。
我們首先看一下處理器的API奕扣。每一個處理器都是繼承于AbstractProcessor
,如下所示:
public class MyProcessor extends AbstractProcessor {
private Filer mFiler; //文件相關(guān)的輔助類
private Elements mElementUtils; //元素相關(guān)的輔助類
private Messager mMessager; //日志相關(guān)的輔助類
//用來指定你使用的 java 版本毕谴。通常你應(yīng)該返回 SourceVersion.latestSupported()
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
//會被處理器調(diào)用成畦,可以在這里獲取Filer距芬,Elements,Messager等輔助類
@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
elementUtils = env.getElementUtils();
typeUtils = env.getTypeUtils();
filer = env.getFiler();
}
//這個方法返回stirng類型的set集合循帐,集合里包含了你需要處理的注解
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotataions = new LinkedHashSet<String>();
annotataions.add("com.example.MyAnnotation");
return annotataions;
}
//核心方法框仔,這個一般的流程就是先掃描查找注解,再生成 java 文件
//這2個步驟設(shè)計的知識點(diǎn)細(xì)節(jié)很多拄养。
@Override
public boolean process(Set<? extends TypeElement> annoations,
RoundEnvironment env) {
return false;
}
}
注冊你的處理器
要像jvm調(diào)用你寫的處理器离斩,你必須先注冊,讓他知道瘪匿。怎么讓它知道呢跛梗,其實很簡單,google 為我們提供了一個庫棋弥,簡單的一個注解就可以核偿。
首先是依賴
compile 'com.google.auto.service:auto-service:1.0-rc2'
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
//...省略非關(guān)鍵代碼
}
接著只需要在你的注解處理器上加上@AutoService(Processor.class)即可
基本概念
-
Elements:一個用來處理Element的工具類
我們看一下Element這個類。在注解處理器中顽染,我們掃描 java 源文件漾岳,源代碼中的每一部分都是Element的一個特定類型。換句話說:Element代表程序中的元素粉寞,比如說 包尼荆,類,方法唧垦。每一個元素代表一個靜態(tài)的捅儒,語言級別的結(jié)構(gòu).
比如:
package com.example; // PackageElement 包元素 public class Foo { // TypeElement 類元素 private int a; // VariableElement 變量元素 private Foo other; // VariableElement 變量元素 public Foo () {} // ExecuteableElement 方法元素 public void setA ( // ExecuteableElement 方法元素 int newA // VariableElement 變量元素 ) {} }
一句話概括:Element代表源代碼,TypeElement代表源代碼中的元素類型振亮,例如類巧还。然后,TypeElement并不包含類的相關(guān)信息双炕。你可以從TypeElement獲取類的名稱狞悲,但你不能獲取類的信息,比如說父類妇斤。這些信息可以通過TypeMirror獲取。你可以通過調(diào)用element.asType()來獲取一個Element的TypeMirror丹拯。
Types:一個用來處理TypeMirror的工具類
Filer:你可以使用這個類來創(chuàng)建.java文件
Messager : 日志相關(guān)的輔助類
到此為止站超,一個基本的注解處理器就寫好了。
下一章節(jié)我們再看看ButterKnife的內(nèi)部實現(xiàn)吧乖酬。
ButterKnife 8.4.0 源碼分析(二)
ButterKnife 中@BindView注解處理流程分析
原理圖
以下是整個庫的處理流程死相,大家可以看完流程分析后再回過頭來重看一遍。
ButterKnife的核心則是ButterKnifeProcessor 這個類咬像。
(1)init 方法算撮,這個主要是獲取一些輔助類
private Filer mFiler; //文件相關(guān)的輔助類
private Elements mElementUtils; //元素相關(guān)的輔助類
private Messager mMessager; //日志相關(guān)的輔助類
@Override public synchronized void init(ProcessingEnvironment env) {
super.init(env);
elementUtils = env.getElementUtils();
typeUtils = env.getTypeUtils();
filer = env.getFiler();
try {
trees = Trees.instance(processingEnv);
} catch (IllegalArgumentException ignored) {
}
}
(2)getSupportedSourceVersion()方法就是默認(rèn)的生宛,獲取你該處理器使用的java版本
@Override public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
(3)getSupportedAnnotations方法已經(jīng)介紹過了,闡述注解處理器應(yīng)該處理那些注解肮柜。以下是ButterKnifeProcessor所處理的注解陷舅。
private Set<Class<? extends Annotation>> getSupportedAnnotations() {
Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
annotations.add(BindArray.class);
annotations.add(BindBitmap.class);
annotations.add(BindBool.class);
annotations.add(BindColor.class);
annotations.add(BindDimen.class);
annotations.add(BindDrawable.class);
annotations.add(BindFloat.class);
annotations.add(BindInt.class);
annotations.add(BindString.class);
annotations.add(BindView.class);
annotations.add(BindViews.class);
annotations.addAll(LISTENERS);
return annotations;
}
(4)接下來就是process方法,這個方法是核心方法审洞。
@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
//1.查找所有的注解信息莱睁,并形成BindingClass(是什么 后面會講) 保存到 map中
Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);
//2.遍歷步驟1的map 的生成.java文件(也就是上文的 類名_ViewBinding 的java文件)
for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingClass bindingClass = entry.getValue();
JavaFile javaFile = bindingClass.brewJava();
try {
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return true;
}
每個注解的查找與解析-findAndParseTargets
private Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingClass> targetClassMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
scanForRClasses(env);
// 處理每個被 @BindView 注解修飾的 element.
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
// we don't SuperficialValidation.validateElement(element)
// so that an unresolved View type can be generated by later processing rounds
try {
parseBindView(element, targetClassMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindView.class, e);
}
}
//....
// Process each annotation that corresponds to a listener.
for (Class<? extends Annotation> listener : LISTENERS) {
findAndParseListener(env, listener, targetClassMap, erasedTargetNames);
}
return targetClassMap;
}
首先我們先看一下參數(shù) RoundEnvironment ,一個可以在處理器處理該處理器 用來查詢注解信息的工具芒澜,當(dāng)然包含你在getSupportedAnnotationTypes注冊的所有注解仰剿。
我們這里只分析一個具有代表性的BindView注解,其它的都是一樣的痴晦,連代碼都一毛一樣南吮。
接著我們繼續(xù)看 parseBindView();這個方法
private void parseBindView(Element element, Map<TypeElement, BindingClass> targetClassMap,
Set<TypeElement> erasedTargetNames) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
//1.檢查用戶使用的合法性
// Start by verifying common generated code restrictions.
boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
|| isBindingInWrongPackage(BindView.class, element);
// 鑒定 element的類型是不是繼承自View
TypeMirror elementType = element.asType();
if (elementType.getKind() == TypeKind.TYPEVAR) {
TypeVariable typeVariable = (TypeVariable) elementType;
elementType = typeVariable.getUpperBound();
}
if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
if (elementType.getKind() == TypeKind.ERROR) {
note(element, "@%s field with unresolved type (%s) "
+ "must elsewhere be generated as a View or interface. (%s.%s)",
BindView.class.getSimpleName(), elementType, enclosingElement.getQualifiedName(),
element.getSimpleName());
} else {
error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
BindView.class.getSimpleName(), enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
}
// 不合法的直接返回
if (hasError) {
return;
}
//2.獲取變量上的BindView 注解的id 值
int id = element.getAnnotation(BindView.class).value();
// 3.獲取 BindingClass誊酌,有緩存機(jī)制, 沒有則創(chuàng)建旨袒,下文會仔細(xì)分析
BindingClass bindingClass = targetClassMap.get(enclosingElement);
if (bindingClass != null) {
ViewBindings viewBindings = bindingClass.getViewBinding(getId(id));
if (viewBindings != null && viewBindings.getFieldBinding() != null) {
FieldViewBinding existingBinding = viewBindings.getFieldBinding();
error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
BindView.class.getSimpleName(), id, existingBinding.getName(),
enclosingElement.getQualifiedName(), element.getSimpleName());
return;
}
} else {
bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
}
//4 生成FieldViewBinding 實體
String name = element.getSimpleName().toString();
TypeName type = TypeName.get(elementType);
boolean required = isFieldRequired(element);
FieldViewBinding binding = new FieldViewBinding(name, type, required);
//5 將FieldViewBinding 對象 加入到 bindingClass 成員變量的集合中 (其實這里也是通過Id先得到ViewBindings,再把FieldViewBinding加入到bindingClass的ViewBindings中 點(diǎn)擊去看一遍可以知道)
bindingClass.addField(getId(id), binding);
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
}
element.getEnclosingElement();是什么呢术辐?是父節(jié)點(diǎn)砚尽。就是上面我們說的。其實就是獲得@BindView注解所在的類辉词。
基本的步驟就是上面的5步
1.檢查用戶使用的合法性
boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
|| isBindingInWrongPackage(BindView.class, element);
private boolean isInaccessibleViaGeneratedCode(Class<? extends Annotation> annotationClass,
String targetThing, Element element) {
boolean hasError = false;
// 得到父節(jié)點(diǎn) 就是@BindView注解所在的類
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
//判斷修飾符必孤,如果包含private or static 就會拋出異常。
// Verify method modifiers.
Set<Modifier> modifiers = element.getModifiers();
if (modifiers.contains(PRIVATE) || modifiers.contains(STATIC)) {
error(element, "@%s %s must not be private or static. (%s.%s)",
annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
//判斷父節(jié)點(diǎn)是否是類類型的瑞躺,不是的話就會拋出異常
//也就是說BindView 的使用必須在一個類里
// Verify containing type.
if (enclosingElement.getKind() != CLASS) {
error(enclosingElement, "@%s %s may only be contained in classes. (%s.%s)",
annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
//判斷父節(jié)點(diǎn)如果是private 類敷搪,則拋出異常
// Verify containing class visibility is not private.
if (enclosingElement.getModifiers().contains(PRIVATE)) {
error(enclosingElement, "@%s %s may not be contained in private classes. (%s.%s)",
annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
return hasError;
}
上面的代碼里遇見注釋了,這里說一下也就是我們在使用bindview注解的時候不能用使用
類不能是private修飾 幢哨,可以是默認(rèn)的或者public
//in adapter
private static final class ViewHolder {
//....
}
//成員變量不能是private修飾 赡勘,可以是默認(rèn)的或者public
@BindView(R.id.word)
private TextView word;
接下來還有一個方法isBindingInWrongPackage。
這個看名字也才出來個大概 就是不能在android 捞镰,java這種源碼的sdk中使用闸与。如果你的包名是以android或者java開頭就會拋出異常。
private boolean isBindingInWrongPackage(Class<? extends Annotation> annotationClass,
Element element) {
// 得到父節(jié)點(diǎn)
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
String qualifiedName = enclosingElement.getQualifiedName().toString();
if (qualifiedName.startsWith("android.")) {
error(element, "@%s-annotated class incorrectly in Android framework package. (%s)",
annotationClass.getSimpleName(), qualifiedName);
return true;
}
if (qualifiedName.startsWith("java.")) {
error(element, "@%s-annotated class incorrectly in Java framework package. (%s)",
annotationClass.getSimpleName(), qualifiedName);
return true;
}
return false;
}
2.獲取變量上的BindView 注解的id 值
比如
@BindView(R.id.tvTitle)
TextView title;
這里獲取到id值就是R.id.tvTitle岸售。
好了践樱,,這一章節(jié)到此結(jié)束啦凸丸。接下來我們再看接下來的3,4,5三步拷邢。
剩下的(三)(四)(五)章節(jié)的內(nèi)容會在我的github主頁上放置鏈接 checkout it out:
https://github.com/tomridder/ButterKnife-8.4.0-