ButterKnife 算是一款知名老牌 Android 開發框架了,經過註解綁定視圖,避免了 findViewById() 的操做,廣受好評!因爲它是在編譯時對註解進行解析完成相關代碼的生成,因此在項目編譯時會略耗時,但不會影響運行時的性能。接下來讓咱們從使用到原理一步步深刻了解這把黃油刀的故事!java
如下內容基於 butterknife:8.8.1
版本,主要包括以下幾個方面的內容:android
首先編寫一個 ButterKnife 簡單使用的例子,方便後續的分析。先在 app的 build.gradle 中加入以下配置,完成 ButterKnife 引入:git
dependencies {
......
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
}
複製代碼
接下來在 Activity 中使用,界面上一個TextView
一個Button
,很簡單就不解釋了:github
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();
}
}
複製代碼
最後編譯一下項目。直覺告訴咱們應該從ButterKnife.bind(this)
開始分析,由於它像是 ButterKnife 和 Activity 創建綁定關係的過程,看具體的代碼:編程
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
複製代碼
sourceView
表明當前界面的頂級父 View,是一個FrameLayout
,繼續看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);
}
// 省略了相關異常處理代碼
}
複製代碼
首先獲得要綁定的 Activity 對應的 Class,而後用根據 Class 獲得一個繼承了Unbinder
的Constructor
,最後經過反射constructor.newInstance(target, source)
獲得Unbinder
子類的一個實例,到此ButterKnife.bind(this)
操做結束。這裏咱們重點關注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 對應的 Constructor,若是存在則直接返回,不然去構造對應的 Constructor。其中BINDINGS
是一個LinkedHashMap
: Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>()
,緩存了對應的 Class 和 Constructor 以提升效率! 接下來看當不存在對應 Constructor 時如何構造一個新的,首先若是clsName
是系統相關的,則直接返回 null,不然先建立一個新的 Class:app
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
複製代碼
這裏新的 Class 的 name 是 com.shh.sometest.MainActivity_ViewBinding
,最後用新的 Class 建立一個 繼承了Unbinder
的 Constructor,並添加到BINDINGS
:框架
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
BINDINGS.put(cls, bindingCtor);
複製代碼
因此最終bind()
方法返回的是MainActivity_ViewBinding
類的實例。既然能夠返回MainActivity_ViewBinding
的實例,那MainActivity_ViewBinding
這個類確定是存在的。能夠在以下目錄找到它(這個類是在項目編譯時期由 annotationProcessor 生成的,關於 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
類兩個參數的構造函數。
重點看這個構造函數,首先是給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);
}
複製代碼
繼續看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()
獲得對應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 轉成指定類型的 View ,若是 xml 中定義的 View 和 Activity 中經過註解綁定的 View 類型不一致,就會拋出上邊方法的異常,可能不少人都遇到過。這樣target.title
的賦值就結束了,接下來就是直接使用findRequiredView()
找到對應 id 的Button
,不用進行類型轉換,而後給它綁定點擊事件,最終調用了在MainActivity
中給Button
綁定點擊事件時定義的submit()
方法。到這裏就完成了 相關 View 的賦值以及事件綁定!
MainActivity_ViewBinding
類中還有一個unbind()
方法,須要在Activity
或Fragment
的onDestory()
中調用,以完成 相關 View 引用的釋放以及點擊事件的解綁操做!
那麼,MainActivity_ViewBinding
類時如何生成的呢?首先,要生成這個類就要先獲得這個類必須的基礎信息,這就涉及到了annotationProcessor技術,和 APT(Annotation Processing Tool)技術相似,它是一種註解處理器,項目編譯時對源代碼進行掃描檢測找出存活時間爲RetentionPolicy.CLASS
的指定註解,而後對註解進行解析處理,進而獲得要生成的類的必要信息,而後根據這些信息動態生成對應的 java 類,至於如何生成 java 類就涉及到了後邊要說的 JavaPoet技術。
這裏咱們先看用註解處理器收集類信息的過程,以前咱們已經在app的 build.gradle引入了 ButterKnife 的註解處理器: butterknife-compiler,其中有一個ButterKnifeProcessor 類完成了註解處理器的核心邏輯。這個類約有1500行代碼,先看下它的基本框架結構:
@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)
註解,來實現註解處理器的註冊,註冊到 javac 後,在項目編譯時就能執行註解處理器了。
ButterKnifeProcessor
繼承了AbstractProcessor
抽象類,並重寫以上五個方法,若是咱們自定義解處理器也是相似的,看下這幾個方法:
首先 init() 方法完成sdk版本的判斷以及相關幫助類的初始化,幫助類主要有如下幾個:
PackageElement
)、類(TypeElement
)、成員變量(VariableElement
)、方法(ExecutableElement
)該方法返回一個Set<String>
,表明ButterKnifeProcessor
要處理的註解類的名稱集合,即 ButterKnife 支持的註解:butterknife-annotations
返回當前系統支持的 java 版本。
返回註解處理器可處理的註解操做。
最後,process() 方法是咱們要重點分析的,在這裏完成了目標類信息的收集並生成對應 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中的數據添加到隊列中
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();
// 查找當前類元素的父類元素
TypeElement parentType = findParentType(type, erasedTargetNames);
// 若是沒找到則保存TypeElement和對應BindingSet
if (parentType == null) {
bindingMap.put(type, builder.build());
} else {
BindingSet parentBinding = bindingMap.get(parentType);
if (parentBinding != null) {
// 若是找到父類元素,則給當前類元素對應的BindingSet.Builder設置父BindingSet
builder.setParent(parentBinding);
bindingMap.put(type, builder.build());
} else {
// 再次入隊列
entries.addLast(entry);
}
}
}
return bindingMap;
}
複製代碼
只保留了BindView
註解信息的掃描解析過程,省略其它相似邏輯,先將掃描獲得的註解相關信息保存到builderMap
和erasedTargetNames
中,最後對這些信息進行從新整理返回一個以TypeElement
爲 key 、BindingSet
爲 value 的 Map,其中TypeElement
表明使用了 ButterKnife 的類,即 Activity、Fragment等,BindingSet
是butterknife-compiler中的一個自定義類,用來存儲要生成類的基本信息以及註解元素的相關信息。 parseBindView()
方法用來解析使用了BindView
註解的元素:
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) {
// 首先要注意,此時element是VariableElement類型的,即成員變量
// enclosingElement是當前元素的父類元素,通常就是咱們使用ButteKnife時定義的View類型成員變量所在的類,能夠理解爲以前例子中的MainActivity
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// 進行相關校驗
// 一、isInaccessibleViaGeneratedCode(),先判斷當前元素的是不是private或static類型,
// 再判斷其父元素是不是一個類以及是不是private類型。
// 二、isBindingInWrongPackage(),是否在系統相關的類中使用了ButteKnife註解
boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
|| isBindingInWrongPackage(BindView.class, element);
// TypeMirror表示Java編程語言中的一種類型。 類型包括基元類型,聲明的類型(類和接口類型),數組類型,類型變量和空類型。
// 還表示了通配符類型參數,可執行文件的簽名和返回類型,以及與包和關鍵字void相對應的僞類型。
TypeMirror elementType = element.asType();
// 若是當前元素是類的成員變量
if (elementType.getKind() == TypeKind.TYPEVAR) {
TypeVariable typeVariable = (TypeVariable) elementType;
elementType = typeVariable.getUpperBound();
}
Name qualifiedName = enclosingElement.getQualifiedName();
Name simpleName = element.getSimpleName();
// 判斷當前元素是不是 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註解時設置的屬性值,即 View 對應的xml中的id
int id = element.getAnnotation(BindView.class).value();
// 嘗試獲取父元素對應的BindingSet.Builder
BindingSet.Builder builder = builderMap.get(enclosingElement);
// QualifiedId記錄了當前元素的包信息以及id
QualifiedId qualifiedId = elementToQualifiedId(element, id);
if (builder != null) {
String existingBindingName = builder.findExistingBindingName(getId(qualifiedId));
// 若是當前id已經被綁定,則拋出異常
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 {
// 建立一個新的BindingSet.Builder並返回,而且以enclosingElement 爲key添加到builderMap中
builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
}
String name = simpleName.toString();
TypeName type = TypeName.get(elementType);
// 判斷當前元素是否使用了Nullable註解
boolean required = isFieldRequired(element);
// 建立一個FieldViewBinding,它包含了元素名、類型、是不是Nullable
// 而後和元素id一同添加到BindingSet.Builder
builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));
// 記錄當前元素的父類元素
erasedTargetNames.add(enclosingElement);
}
複製代碼
部分代碼已經寫了註釋,這裏看下getOrCreateBindingBuilder()
方法的實現:
private BindingSet.Builder getOrCreateBindingBuilder( Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) {
// 先判斷enclosingElement對應的BindingSet.Builder是否已存在
BindingSet.Builder builder = builderMap.get(enclosingElement);
if (builder == null) {
// 建立一個BindingSet.Builder
builder = BindingSet.newBuilder(enclosingElement);
// 添加到builderMap
builderMap.put(enclosingElement, builder);
}
return builder;
}
複製代碼
因爲用BindingSet
保存了註解相關的信息,因此繼續瞭解下它的newBuilder()
過程:
static Builder newBuilder(TypeElement enclosingElement) {
TypeMirror typeMirror = enclosingElement.asType();
// 判斷當前父元素的類型
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
主要保存了要生成的目標類的基本特徵信息,以及類中使用了 ButterKnife 註解的元素的信息,這樣一個BindingSet
就和一個使用了ButterKnife 的類對應了起來。
到這裏要生成的目標類基本信息就收集就完成了,接下來就是生成 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();
// 獲得java類源碼
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,根據BindingSet
獲得一個JavaFile
對象,而後輸入 java 類,這個過程用到了JavaPoet開源庫,提供了一種友好的方式來輔助生成 java 類代碼,同時將類代碼生成文件,不然須要本身拼接字符串來實現,能夠發現BindingSet
除了保存信息目標類信息外,還封裝了 JavaPoet 生成目標類代碼的過程。
在繼續往下分析前,先了解下 JavaPoet 中一些重要的類(這些類還有許多實用的方法哦):
還有幾個佔位符也瞭解下:
有了一些基礎概念後,繼續看用 JavaPoet 生成對應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()
方法須要兩個參數:要生成的目標類的包名,以及TypeSpec
對象,即createType()
方法的返回值,表明一個類、接口、枚舉聲明。結合文章開始的例子,這個TypeSpec
對象應表明一個類聲明:
private TypeSpec createType(int sdk, boolean debuggable) {
// TypeSpec.classBuilder() 方法設置類名稱
// addModifiers() 方法設置類的修飾符爲 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 {
// 讓當前類Unbinder接口,以前生成的MainActivity_ViewBinding類就實現了Unbinder接口
result.addSuperinterface(UNBINDER);
}
if (hasTargetField()) {
// 添加一個類成員變量,能夠對應到MainActivity_ViewBinding類中的private MainActivity target
result.addField(targetTypeName, "target", PRIVATE);
}
// 判斷目標類的類型
if (isView) {
result.addMethod(createBindingConstructorForView());
} else if (isActivity) {
// 因爲以前的例子是Activity類型的,因此會走到這裏,去生成MainActivity_ViewBinding類默認的構造函數
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綁定的構造函數,能夠對應到MainActivity_ViewBinding類兩個參數的構造函數
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()) {
// 給構造函數添加一個修飾符爲final、targetTypeName,名稱爲target的參數,即final MainActivity target
constructor.addParameter(targetTypeName, "target", FINAL);
} else {
constructor.addParameter(targetTypeName, "target");
}
if (constructorNeedsView()) {
// 給構造函數添加一個View類型的source參數
constructor.addParameter(VIEW, "source");
} else {
constructor.addParameter(CONTEXT, "context");
}
......
if (hasTargetField()) {
// 給構造函數添加一行this.target = target;的聲明代碼
constructor.addStatement("this.target = target");
constructor.addCode("\n");
}
if (hasViewBindings()) {
if (hasViewLocal()) {
// 給構造函數添加一行View view;的聲明代碼
constructor.addStatement("$T view", VIEW);
}
for (ViewBinding binding : viewBindings) {
// 根據id查找view並完成賦值
addViewBinding(constructor, binding, debuggable);
}
......
}
......
return constructor.build();
}
複製代碼
那麼是如何根據id查找view呢?答案就在addViewBinding()
方法,如下解釋一樣類比MainActivity_ViewBinding兩個參數的構造函數:
private void addViewBinding(MethodSpec.Builder result, ViewBinding binding, boolean debuggable) {
if (binding.isSingleFieldBinding()) {
FieldViewBinding fieldBinding = binding.getFieldBinding();
// CodeBlock 表示代碼塊,用來完成view查找、賦值語句的拼接
// 至關於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 {
// 繼續給代碼塊添加Utils.find
builder.add("$T.find", UTILS);
// 根據上邊的分析可知,會給代碼塊添加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) {
// 繼續添加view的相關描述,例如field 'title'
builder.add(", $S", asHumanDescription(singletonList(fieldBinding)));
}
if (requiresCast) {
繼續添加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;
}
複製代碼
結合對createType()
流程的分析,咱們基本瞭解了 MainActivity_ViewBinding 類中構造函數的構建過程、以及 title(以前例子的TextView)的查找賦值的代碼是如何構建出來的,這樣就把註解處理器中 process()方法中BindView
註解的處理流程就跑通了。隨然這只是一小部分的分析,但並不妨礙咱們理解其它註解背後的工做流程。
能夠看出 ButterKnife 整個過程是在項目編譯階段完成的,主要用到了 annotationProcessor 和 JavaPoet 技術,使用時經過生成的輔助類完成操做,並非在項目運行時經過註解加反射實現的,因此並不會影響項目運行時的性能,可能僅在項目編譯時有略微的影響。