1、ButterKnife簡(jiǎn)介
ButterKnife是Android大神JakeWharton出品的又一神器,它通過(guò)注解的方式來(lái)替代findViewById來(lái)綁定view和一系列匿名的view的事件。最常見(jiàn)的用法如下:
@BindView(R.id.title) TextView title;
@BindView(R.id.subtitle) TextView subtitle;
@BindView(R.id.footer) TextView footer;
@OnClick(R.id.submit)
public void sayHi(Button button) {
button.setText("Hello!");
}
但是ButterKnife除了綁定view和添加事件,還能綁定Resource,有@BindBool, @BindColor, @BindDimen, @BindDrawable, @BindInt, @BindString等標(biāo)簽。
2、ButterKnife總體架構(gòu)
以截止到2017.6.29的最新版本8.6.0源碼分析。它的各個(gè)模塊的依賴關(guān)系如下:

1、butterknife是對(duì)外提供接口的模塊;
2、butterknife-compile負(fù)責(zé)編譯期對(duì)注解進(jìn)行解析,然后生成綁定view的java文件,以xxx_ViewBinding命名,放在對(duì)應(yīng)工程/build/generated/source目錄下;
3、butterknife-gradle—plugin是8.2.0之后為了支持library工程而新增的模塊;
4、butterknife-annotations是專門(mén)放置注解的包,我們常使用的Bindview、BindString、OnClick等注解都在這個(gè)包里;
5、butterknife是針對(duì)butterknife-gradle—plugin提供的靜態(tài)代碼檢查工具。
3、注解(Annotation)
閱讀ButterKnife的源碼,必須要對(duì)java的注解有一定的了解,比如@Override,@Deprecated等,這種注解大家一定見(jiàn)過(guò)。
注解的目的是將一些本來(lái)重復(fù)性的工作,變成程序自動(dòng)完成,簡(jiǎn)化和自動(dòng)化該過(guò)程。比如用于生成Javadoc,比如編譯時(shí)進(jìn)行格式檢查,比如自動(dòng)生成代碼等,用于提升軟件的質(zhì)量和提高軟件的生產(chǎn)效率。
作為Android開(kāi)發(fā),日常碰到的注解主要有來(lái)自JDK里的,也有Android SDK里的,也有自定義的。推薦閱讀
ButterKnife其實(shí)就是基于自定義注解的方式實(shí)現(xiàn)的。
3.1 andorid-apt(Annotation Processing Tool)
android-apt 是一個(gè)Gradle插件,協(xié)助Android Studio 處理annotation processors, 它有兩個(gè)目的:
1、允許配置只在編譯時(shí)作為注解處理器的依賴,而不添加到最后的APK或library
2、設(shè)置源路徑,使注解處理器生成的代碼能被Android Studio正確的引用
使用該插件,添加如下到你的構(gòu)建腳本中:
//配置在Project下的build.gradle中 buildscript {
repositories {
mavenCentral()
}
dependencies {
//替換成最新的 gradle版本
classpath 'com.android.tools.build:gradle:1.3.0'
//替換成最新android-apt版本
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
} } //配置到Module下的build.gradle中 apply plugin: 'com.android.application' apply plugin: 'com.neenbedankt.android-apt'
伴隨著 Android Gradle 插件 2.2 版本的發(fā)布,,Android Gradle 插件提供了名為 annotationProcessor 的功能來(lái)完全代替 android-apt。
修改前配置如下:
compile 'com.jakewharton:butterknife:8.0.1'
apt 'com.jakewharton:butterknife-compiler:8.0.1'
修改后配置如下:
compile 'com.jakewharton:butterknife:8.6.0'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.6.0'
整個(gè)注解處理過(guò)程包括三部分:1、注解處理器(Processor);2、注冊(cè)注解處理器(AutoService);2、代碼生成器(JavaPoet).
示例:
package com.example;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
private Types typeUtils;
private Elements elementUtils;
private Filer filer;
private Messager messager;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
//提供處理注解和生成代碼的工具類
typeUtils = processingEnv.getTypeUtils();
elementUtils = processingEnv.getElementUtils();
filer = processingEnv.getFiler();
messager = processingEnv.getMessager();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//1、處理注解;
//2、生成代碼。
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotataions = new LinkedHashSet<String>();
annotataions.add(MyAnnotation.class.getCanonicalName());
return annotataions;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
定義完注解處理器后,還需要告訴編譯器該注解處理器的信息,需在 src/main/resource/META-INF/service 目錄下增加 javax.annotation.processing.Processor 文件,并將注解處理器的類名配置在該文件中。
![image.png-33kB][2]
![image.png-34.9kB][3]
javapoet也是square開(kāi)源的生成.java文件的API。
4、ButterKnife整體流程

4.1 ButterKnifeProcessor
ButterKnifeProcessor主要做了兩件事:
1、解析所有包含了ButterKnife注解的類;
2、根據(jù)解析結(jié)果,使用JavaPoet生成對(duì)應(yīng)的類。
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
//解析所有包含了ButterKnife注解的類
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
//根據(jù)解析結(jié)果,使用JavaPoet生成對(duì)應(yīng)的類
JavaFile javaFile = binding.brewJava(sdk);
try {
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return false;
}
ButterKnife支持的所有注解在getSupportedAnnotations方法里面定義。
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;
}
private static final List<Class<? extends Annotation>> LISTENERS = Arrays.asList(//
OnCheckedChanged.class, //
OnClick.class, //
OnEditorAction.class, //
OnFocusChange.class, //
OnItemClick.class, //
OnItemLongClick.class, //
OnItemSelected.class, //
OnLongClick.class, //
OnPageChange.class, //
OnTextChanged.class, //
OnTouch.class //
);
4.2 @BindView、@BindString、@OnClick
下面以@BindView、@BindString、@OnClick三個(gè)比較常用的注解為例,分析下具體的綁定過(guò)程。
在findAndParseTargets方法中解析所有注解,每個(gè)類對(duì)應(yīng)一個(gè)BindingSet,解析過(guò)程中會(huì)把每個(gè)注解的解析結(jié)果放到BindingSet中,比如@BindString對(duì)應(yīng)FieldResourceBinding類型,@BindView對(duì)應(yīng)FieldViewBinding類型,解析完成后調(diào)用BindingSet的brewJava生成對(duì)應(yīng)的JavaFile,即可通過(guò)JavaPoet生成.java文件。
4.2.1@BindView:
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> erasedTargetNames) {
//1、TypeElement就是該注解Element所在的類
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// Start by verifying common generated code restrictions.
//2、驗(yàn)證生成代碼時(shí)是否可見(jiàn)和是否在錯(cuò)誤的包里面
boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
|| isBindingInWrongPackage(BindView.class, element);
// Verify that the target type extends from View.
//3、 驗(yàn)證目標(biāo)類型是否繼承自View
TypeMirror elementType = element.asType();
if (elementType.getKind() == TypeKind.TYPEVAR) {
TypeVariable typeVariable = (TypeVariable) elementType;
elementType = typeVariable.getUpperBound();
}
Name qualifiedName = enclosingElement.getQualifiedName();
Name simpleName = element.getSimpleName();
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;
}
}
//4、上述兩步只要有錯(cuò)誤,直接返回
if (hasError) {
return;
}
// Assemble information on the field.
//5、解析注解的value
int id = element.getAnnotation(BindView.class).value();
//6、查找所在類對(duì)應(yīng)的BindingSet構(gòu)造類。如果有還要判斷這個(gè)view的id是否已經(jīng)綁定過(guò),如果沒(méi)有則新建。
BindingSet.Builder builder = builderMap.get(enclosingElement);
QualifiedId qualifiedId = elementToQualifiedId(element, id);
if (builder != null) {
String existingBindingName = builder.findExistingBindingName(getId(qualifiedId));
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);
}
//7、解析注解的view。比如@BindView(R.id.tv_title) TextView tvTitle,那么name就是tvTitle,type就是TextView,required只要沒(méi)有@Nullable的注解就是true。
String name = simpleName.toString();
TypeName type = TypeName.get(elementType);
boolean required = isFieldRequired(element);
//8、將上述信息封裝成FieldViewBinding,加到builder中。
builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
}
4.2.2@BindString:
private void parseResourceString(Element element,
Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) {
boolean hasError = false;
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// Verify that the target type is String.
if (!STRING_TYPE.equals(element.asType().toString())) {
error(element, "@%s field type must be 'String'. (%s.%s)",
BindString.class.getSimpleName(), enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
// Verify common generated code restrictions.
hasError |= isInaccessibleViaGeneratedCode(BindString.class, "fields", element);
hasError |= isBindingInWrongPackage(BindString.class, element);
if (hasError) {
return;
}
// Assemble information on the field.
String name = element.getSimpleName().toString();
int id = element.getAnnotation(BindString.class).value();
QualifiedId qualifiedId = elementToQualifiedId(element, id);
BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
builder.addResource(
new FieldResourceBinding(getId(qualifiedId), name, FieldResourceBinding.Type.STRING));
erasedTargetNames.add(enclosingElement);
}
看代碼,跟綁定View的過(guò)程基本一致,唯一的區(qū)別是它最后封裝成了
FieldResourceBinding類,然后通過(guò)addResource加到builder中。
4.2.3@OnClick:
所有Listener的解析都在parseListenerAnnotation方法中,代碼總共200多行,這里不一一分析了,下面截取關(guān)鍵代碼做解析。
以如下注解代碼為例:
package test;
import butterknife.OnClick;
public class Test {
@OnClick(1) void doStuff() {}
}
ExecutableElement executableElement = (ExecutableElement) element;//(doStuff())
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();//(test.Test)
Annotation annotation = element.getAnnotation(annotationClass);//(interface butterknife.OnClick)
Method annotationValue = annotationClass.getDeclaredMethod("value");//(public abstract int[] butterknife.OnClick.value())
int[] ids = (int[])annotationValue.invoke(annotation);//(1)
String name = executableElement.getSimpleName().toString();//(doStuff)
boolean required = isListenerRequired(executableElement);//(true)
//
上面是注解及其節(jié)點(diǎn)的一些基本屬性。后面的值是調(diào)試過(guò)程中看到的值。
下面看下OnClick注解的定義:
@Target(METHOD)
@Retention(CLASS)
@ListenerClass(
targetType = "android.view.View",
setter = "setOnClickListener",
type = "butterknife.internal.DebouncingOnClickListener",
method = @ListenerMethod(
name = "doClick",
parameters = "android.view.View"
)
)
public @interface OnClick {
/** View IDs to which the method will be bound. */
@IdRes int[] value() default { View.NO_ID };
}
它有一個(gè)ListenerClass的注解,用來(lái)定義目標(biāo)類型、設(shè)置方法、listener類型、method(listener里面只有一個(gè)回調(diào)時(shí)使用)、callbacks(listener里面有多個(gè)回調(diào)時(shí)使用)。
比如OnTextChanged里面的ListenerClass注解是這樣的,它只實(shí)現(xiàn)OnTextChanged方法的回調(diào):
@ListenerClass(
targetType = "android.widget.TextView",
setter = "addTextChangedListener",
remover = "removeTextChangedListener",
type = "android.text.TextWatcher",
callbacks = OnTextChanged.Callback.class
)
繼續(xù)看parseListenerAnnotation方法里面的代碼。
ListenerClass listener = annotationClass.getAnnotation(ListenerClass.class);
ListenerMethod method;
ListenerMethod[] methods = listener.method();
通過(guò)解析ListenerClass,獲取一個(gè)method實(shí)例。然后再根據(jù)methodParameters及其其他一些邏輯生成一個(gè)參數(shù)列表。
MethodViewBinding binding = new MethodViewBinding(name, Arrays.asList(parameters), required);
builder.addMethod(getId(qualifiedId), listener, method, binding)
最后把注解listener、method和binding加入到builder中,用戶后續(xù)生成代碼。
4.2.4 上面大致分析了三種不同注解的解析方式,下面看下到底是如何通過(guò)BuildSet生成java類的。
4.3 生成.java文件
JavaFile javaFile = binding.brewJava(sdk);
javaFile.writeTo(filer);
ButterKnifeProcessor中process方法中上面的代碼觸發(fā)了生成代碼的邏輯,最后寫(xiě)到文件中。
JavaFile brewJava(int sdk) {
return JavaFile.builder(bindingClassName.packageName(), createType(sdk))
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
brewJava調(diào)用createType構(gòu)造具體的類。其中最重要的代碼是:
result.addMethod(createBindingConstructor(sdk));
它負(fù)責(zé)在構(gòu)造方法中綁定view,生成的代碼類似(源碼中SimpleActivity)如下:
@UiThread
public SimpleActivity_ViewBinding(final SimpleActivity target, View source) {
this.target = target;
View view;
target.title = Utils.findRequiredViewAsType(source, R.id.title, "field 'title'", TextView.class);
target.subtitle = Utils.findRequiredViewAsType(source, R.id.subtitle, "field 'subtitle'", TextView.class);
view = Utils.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', and method 'sayGetOffMe'");
target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class);
view2130968578 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.sayHello();
}
});
view.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View p0) {
return target.sayGetOffMe();
}
});
view = Utils.findRequiredView(source, R.id.list_of_things, "field 'listOfThings' and method 'onItemClick'");
target.listOfThings = Utils.castView(view, R.id.list_of_things, "field 'listOfThings'", ListView.class);
view2130968579 = view;
((AdapterView<?>) view).setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> p0, View p1, int p2, long p3) {
target.onItemClick(p2);
}
});
target.footer = Utils.findRequiredViewAsType(source, R.id.footer, "field 'footer'", TextView.class);
target.headerViews = Utils.listOf(
Utils.findRequiredView(source, R.id.title, "field 'headerViews'"),
Utils.findRequiredView(source, R.id.subtitle, "field 'headerViews'"),
Utils.findRequiredView(source, R.id.hello, "field 'headerViews'"));
}
下面結(jié)合createBindingConstructor方法做具體分析,直接在代碼中進(jìn)行注釋說(shuō)明。
private MethodSpec createBindingConstructor(int sdk) {
========
MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
.addAnnotation(UI_THREAD)
.addModifiers(PUBLIC);
========1、上面代碼在方法SimpleActivity_ViewBinding增加UI_THREAD注解和public修飾符。
if (hasMethodBindings()) {
constructor.addParameter(targetTypeName, "target", FINAL);
} else {
constructor.addParameter(targetTypeName, "target");
}
========2、上面代碼增加參數(shù)final SimpleActivity target
if (constructorNeedsView()) {
constructor.addParameter(VIEW, "source");
} else {
constructor.addParameter(CONTEXT, "context");
}
========3、上面代碼增加參數(shù)View source
if (hasUnqualifiedResourceBindings()) {
// Aapt can change IDs out from underneath us, just suppress since all will work at runtime.
constructor.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class)
.addMember("value", "$S", "ResourceType")
.build());
}
if (hasOnTouchMethodBindings()) {
constructor.addAnnotation(AnnotationSpec.builder(SUPPRESS_LINT)
.addMember("value", "$S", "ClickableViewAccessibility")
.build());
}
===========
if (parentBinding != null) {
if (parentBinding.constructorNeedsView()) {
constructor.addStatement("super(target, source)");
} else if (constructorNeedsView()) {
constructor.addStatement("super(target, source.getContext())");
} else {
constructor.addStatement("super(target, context)");
}
constructor.addCode("\n");
}
========上面代碼是判斷有繼承的情況
if (hasTargetField()) {
constructor.addStatement("this.target = target");
constructor.addCode("\n");
}
========上面代碼增加this.target = target;
if (hasViewBindings()) {
if (hasViewLocal()) {
// Local variable in which all views will be temporarily stored.
constructor.addStatement("$T view", VIEW);
}
for (ViewBinding binding : viewBindings) {
addViewBinding(constructor, binding);
}
for (FieldCollectionViewBinding binding : collectionBindings) {
constructor.addStatement("$L", binding.render());
}
if (!resourceBindings.isEmpty()) {
constructor.addCode("\n");
}
}
========上面代碼實(shí)現(xiàn)view的綁定,具體代碼在addViewBinding,它會(huì)根據(jù)不同的情況生成不同的代碼;
if (!resourceBindings.isEmpty()) {
if (constructorNeedsView()) {
constructor.addStatement("$T context = source.getContext()", CONTEXT);
}
if (hasResourceBindingsNeedingResource(sdk)) {
constructor.addStatement("$T res = context.getResources()", RESOURCES);
}
for (ResourceBinding binding : resourceBindings) {
constructor.addStatement("$L", binding.render(sdk));
}
}
========上面代碼實(shí)現(xiàn)resource的綁定;
return constructor.build();
}
addViewBinding方法里面的大致步驟是先f(wàn)indView,然后調(diào)用addFieldBinding(result, binding)和addMethodBindings(result, binding)實(shí)現(xiàn)view的綁定和listener的設(shè)置。
5 運(yùn)行期間
運(yùn)行期間,ButterKnife.bind(this)通過(guò)反射去獲取對(duì)應(yīng)的xxx__ViewBinding類的實(shí)例,在該類的構(gòu)造方法中,完成view或者resource的查找和綁定,以及l(fā)istener的設(shè)置。
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
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);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to invoke " + constructor, e);
} catch (InstantiationException e) {
throw new RuntimeException("Unable to invoke " + constructor, e);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
}
if (cause instanceof Error) {
throw (Error) cause;
}
throw new RuntimeException("Unable to create binding instance.", cause);
}
}
參考鏈接:
1、https://github.com/ShowJoy-com/showjoy-blog/issues/30?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io
2、http://www.itdecent.cn/p/b8b59fb80554
3、http://blog.csdn.net/crazy1235/article/details/51876192
4、http://blog.csdn.net/asce1885/article/details/52878076