ButterKnife源碼分析
1. ButterKnife 簡介
1.1 代碼結(jié)構(gòu)
- butterknife ;android library model 提供android使用的API
- butterknife-annotations; java-model砸烦,使用時的注解
- butterknife-compiler钻心;java-model杆麸,編譯時用到的注解的處理器
- butterknife-gradle-plugin;自定義的gradle插件胧卤,輔助生成有關(guān)代碼
- butterknife-integration-test蠢护;該項目的測試用例
- butterknife-lint;該項目的lint檢查
1.2 流程簡述
[圖片上傳失敗...
2. ButterKnife解析
2.1
對控件進(jìn)行綁定:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.btn1)
Button btn;
@BindView(R.id.tv1)
TextView tv;
@OnClick(R.id.btn1)
public void btnClick() {
Toast.makeText(this, "aa", Toast.LENGTH_SHORT).show();
}
2.2 編譯唆迁,生成Java代碼
2.3.1
在Java代碼的編譯時期鸭丛,javac 會調(diào)用java注解處理器來生成輔助代碼竞穷。生成的代碼就在 build/generated/source/apt
public class MainActivity_ViewBinding<T extends MainActivity> implements Unbinder {
protected T target;
private View view2131427416;
@UiThread
public MainActivity_ViewBinding(final T target, View source) {
this.target = target;
View view;
view = Utils.findRequiredView(source, R.id.btn1, "field 'btn' and method 'btnClick'");
//這里的 target 其實就是我們的 Activity
//這個castView就是將得到的View轉(zhuǎn)化成具體的子View
target.btn = Utils.castView(view, R.id.btn1, "field 'btn'", Button.class);
view2131427416 = view;
//為按鈕設(shè)置點擊事件
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.btnClick();
}
});
target.tv = Utils.findRequiredViewAsType(source, R.id.tv1, "field 'tv'", TextView.class);
}
@Override
@CallSuper
public void unbind() {
T target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
target.btn = null;
target.tv = null;
view2131427416.setOnClickListener(null);
view2131427416 = null;
this.target = null;
}
方法說明:
findRequiredView:這個方法就是找到布局文件配置的View。世人唾棄的findViewById方法就是在這里自動調(diào)用的鳞溉。
View view = source.findViewById(id);
if (view != null) {
return view;
}
2.3.2
ButterKnife注解處理器
用來解析注解并生成對應(yīng)java代碼
ButterKnife注解處理器里包含下面幾個重要的方法:
- init() :初始化瘾带,得到Elements、Types熟菲、Filer等工具類
- getSupportedAnnotationTypes() :描述注解處理器需要處理的注解
- process() :掃描分析注解看政,生成代碼。這個是ButterKnife的核心抄罕,下面著重分析允蚣。
process方法:
- 獲得TypeElement -> BindingSet的映射關(guān)系
- 運用JavaPoet框架來生成形式為xxxx_ViewBinding代碼
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
//查找所有的注解信息,并形成BindingClass保存到 map中
Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);
// 生成MainActivity_ViewBinding代碼
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());
}
}
findAndParseTargets方法
省略掉無關(guān)的代碼呆贿,findAndParseTargets分成三部分:
- scanForRClasses:建立view與R的id的關(guān)系,將R.id轉(zhuǎn)換成MainActivity_ViewBinding中的view嚷兔。
- 解析各種注解(這里以BindView為例)森渐。
- 根據(jù)buildMap來生成bindingMap。
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
// 建立view與R的id的關(guān)系
scanForRClasses(env);
// 解析查找所有包含BindView注解的element
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
try {
parseBindView(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindView.class, e);
}
}
// 將Map.Entry<TypeElement, BindingSet.Builder>轉(zhuǎn)化為Map<TypeElement, BindingSet>
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);
if (parentType == null) {
bindingMap.put(type, builder.build());
} else {
BindingSet parentBinding = bindingMap.get(parentType);
if (parentBinding != null) {
builder.setParent(parentBinding);
bindingMap.put(type, builder.build());
} else {
// Has a superclass binding but we haven't built it yet. Re-enqueue for later.
entries.addLast(entry);
}
}
}
return bindingMap;
}
在注解處理器中冒晰,我們掃描 java 源文件同衣,源代碼中的每一部分都是Element的一個特定類型。換句話說:Element代表程序中的元素壶运,比如說 包耐齐,類,方法蒋情。每一個元素代表一個靜態(tài)的埠况,語言級別的結(jié)構(gòu),這些都是Element的子類
比如:
public class ClassA { // TypeElement
private int var_0; // VariableElement
public ClassA() {} // ExecuteableElement
public void setA( // ExecuteableElement
int newA // TypeElement
) {
}
}
TypeMirror:
TypeMirror表示 Java 編程語言中的類型。這些類型包括基本類型棵癣、聲明類型(類和接口類型)询枚、數(shù)組類型、類型變量和 null 類型浙巫。還可以表示通配符類型參數(shù)金蜀、executable 的簽名和返回類型,以及對應(yīng)于包和關(guān)鍵字 void 的偽類型的畴。
parseBindView方法
parseBindView先檢測是否有錯誤渊抄,然后將name(變量名,例如tvTitle)丧裁、type(類名护桦,例如TextView)、required(是否有@nullable注解)封裝成FieldViewBinding放到builder這個Map里煎娇。
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> erasedTargetNames) {
// 得到包含注解所屬的TypeElement二庵,例如MainActivity
//判斷修飾符,如果包含private or static //就會拋出異常缓呛。
//判斷父節(jié)點如果是private 類催享,則拋出異常
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
//1. isInaccessibleViaGeneratedCode檢驗enclosingElement(MainActivity)是類、不是private哟绊,檢驗element不是private活著static
// isBindingInWrongPackage檢驗enclosingElement的包名是不是系統(tǒng)相關(guān)的類
boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element) || isBindingInWrongPackage(BindView.class, element);
TypeMirror elementType = element.asType();
if (elementType.getKind() == TypeKind.TYPEVAR) {
TypeVariable typeVariable = (TypeVariable) elementType;
elementType = typeVariable.getUpperBound();
}
Name qualifiedName = enclosingElement.getQualifiedName();
Name simpleName = element.getSimpleName();
// 判斷element是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;
}
//2.獲取id 值
int id = element.getAnnotation(BindView.class).value();
//// 3.獲取 BindingClass因妙,有緩存機(jī)制, 沒有則創(chuàng)建
BindingSet.Builder builder = builderMap.get(enclosingElement);
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 {
builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
}
//4. 生成FieldViewBinding 實體
String name = simpleName.toString();
TypeName type = TypeName.get(elementType);
boolean required = isFieldRequired(element);
//5.FieldViewBinding加入到 bindingClass 成員變量的集合中
builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
}
上面代碼可分為5個步驟
1. 檢查用戶使用的合法性
private boolean isInaccessibleViaGeneratedCode(Class<? extends Annotation> annotationClass,
String targetThing, Element element) {
boolean hasError = false;
// 得到父節(jié)點
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é)點是否是類類型的攀涵,不是的話就會拋出異常
//也就是說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é)點如果是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;
}
//不能再以android開頭的包中使用
if (qualifiedName.startsWith("android.")) {
error(element, "@%s-annotated class incorrectly in Android framework package. (%s)",
annotationClass.getSimpleName(), qualifiedName);
return true;
}
//不能再以java開頭的包中使用
if (qualifiedName.startsWith("java.")) {
error(element, "@%s-annotated class incorrectly in Java framework package. (%s)",
annotationClass.getSimpleName(), qualifiedName);
return true;
}
2. 獲取自己傳入的id值
3. 獲取BindingClass洽沟,如果緩存中沒有就新建一個實例以故。
4. 獲取注解名字和控件類型,生成FieldViewBinding裆操。
5. 添加到bindingClass集合中
2.3.3 運用JavaPoet框架來生成代碼
在生成Map<TypeElement,BindingSet>后怒详,下一步就是遍歷Map,調(diào)用每一個類的brewJava()方法鳄乏,根據(jù)TypeElement(MainActivity)利用Filer工具類來生成文件TypeElement_ViewBinding(MainActivity_ViewBinding)。
private TypeSpec createBindingClass() {
1. 生成類名
//bindingClassName.simpleName() 就是我們在findAndParseTargets方法形成的類的名字:xxx_ViewBinding名字
xxx也就是使用butterknife的類名
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
//增加類修飾符public
.addModifiers(PUBLIC);
//使用butterknife的類是 final類
TypeName targetType;
if (isFinal) {
//增加類修飾符FINAL
result.addModifiers(FINAL);
targetType = targetTypeName;
} else {
//不是final類棘利,增加泛型參數(shù)T
targetType = TypeVariableName.get("T");
result.addTypeVariable(TypeVariableName.get("T", targetTypeName));
}
//如果有父類直接繼承
if (hasParentBinding()) {
result.superclass(ParameterizedTypeName.get(getParentBinding(), targetType));
} else {
//增加實現(xiàn)接口
result.addSuperinterface(UNBINDER);
//增加成員變量
result.addField(targetType, "target", isFinal ? PRIVATE : PROTECTED);
}
}
2. 生成構(gòu)造函數(shù)
if (!bindNeedsView()) {
// Add a delegating constructor with a target type + view signature for reflective use.
result.addMethod(createBindingViewDelegateConstructor(targetType));
}
result.addMethod(createBindingConstructor(targetType));
3. 生成unbind方法橱野。
if (hasViewBindings() || !hasParentBinding()) {
result.addMethod(createBindingUnbindMethod(result, targetType));
}
return result.build();
}
3. ButterKnife.bind(this)
編譯生成代碼后,在bind方法中調(diào)用善玫。
ButterKnife.bind的一系列方法都會調(diào)用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);
}
...
這里通過獲取Class獲取XXX_ViewBinding的Constructor構(gòu)造方法水援,然后調(diào)用constructor.newInstance(target, source)創(chuàng)建生成代碼的對象。
findBindingConstructorForClass方法
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 {
//構(gòu)造一個class蜗元,可以看類名就是我們生成的。
Class<?> bindingClass = Class.forName(clsName + "_ViewBinding");
//noinspection unchecked
// 獲取我們的構(gòu)造函數(shù)
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;
}
這里的BINGDINGS是一個Constructor的Map緩存系冗。
4. ButterKnife學(xué)習(xí)曲線
[圖片上傳失敗...(image-4f410b-1534244273443)]