ButterKnife 算是一款知名老牌 Android 開發(fā)框架了负甸,通過注解綁定視圖,避免了 findViewById() 的操作,廣受好評迫淹!由于它是在編譯時對注解進(jìn)行解析完成相關(guān)代碼的生成,所以在項目編譯時會略耗時夕吻,但不會影響運行時的性能蔓倍。接下來讓我們從使用到原理一步步深入了解這把黃油刀的故事滞诺!
以下內(nèi)容基于 butterknife:8.8.1
版本阎曹,主要包括如下幾個方面的內(nèi)容:
- 簡單使用
- 原理分析
- 注解處理器
- JavaPoet
一癣缅、簡單使用
首先編寫一個 ButterKnife 簡單使用的例子,方便后續(xù)的分析友存。先在 app的 build.gradle 中加入如下配置祷膳,完成 ButterKnife 引入:
dependencies {
......
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
}
接下來在 Activity 中使用,界面上一個TextView
一個Button
屡立,很簡單就不解釋了:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv_title)
TextView title;
@OnClick(R.id.bt_submit)
public void submit() {
title.setText("hello world");
}
private Unbinder unbinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
unbinder = ButterKnife.bind(this);
}
@Override
protected void onDestroy() {
unbinder.unbind();
super.onDestroy();
}
}
二直晨、原理分析
最后編譯一下項目。直覺告訴我們應(yīng)該從ButterKnife.bind(this)
開始分析膨俐,因為它像是 ButterKnife 和 Activity 建立綁定關(guān)系的過程勇皇,看具體的代碼:
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
sourceView
代表當(dāng)前界面的頂級父 View,是一個FrameLayout
焚刺,繼續(xù)看createBinding()
方法:
private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
Class<?> targetClass = target.getClass();
if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
try {
return constructor.newInstance(target, source);
}
// 省略了相關(guān)異常處理代碼
}
首先得到要綁定的 Activity 對應(yīng)的 Class敛摘,然后用根據(jù) Class 得到一個繼承了Unbinder
的Constructor
,最后通過反射constructor.newInstance(target, source)
得到Unbinder
子類的一個實例乳愉,到此ButterKnife.bind(this)
操作結(jié)束兄淫。這里我們重點關(guān)注findBindingConstructorForClass()
方法是如何得到constructor
實例的:
@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null) {
if (debug) Log.d(TAG, "HIT: Cached in binding map.");
return bindingCtor;
}
String clsName = cls.getName();
if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return null;
}
try {
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
//noinspection unchecked
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
} catch (ClassNotFoundException e) {
if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
} catch (NoSuchMethodException e) {
throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
}
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
整體的流程是先檢查BINDINGS
是否存在 Class 對應(yīng)的 Constructor,如果存在則直接返回蔓姚,否則去構(gòu)造對應(yīng)的 Constructor捕虽。其中BINDINGS
是一個LinkedHashMap
:
Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>()
,緩存了對應(yīng)的 Class 和 Constructor 以提高效率坡脐!
接下來看當(dāng)不存在對應(yīng) Constructor 時如何構(gòu)造一個新的泄私,首先如果clsName
是系統(tǒng)相關(guān)的,則直接返回 null,否則先創(chuàng)建一個新的 Class:
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
這里新的 Class 的 name 是 com.shh.sometest.MainActivity_ViewBinding
晌端,最后用新的 Class 創(chuàng)建一個 繼承了Unbinder
的 Constructor捅暴,并添加到BINDINGS
:
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
BINDINGS.put(cls, bindingCtor);
所以最終bind()
方法返回的是MainActivity_ViewBinding
類的實例。既然可以返回MainActivity_ViewBinding
的實例斩松,那MainActivity_ViewBinding
這個類肯定是存在的伶唯【跫龋可以在如下目錄找到它(這個類是在項目編譯時期由 annotationProcessor 生成的惧盹,關(guān)于 annotationProcessor 后邊會說到):
來看看它里邊都做了那些事:
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
private View view2131165217;
@UiThread
public MainActivity_ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public MainActivity_ViewBinding(final MainActivity target, View source) {
this.target = target;
View view;
target.title = Utils.findRequiredViewAsType(source, R.id.tv_title, "field 'title'", TextView.class);
view = Utils.findRequiredView(source, R.id.bt_submit, "method 'submit'");
view2131165217 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.submit();
}
});
}
@Override
@CallSuper
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.title = null;
view2131165217.setOnClickListener(null);
view2131165217 = null;
}
}
之前createBinding()
方法中return constructor.newInstance(target, source);
操作使用的就是MainActivity_ViewBinding
類兩個參數(shù)的構(gòu)造函數(shù)。
重點看這個構(gòu)造函數(shù)瞪讼,首先是給target.title
賦值钧椰,即MainActivity
中的TextView
,用到了一個findRequiredViewAsType()
方法:
public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who,
Class<T> cls) {
View view = findRequiredView(source, id, who);
return castView(view, id, who, cls);
}
繼續(xù)看findRequiredView()
方法:
public static View findRequiredView(View source, @IdRes int id, String who) {
View view = source.findViewById(id);
if (view != null) {
return view;
}
String name = getResourceEntryName(source, id);
throw new IllegalStateException("Required view '"
+ name
+ "' with ID "
+ id
+ " for "
+ who
+ " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'"
+ " (methods) annotation.");
}
最終還是通過findViewById()
得到對應(yīng)View符欠,然后就是castView()
:
public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
try {
return cls.cast(view);
} catch (ClassCastException e) {
String name = getResourceEntryName(view, id);
throw new IllegalStateException("View '"
+ name
+ "' with ID "
+ id
+ " for "
+ who
+ " was of the wrong type. See cause for more info.", e);
}
}
核心就是把findRequiredView()
得到的 View 轉(zhuǎn)成指定類型的 View 嫡霞,如果 xml 中定義的 View 和 Activity 中通過注解綁定的 View 類型不一致,就會拋出上邊方法的異常希柿,可能很多人都遇到過诊沪。這樣target.title
的賦值就結(jié)束了,接下來就是直接使用findRequiredView()
找到對應(yīng) id 的Button
曾撤,不用進(jìn)行類型轉(zhuǎn)換端姚,然后給它綁定點擊事件,最終調(diào)用了在MainActivity
中給Button
綁定點擊事件時定義的submit()
方法挤悉。到這里就完成了 相關(guān) View 的賦值以及事件綁定渐裸!
MainActivity_ViewBinding
類中還有一個unbind()
方法,需要在Activity
或Fragment
的onDestory()
中調(diào)用装悲,以完成 相關(guān) View 引用的釋放以及點擊事件的解綁操作昏鹃!
三、注解處理器
那么诀诊,MainActivity_ViewBinding
類時如何生成的呢洞渤?首先,要生成這個類就要先得到這個類必須的基礎(chǔ)信息属瓣,這就涉及到了annotationProcessor技術(shù)载迄,和 APT(Annotation Processing Tool)技術(shù)類似,它是一種注解處理器奠涌,項目編譯時對源代碼進(jìn)行掃描檢測找出存活時間為RetentionPolicy.CLASS
的指定注解宪巨,然后對注解進(jìn)行解析處理,進(jìn)而得到要生成的類的必要信息溜畅,然后根據(jù)這些信息動態(tài)生成對應(yīng)的 java 類捏卓,至于如何生成 java 類就涉及到了后邊要說的 JavaPoet技術(shù)。
這里我們先看用注解處理器收集類信息的過程,之前我們已經(jīng)在app的 build.gradle引入了 ButterKnife 的注解處理器: butterknife-compiler怠晴,其中有一個ButterKnifeProcessor 類完成了注解處理器的核心邏輯遥金。這個類約有1500行代碼,先看下它的基本框架結(jié)構(gòu):
@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
String sdk = env.getOptions().get(OPTION_SDK_INT);
......
debuggable = !"false".equals(env.getOptions().get(OPTION_DEBUGGABLE));
elementUtils = env.getElementUtils();
typeUtils = env.getTypeUtils();
filer = env.getFiler();
try {
trees = Trees.instance(processingEnv);
} catch (IllegalArgumentException ignored) {
}
}
@Override
public Set<String> getSupportedOptions() {
return ImmutableSet.of(OPTION_SDK_INT, OPTION_DEBUGGABLE);
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}
@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
JavaFile javaFile = binding.brewJava(sdk, debuggable);
try {
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return false;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
注意蒜田,ButterKnifeProcessor
類上使用了@AutoService(Processor.class)
注解稿械,來實現(xiàn)注解處理器的注冊,注冊到 javac 后冲粤,在項目編譯時就能執(zhí)行注解處理器了美莫。
ButterKnifeProcessor
繼承了AbstractProcessor
抽象類,并重寫以上五個方法梯捕,如果我們自定義解處理器也是類似的厢呵,看下這幾個方法:
1、init()
首先 init() 方法完成sdk版本的判斷以及相關(guān)幫助類的初始化傀顾,幫助類主要有以下幾個:
- Elements elementUtils襟铭,注解處理器運行掃描源文件時,以獲取元素(Element)相關(guān)的信息短曾。Element 有以下幾個子類:
包(PackageElement
)寒砖、類(TypeElement
)、成員變量(VariableElement
)嫉拐、方法(ExecutableElement
) - Types typeUtils哩都,
- Filer filer,用來生成 java 類文件椭岩。
- Trees trees茅逮,
2、getSupportedAnnotationTypes()
該方法返回一個Set<String>
判哥,代表ButterKnifeProcessor
要處理的注解類的名稱集合献雅,即 ButterKnife 支持的注解:butterknife-annotations
3、getSupportedSourceVersion()
返回當(dāng)前系統(tǒng)支持的 java 版本塌计。
4挺身、getSupportedOptions()
返回注解處理器可處理的注解操作。
5锌仅、process()
最后章钾,process() 方法是我們要重點分析的,在這里完成了目標(biāo)類信息的收集并生成對應(yīng) java 類热芹。
首先看注解信息的掃描收集贱傀,即通過findAndParseTargets()
方法:
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
scanForRClasses(env);
......
......
// env.getElementsAnnotatedWith(BindView.class)獲取所有使用BindView注解的元素
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
try {
parseBindView(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindView.class, e);
}
}
......
......
// 將builderMap中的數(shù)據(jù)添加到隊列中
Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries =
new ArrayDeque<>(builderMap.entrySet());
Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();
while (!entries.isEmpty()) {
// 出隊列
Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst();
TypeElement type = entry.getKey();
BindingSet.Builder builder = entry.getValue();
// 查找當(dāng)前類元素的父類元素
TypeElement parentType = findParentType(type, erasedTargetNames);
// 如果沒找到則保存TypeElement和對應(yīng)BindingSet
if (parentType == null) {
bindingMap.put(type, builder.build());
} else {
BindingSet parentBinding = bindingMap.get(parentType);
if (parentBinding != null) {
// 如果找到父類元素,則給當(dāng)前類元素對應(yīng)的BindingSet.Builder設(shè)置父BindingSet
builder.setParent(parentBinding);
bindingMap.put(type, builder.build());
} else {
// 再次入隊列
entries.addLast(entry);
}
}
}
return bindingMap;
}
只保留了BindView
注解信息的掃描解析過程伊脓,省略其它類似邏輯府寒,先將掃描得到的注解相關(guān)信息保存到builderMap
和erasedTargetNames
中,最后對這些信息進(jìn)行重新整理返回一個以TypeElement
為 key 、BindingSet
為 value 的 Map株搔,其中TypeElement
代表使用了 ButterKnife 的類剖淀,即 Activity、Fragment等纤房,BindingSet
是butterknife-compiler中的一個自定義類纵隔,用來存儲要生成類的基本信息以及注解元素的相關(guān)信息。
parseBindView()
方法用來解析使用了BindView
注解的元素:
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> erasedTargetNames) {
// 首先要注意炮姨,此時element是VariableElement類型的捌刮,即成員變量
// enclosingElement是當(dāng)前元素的父類元素,一般就是我們使用ButteKnife時定義的View類型成員變量所在的類剑令,可以理解為之前例子中的MainActivity
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// 進(jìn)行相關(guān)校驗
// 1糊啡、isInaccessibleViaGeneratedCode(),先判斷當(dāng)前元素的是否是private或static類型吁津,
// 再判斷其父元素是否是一個類以及是否是private類型。
// 2堕扶、isBindingInWrongPackage()碍脏,是否在系統(tǒng)相關(guān)的類中使用了ButteKnife注解
boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
|| isBindingInWrongPackage(BindView.class, element);
// TypeMirror表示Java編程語言中的一種類型。 類型包括基元類型稍算,聲明的類型(類和接口類型)典尾,數(shù)組類型,類型變量和空類型糊探。
// 還表示了通配符類型參數(shù)钾埂,可執(zhí)行文件的簽名和返回類型,以及與包和關(guān)鍵字void相對應(yīng)的偽類型科平。
TypeMirror elementType = element.asType();
// 如果當(dāng)前元素是類的成員變量
if (elementType.getKind() == TypeKind.TYPEVAR) {
TypeVariable typeVariable = (TypeVariable) elementType;
elementType = typeVariable.getUpperBound();
}
Name qualifiedName = enclosingElement.getQualifiedName();
Name simpleName = element.getSimpleName();
// 判斷當(dāng)前元素是否是 View 的子類褥紫,或者是接口,不是的話拋出異常
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, qualifiedName, simpleName);
} else {
error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
BindView.class.getSimpleName(), qualifiedName, simpleName);
hasError = true;
}
}
if (hasError) {
return;
}
// 獲得元素使用BindView注解時設(shè)置的屬性值瞪慧,即 View 對應(yīng)的xml中的id
int id = element.getAnnotation(BindView.class).value();
// 嘗試獲取父元素對應(yīng)的BindingSet.Builder
BindingSet.Builder builder = builderMap.get(enclosingElement);
// QualifiedId記錄了當(dāng)前元素的包信息以及id
QualifiedId qualifiedId = elementToQualifiedId(element, id);
if (builder != null) {
String existingBindingName = builder.findExistingBindingName(getId(qualifiedId));
// 如果當(dāng)前id已經(jīng)被綁定髓考,則拋出異常
if (existingBindingName != null) {
error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
BindView.class.getSimpleName(), id, existingBindingName,
enclosingElement.getQualifiedName(), element.getSimpleName());
return;
}
} else {
// 創(chuàng)建一個新的BindingSet.Builder并返回,并且以enclosingElement 為key添加到builderMap中
builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
}
String name = simpleName.toString();
TypeName type = TypeName.get(elementType);
// 判斷當(dāng)前元素是否使用了Nullable注解
boolean required = isFieldRequired(element);
// 創(chuàng)建一個FieldViewBinding弃酌,它包含了元素名氨菇、類型、是否是Nullable
// 然后和元素id一同添加到BindingSet.Builder
builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));
// 記錄當(dāng)前元素的父類元素
erasedTargetNames.add(enclosingElement);
}
部分代碼已經(jīng)寫了注釋妓湘,這里看下getOrCreateBindingBuilder()
方法的實現(xiàn):
private BindingSet.Builder getOrCreateBindingBuilder(
Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) {
// 先判斷enclosingElement對應(yīng)的BindingSet.Builder是否已存在
BindingSet.Builder builder = builderMap.get(enclosingElement);
if (builder == null) {
// 創(chuàng)建一個BindingSet.Builder
builder = BindingSet.newBuilder(enclosingElement);
// 添加到builderMap
builderMap.put(enclosingElement, builder);
}
return builder;
}
由于用BindingSet
保存了注解相關(guān)的信息查蓉,所以繼續(xù)了解下它的newBuilder()
過程:
static Builder newBuilder(TypeElement enclosingElement) {
TypeMirror typeMirror = enclosingElement.asType();
// 判斷當(dāng)前父元素的類型
boolean isView = isSubtypeOfType(typeMirror, VIEW_TYPE);
boolean isActivity = isSubtypeOfType(typeMirror, ACTIVITY_TYPE);
boolean isDialog = isSubtypeOfType(typeMirror, DIALOG_TYPE);
TypeName targetType = TypeName.get(typeMirror);
if (targetType instanceof ParameterizedTypeName) {
targetType = ((ParameterizedTypeName) targetType).rawType;
}
// 獲取父類元素的包名
String packageName = getPackage(enclosingElement).getQualifiedName().toString();
// 獲取父類元素的名稱
String className = enclosingElement.getQualifiedName().toString().substring(
packageName.length() + 1).replace('.', '$');
// 即最終要生成的java類的名稱
ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");
// 判斷父類元素是否為final類型
boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog);
}
所以BindingSet
主要保存了要生成的目標(biāo)類的基本特征信息,以及類中使用了 ButterKnife 注解的元素的信息榜贴,這樣一個BindingSet
就和一個使用了ButterKnife 的類對應(yīng)了起來豌研。
四、JavaPoet
到這里要生成的目標(biāo)類基本信息就收集就完成了,接下來就是生成 java 類文件了聂沙,再回到 process()方法:
@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
// 得到j(luò)ava類源碼
JavaFile javaFile = binding.brewJava(sdk, debuggable);
try {
// 生成java文件
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return false;
}
遍歷 bindingMap秆麸,根據(jù)BindingSet
得到一個JavaFile
對象,然后輸入 java 類及汉,這個過程用到了JavaPoet開源庫沮趣,提供了一種友好的方式來輔助生成 java 類代碼,同時將類代碼生成文件坷随,否則需要自己拼接字符串來實現(xiàn)房铭,可以發(fā)現(xiàn)BindingSet
除了保存信息目標(biāo)類信息外,還封裝了 JavaPoet 生成目標(biāo)類代碼的過程温眉。
在繼續(xù)往下分析前缸匪,先了解下 JavaPoet 中一些重要的類(這些類還有許多實用的方法哦):
- TypeSpec 表示類、接口类溢、或者枚舉聲明
- ParameterSpec 表示參數(shù)聲明
- MethodSpec 表示構(gòu)造函數(shù)凌蔬、方法聲明
- FieldSpec 表示成員變量,一個字段聲明
- CodeBlock 表示代碼塊闯冷,用來拼接代碼
- JavaFile 表示Java類的代碼
還有幾個占位符也了解下:
-
$L砂心,for Literals 替換字符串、原語蛇耀、JavaPoet中的類型
例如beginControlFlow("for (int i = $L; i < $L; i++)", 1, 10)
addStatement("result = result $L i", "+") - $S辩诞,for Strings 替換字符串,例如addStatement("return $S", "hello")
- $T纺涤,for Types 替換類型译暂,例如addStatement("return new $T()", Date.class)
- $N,for Names 替換JavaPoet中的聲明
有了一些基礎(chǔ)概念后撩炊,繼續(xù)看用 JavaPoet 生成對應(yīng)JavaFile
的過程:
JavaFile brewJava(int sdk, boolean debuggable) {
return JavaFile.builder(bindingClassName.packageName(), createType(sdk, debuggable))
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
用到的JavaFile.builder()
方法需要兩個參數(shù):要生成的目標(biāo)類的包名外永,以及TypeSpec
對象,即createType()
方法的返回值衰抑,代表一個類象迎、接口、枚舉聲明呛踊。結(jié)合文章開始的例子砾淌,這個TypeSpec
對象應(yīng)代表一個類聲明:
private TypeSpec createType(int sdk, boolean debuggable) {
// TypeSpec.classBuilder() 方法設(shè)置類名稱
// addModifiers() 方法設(shè)置類的修飾符為 public
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(PUBLIC);
// 如果是final類則添加final修飾符
if (isFinal) {
result.addModifiers(FINAL);
}
if (parentBinding != null) {
result.superclass(parentBinding.bindingClassName);
} else {
// 讓當(dāng)前類Unbinder接口,之前生成的MainActivity_ViewBinding類就實現(xiàn)了Unbinder接口
result.addSuperinterface(UNBINDER);
}
if (hasTargetField()) {
// 添加一個類成員變量谭网,可以對應(yīng)到MainActivity_ViewBinding類中的private MainActivity target
result.addField(targetTypeName, "target", PRIVATE);
}
// 判斷目標(biāo)類的類型
if (isView) {
result.addMethod(createBindingConstructorForView());
} else if (isActivity) {
// 由于之前的例子是Activity類型的汪厨,所以會走到這里,去生成MainActivity_ViewBinding類默認(rèn)的構(gòu)造函數(shù)
result.addMethod(createBindingConstructorForActivity());
} else if (isDialog) {
result.addMethod(createBindingConstructorForDialog());
}
if (!constructorNeedsView()) {
// Add a delegating constructor with a target type + view signature for reflective use.
result.addMethod(createBindingViewDelegateConstructor());
}
// 生成view綁定的構(gòu)造函數(shù)愉择,可以對應(yīng)到MainActivity_ViewBinding類兩個參數(shù)的構(gòu)造函數(shù)
result.addMethod(createBindingConstructor(sdk, debuggable));
if (hasViewBindings() || parentBinding == null) {
// 生成ubinder()方法
result.addMethod(createBindingUnbindMethod(result));
}
return result.build();
}
這里重點看下createBindingConstructor()
的過程劫乱,代碼較多织中,只保留部分以方便分析:
private MethodSpec createBindingConstructor(int sdk, boolean debuggable) {
MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
.addAnnotation(UI_THREAD)
.addModifiers(PUBLIC);
if (hasMethodBindings()) {
// 給構(gòu)造函數(shù)添加一個修飾符為final、targetTypeName衷戈,名稱為target的參數(shù)狭吼,即final MainActivity target
constructor.addParameter(targetTypeName, "target", FINAL);
} else {
constructor.addParameter(targetTypeName, "target");
}
if (constructorNeedsView()) {
// 給構(gòu)造函數(shù)添加一個View類型的source參數(shù)
constructor.addParameter(VIEW, "source");
} else {
constructor.addParameter(CONTEXT, "context");
}
......
if (hasTargetField()) {
// 給構(gòu)造函數(shù)添加一行this.target = target;的聲明代碼
constructor.addStatement("this.target = target");
constructor.addCode("\n");
}
if (hasViewBindings()) {
if (hasViewLocal()) {
// 給構(gòu)造函數(shù)添加一行View view;的聲明代碼
constructor.addStatement("$T view", VIEW);
}
for (ViewBinding binding : viewBindings) {
// 根據(jù)id查找view并完成賦值
addViewBinding(constructor, binding, debuggable);
}
......
}
......
return constructor.build();
}
那么是如何根據(jù)id查找view呢?答案就在addViewBinding()
方法殖妇,以下解釋同樣類比MainActivity_ViewBinding兩個參數(shù)的構(gòu)造函數(shù):
private void addViewBinding(MethodSpec.Builder result, ViewBinding binding, boolean debuggable) {
if (binding.isSingleFieldBinding()) {
FieldViewBinding fieldBinding = binding.getFieldBinding();
// CodeBlock 表示代碼塊刁笙,用來完成view查找、賦值語句的拼接
// 相當(dāng)于target.title =
CodeBlock.Builder builder = CodeBlock.builder()
.add("target.$L = ", fieldBinding.getName());
// 由于例子中title是TextView谦趣,不是View所以requiresCast為true
boolean requiresCast = requiresCast(fieldBinding.getType());
// debuggable為true疲吸、fieldBinding.isRequired()為true,則以下if條件不成立
if (!debuggable || (!requiresCast && !fieldBinding.isRequired())) {
if (requiresCast) {
builder.add("($T) ", fieldBinding.getType());
}
builder.add("source.findViewById($L)", binding.getId().code);
} else {
// 繼續(xù)給代碼塊添加Utils.find
builder.add("$T.find", UTILS);
// 根據(jù)上邊的分析可知前鹅,會給代碼塊添加RequiredView
builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView");
if (requiresCast) {
// 給代碼塊添加AsType
builder.add("AsType");
}
// 給代碼塊添加(source, R.id.tv_title
builder.add("(source, $L", binding.getId().code);
if (fieldBinding.isRequired() || requiresCast) {
// 繼續(xù)添加view的相關(guān)描述摘悴,例如field 'title'
builder.add(", $S", asHumanDescription(singletonList(fieldBinding)));
}
if (requiresCast) {
繼續(xù)添加view的Class類型,例如TextView.class
builder.add(", $T.class", fieldBinding.getRawType());
}
// 添加右括號舰绘,到這里就完成了view的查找與賦值
// 例如target.title = Utils.findRequiredViewAsType(source, R.id.tv_title, "field 'title'", TextView.class);
builder.add(")");
}
result.addStatement("$L", builder.build());
return;
}
結(jié)合對createType()
流程的分析蹂喻,我們基本了解了 MainActivity_ViewBinding 類中構(gòu)造函數(shù)的構(gòu)建過程、以及 title(之前例子的TextView)的查找賦值的代碼是如何構(gòu)建出來的除盏,這樣就把注解處理器中 process()方法中BindView
注解的處理流程就跑通了叉橱。隨然這只是一小部分的分析,但并不妨礙我們理解其它注解背后的工作流程者蠕。
五、小結(jié)
可以看出 ButterKnife 整個過程是在項目編譯階段完成的掐松,主要用到了 annotationProcessor 和 JavaPoet 技術(shù)踱侣,使用時通過生成的輔助類完成操作,并不是在項目運行時通過注解加反射實現(xiàn)的大磺,所以并不會影響項目運行時的性能抡句,可能僅在項目編譯時有略微的影響。