ButterKnife源碼簡析
ButterKnife 是一個 Android 視圖快速注入庫,它通過給 View 字段添加注解,可以讓我們丟掉 findViewById() 來獲取 View 的方法,從而簡化了代碼。
編譯時注解
概述
編譯時注解的核心依賴 APT ( Annotation Processing Tools ) 實現,原理是在某些代碼元素上(如類型、函數、字段等)添加注解,在編譯時編譯器會檢查 AbstractProcessor 的子類,并且調用該類型的 process 函數,然后將添加了注解的所有元素都傳遞到 process 函數中,使得開發人員可以在編譯器進行相應的處理,例如,根據注解生成新的Java類,這也就是EventBus,Retrofit,Dragger等開源庫的基本原理。
創建
創建一個 Java Library
創建一個 annotationcompiler 的 java module
配置 gradle
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
同時需要在 app 的 module 中配置一下 Java7
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
創建 Annotation
package com.yydcdut.annotation;
public @interface InjectView {
int value();
}
創建 AbstractProcessor
package com.yydcdut.process;
@SupportedAnnotationTypes("com.yydcdut.annotation.InjectView")
public class ViewInjectProcessor extends AbstractProcessor {
/**
* 初始化
*
* @param processingEnvironment 提供了一些實用的工具類Elements, Types和Filer
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
/**
* 指定使用的java版本
*
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
/**
* 指定哪些注解應該被注解處理器注冊
*
* @return
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
return super.getSupportedAnnotationTypes();
}
/**
* 主要的邏輯處理
*
* @param set
* @param env
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment env) {
return false;
}
}
注冊處理器
在 java module 里的 main 目錄中創建一個 resources 文件夾,然后下邊在創建 META-INF/services ,創建一個 javax.annotation.processing.Processor 文件,在此文件中寫入注解處理器的類全稱
com.yydcdut.process.ViewInjectProcessor
添加 android-apt
在 project 下的 build.gradle 中添加 apt 插件
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
然后在 app 中的 build.gradle 添加 apt 插件
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
添加依賴
在 app 中的 build.gradle 添加依賴
dependencies {
compile project(':annotationcompiler')
}
Code
@SupportedAnnotationTypes("com.yydcdut.annotation.InjectView")
public class ViewInjectProcessor extends AbstractProcessor {
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<String>();
types.add(InjectView.class.getCanonicalName());
return types;
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment env) {
generateCode(set, env);
return true;
}
private void generateCode(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
StringBuilder builder = new StringBuilder()
.append("package com.yydcdut.test.generated;\n\n")
.append("public class GeneratedClass {\n\n") // open class
.append("\tpublic String getMessage() {\n") // open method
.append("\t\treturn \"");
// for each javax.lang.model.element.Element annotated with the CustomAnnotation
for (Element element : roundEnv.getElementsAnnotatedWith(InjectView.class)) {
int id = element.getAnnotation(InjectView.class).value();
builder.append(id + "\n");
}
builder.append("\";\n") // end return
.append("\t}\n") // close method
.append("}\n"); // close class
try { // write the file
JavaFileObject source = processingEnv.getFiler().createSourceFile("com.yydcdut.test.generated.GeneratedClass");
Writer writer = source.openWriter();
writer.write(builder.toString());
writer.flush();
writer.close();
} catch (IOException e) {
// Note: calling e.printStackTrace() will print IO errors
// that occur from the file already existing after its first run, this is normal
e.printStackTrace();
}
}
}
被注解的部分代碼:
package com.yydcdut.textdemo;
public class MainActivity extends Activity {
@InjectView(value = 123)
private EditText mEditText;
}
編譯之后查看 app/build/generated/source/apt/debug/com/yydcdut/test/generated/GeneratedClass.java
源碼解析
ButterKnifeProcessor
這里只分析 BindView 的過程
ButterKnifeProcessor
根據上述創建注解器的流程,那么我們分析源碼也有了一個流程,就直接看 ButterKnife 的 AbstractProcessor :
@AutoService(Processor.class)//自動注冊處理器
public final class ButterKnifeProcessor extends AbstractProcessor {
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 //
);
private Elements elementUtils;//處理Element的工具類
private Types typeUtils;//處理TypeMirror的工具類
private Filer filer;//可以創建.java文件
@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
//......
elementUtils = env.getElementUtils();
typeUtils = env.getTypeUtils();
filer = env.getFiler();
//......
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}
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;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);////查找所有的被注解,封裝成BindingSet保存到map中
//遍歷步map的生成.java文件
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
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;
}
}
在 process 中主要做了兩件事,第一是找出被注解的元素,封裝成 Map<TypeElement, BindingSet> ,第一個參數可以想成類,第二個參數可以想成所有有關信息的封裝;第二就是遍歷這個 map ,然后針對每個類生成對應的 ViewBinder 類。
ButterKnifeProcessor#findAndParseTargets
那么主要的查找工作在 findAndParseTargets() 中進行:
public final class ButterKnifeProcessor extends AbstractProcessor {
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
// .......
// Process each @BindView element.
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {//拿到被BindView注解的元素
// we don't SuperficialValidation.validateElement(element)
// so that an unresolved View type can be generated by later processing rounds
try {
parseBindView(element, builderMap, erasedTargetNames);//解析
} catch (Exception e) {
logParsingError(element, BindView.class, e);
}
}
// .......
return bindingMap;
}
}
RoundEnvironment 可以理解成查詢注解信息的類,而 Element 可以理解成程序中的元素,比如包、類、方法等等。
public class ClassA { // TypeElement
private int var; // VariableElement
public ClassA() {// ExecuteableElement
}
public void setA(int newA // TypeElement
) {
}
}
TypeElement 代表源代碼中元素類型,但是 TypeElement 并不包含類的相關信息,可以從 TypeElement 獲取類的名稱,但不能獲取類的信息,比如說父類。這些信息可以通過 TypeMirror 獲取。你可以通過調用 element.asType() 來獲取一個 Element 的 TypeMirror 。
ButterKnifeProcessor#parseBindView
public final class ButterKnifeProcessor extends AbstractProcessor {
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();//類 com.yydcdut.textdemo.MainActivity
// Start by verifying common generated code restrictions.
boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
|| isBindingInWrongPackage(BindView.class, element);//檢查合法性
// Verify that the target type extends from View.
TypeMirror elementType = element.asType();//android.widget.EditText
if (elementType.getKind() == TypeKind.TYPEVAR) {//false (elementType.getKind()==DECLARED)
TypeVariable typeVariable = (TypeVariable) elementType;
elementType = typeVariable.getUpperBound();
}
Name qualifiedName = enclosingElement.getQualifiedName();//com.yydcdut.textdemo.MainActivity
Name simpleName = element.getSimpleName();//mEditText
if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {//必須為view類型的子類或者是接口
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;
}
// Assemble information on the field.
int id = element.getAnnotation(BindView.class).value();//得到id
BindingSet.Builder builder = builderMap.get(enclosingElement);
QualifiedId qualifiedId = elementToQualifiedId(element, id);//封裝成QualifiedId
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);//為這個類創建BindingSet.Builder
}
String name = simpleName.toString();//mEditText
TypeName type = TypeName.get(elementType);//android.widget.EditText
boolean required = isFieldRequired(element);
builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));//封裝后放入builder中
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
}
/**
* 判斷修飾符等
*
* @param annotationClass
* @param targetThing
* @param element
* @return
*/
private boolean isInaccessibleViaGeneratedCode(Class<? extends Annotation> annotationClass, String targetThing, Element element) {
boolean hasError = false;
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// Verify method modifiers.
Set<Modifier> modifiers = element.getModifiers();
if (modifiers.contains(PRIVATE) || modifiers.contains(STATIC)) {//不能是private或者static的
error(element, "@%s %s must not be private or static. (%s.%s)", annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName());
hasError = true;
}
// Verify containing type.
if (enclosingElement.getKind() != CLASS) {//不能是class
error(enclosingElement, "@%s %s may only be contained in classes. (%s.%s)", annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName());
hasError = true;
}
// Verify containing class visibility is not private.
if (enclosingElement.getModifiers().contains(PRIVATE)) {//不能是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或者java的
*
* @param annotationClass
* @param element
* @return
*/
private boolean isBindingInWrongPackage(Class<? extends Annotation> annotationClass, Element element) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
String qualifiedName = enclosingElement.getQualifiedName().toString();//com.yydcdut.textdemo.MainActivity
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;
}
/**
* 是不是otherType的子類型
*
* @param typeMirror
* @param otherType
* @return
*/
static boolean isSubtypeOfType(TypeMirror typeMirror, String otherType) {
if (isTypeEqual(typeMirror, otherType)) {
return true;
}
if (typeMirror.getKind() != TypeKind.DECLARED) {
return false;
}
DeclaredType declaredType = (DeclaredType) typeMirror;
List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
if (typeArguments.size() > 0) {
StringBuilder typeString = new StringBuilder(declaredType.asElement().toString());
typeString.append('<');
for (int i = 0; i < typeArguments.size(); i++) {
if (i > 0) {
typeString.append(',');
}
typeString.append('?');
}
typeString.append('>');
if (typeString.toString().equals(otherType)) {
return true;
}
}
Element element = declaredType.asElement();
if (!(element instanceof TypeElement)) {
return false;
}
TypeElement typeElement = (TypeElement) element;
TypeMirror superType = typeElement.getSuperclass();
if (isSubtypeOfType(superType, otherType)) {
return true;
}
for (TypeMirror interfaceType : typeElement.getInterfaces()) {
if (isSubtypeOfType(interfaceType, otherType)) {
return true;
}
}
return false;
}
/**
* 是不是接口
*
* @param typeMirror
* @return
*/
private boolean isInterface(TypeMirror typeMirror) {
return typeMirror instanceof DeclaredType
&& ((DeclaredType) typeMirror).asElement().getKind() == INTERFACE;
}
private QualifiedId elementToQualifiedId(Element element, int id) {
return new QualifiedId(elementUtils.getPackageOf(element).getQualifiedName().toString(), id);
}
/**
* 判斷有沒有被Nullable修飾
*
* @param element
* @return
*/
private static boolean isFieldRequired(Element element) {
return !hasAnnotationWithName(element, NULLABLE_ANNOTATION_NAME);
}
private BindingSet.Builder getOrCreateBindingBuilder(
Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) {
BindingSet.Builder builder = builderMap.get(enclosingElement);
if (builder == null) {
builder = BindingSet.newBuilder(enclosingElement);
builderMap.put(enclosingElement, builder);
}
return builder;
}
}
這里是解析被 BindView 修飾的元素,將信息都封裝成 BindingSet.Builder 。
BindingSet#newBuilder
final class BindingSet {
private final TypeName targetTypeName;
private final ClassName bindingClassName;
private final boolean isFinal;
private final boolean isView;
private final boolean isActivity;
private final boolean isDialog;
private final ImmutableList<ViewBinding> viewBindings;
private final ImmutableList<FieldCollectionViewBinding> collectionBindings;
private final ImmutableList<ResourceBinding> resourceBindings;
private final BindingSet parentBinding;
private BindingSet(TypeName targetTypeName, ClassName bindingClassName, boolean isFinal,
boolean isView, boolean isActivity, boolean isDialog, ImmutableList<ViewBinding> viewBindings,
ImmutableList<FieldCollectionViewBinding> collectionBindings,
ImmutableList<ResourceBinding> resourceBindings, BindingSet parentBinding) {
this.isFinal = isFinal;
this.targetTypeName = targetTypeName;
this.bindingClassName = bindingClassName;
this.isView = isView;
this.isActivity = isActivity;
this.isDialog = isDialog;
this.viewBindings = viewBindings;
this.collectionBindings = collectionBindings;
this.resourceBindings = resourceBindings;
this.parentBinding = parentBinding;
}
static Builder newBuilder(TypeElement enclosingElement) {
TypeMirror typeMirror = enclosingElement.asType();//android.widget.EditText
boolean isView = isSubtypeOfType(typeMirror, VIEW_TYPE);//是不是View
boolean isActivity = isSubtypeOfType(typeMirror, ACTIVITY_TYPE);//是不是Activity
boolean isDialog = isSubtypeOfType(typeMirror, DIALOG_TYPE);//是不是Dialog
TypeName targetType = TypeName.get(typeMirror);
if (targetType instanceof ParameterizedTypeName) {
targetType = ((ParameterizedTypeName) targetType).rawType;
}
String packageName = getPackage(enclosingElement).getQualifiedName().toString();//com.yydcdut.textdemo
String className = enclosingElement.getQualifiedName().toString().substring(
packageName.length() + 1).replace('.', '$');//MainActivity
ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");//com.yydcdut.textdemo.MainActivity_ViewBinding
boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);//是不是final修飾的
return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog);
}
static final class Builder {
private final TypeName targetTypeName;
private final ClassName bindingClassName;
private final boolean isFinal;
private final boolean isView;
private final boolean isActivity;
private final boolean isDialog;
private final Map<Id, ViewBinding.Builder> viewIdMap = new LinkedHashMap<>();
private Builder(TypeName targetTypeName, ClassName bindingClassName, boolean isFinal,
boolean isView, boolean isActivity, boolean isDialog) {
this.targetTypeName = targetTypeName;
this.bindingClassName = bindingClassName;
this.isFinal = isFinal;
this.isView = isView;
this.isActivity = isActivity;
this.isDialog = isDialog;
}
void addField(Id id, FieldViewBinding binding) {
getOrCreateViewBindings(id).setFieldBinding(binding);
}
private ViewBinding.Builder getOrCreateViewBindings(Id id) {
ViewBinding.Builder viewId = viewIdMap.get(id);
if (viewId == null) {
viewId = new ViewBinding.Builder(id);
viewIdMap.put(id, viewId);
}
return viewId;
}
BindingSet build() {
ImmutableList.Builder<ViewBinding> viewBindings = ImmutableList.builder();
for (ViewBinding.Builder builder : viewIdMap.values()) {
viewBindings.add(builder.build());
}
return new BindingSet(targetTypeName, bindingClassName, isFinal, isView, isActivity, isDialog, viewBindings.build(), collectionBindings.build(), resourceBindings.build(), parentBinding);
}
}
}
ButterKnifeProcessor#findAndParseTargets
public final class ButterKnifeProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
//....
//....
// Associate superclass binders with their subclass binders. This is a queue-based tree walk
// which starts at the roots (superclasses) and walks to the leafs (subclasses).
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();//com.yydcdut.textdemo.MainActivity
BindingSet.Builder builder = entry.getValue();
TypeElement parentType = findParentType(type, erasedTargetNames);//父類
if (parentType == null) {//自己就是了
bindingMap.put(type, builder.build());
} else {
BindingSet parentBinding = bindingMap.get(parentType);//拿到父類的BindingSet
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;
}
/**
* Finds the parent binder type in the supplied set, if any.
*/
private TypeElement findParentType(TypeElement typeElement, Set<TypeElement> parents) {
TypeMirror type;
while (true) {
type = typeElement.getSuperclass();
if (type.getKind() == TypeKind.NONE) {
return null;
}
typeElement = (TypeElement) ((DeclaredType) type).asElement();
if (parents.contains(typeElement)) {
return typeElement;
}
}
}
}
ButterKnifeProcessor#process
findAndParseTargets 算是解析完了,再回過頭來看第二步,生成文件的步驟
public final class ButterKnifeProcessor extends AbstractProcessor {
@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);//變成JavaFile
try {
javaFile.writeTo(filer);//生成文件
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return false;
}
}
將信息轉變成 JavaFile ,通過 filer 完成。
write過程
final class BindingSet {
JavaFile brewJava(int sdk) {
return JavaFile.builder(bindingClassName.packageName(), createType(sdk))//packageName-->com.yydcdut.textdemo
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
private TypeSpec createType(int sdk) {
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(PUBLIC);//申明類
if (isFinal) {
result.addModifiers(FINAL);//添加final關鍵字
}
if (parentBinding != null) {
result.superclass(parentBinding.bindingClassName);//繼承父類
} else {
result.addSuperinterface(UNBINDER);//實現Unbinder接口
}
if (hasTargetField()) {
result.addField(targetTypeName, "target", PRIVATE);//設置private的target變量
}
if (isView) {
result.addMethod(createBindingConstructorForView());
} else if (isActivity) {
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());
}
result.addMethod(createBindingConstructor(sdk));
if (hasViewBindings() || parentBinding == null) {
result.addMethod(createBindingUnbindMethod(result));
}
return result.build();
}
private MethodSpec createBindingConstructorForView() {
MethodSpec.Builder builder = MethodSpec.constructorBuilder()//創建構造器
.addAnnotation(UI_THREAD)//添加UIThread的注解
.addModifiers(PUBLIC)//為public
.addParameter(targetTypeName, "target");//構造函數中得有target參數
if (constructorNeedsView()) {//構造函數中的方法
builder.addStatement("this(target, target)");
} else {
builder.addStatement("this(target, target.getContext())");
}
return builder.build();
}
/**
* True if this binding requires a view. Otherwise only a context is needed.
*/
private boolean constructorNeedsView() {
return hasViewBindings() //
|| parentBinding != null && parentBinding.constructorNeedsView();
}
private MethodSpec createBindingViewDelegateConstructor() {
return MethodSpec.constructorBuilder()
.addJavadoc("@deprecated Use {@link #$T($T, $T)} for direct creation.\n "
+ "Only present for runtime invocation through {@code ButterKnife.bind()}.\n",
bindingClassName, targetTypeName, CONTEXT)
.addAnnotation(Deprecated.class)
.addAnnotation(UI_THREAD)
.addModifiers(PUBLIC)
.addParameter(targetTypeName, "target")
.addParameter(VIEW, "source")
.addStatement(("this(target, source.getContext())"))
.build();
}
private MethodSpec createBindingConstructor(int sdk) {
MethodSpec.Builder constructor = MethodSpec.constructorBuilder()//構造方法
.addAnnotation(UI_THREAD)//UIThread注解
.addModifiers(PUBLIC);//public
if (hasMethodBindings()) {
constructor.addParameter(targetTypeName, "target", FINAL);//final的target變量
} else {
constructor.addParameter(targetTypeName, "target");
}
if (constructorNeedsView()) {
constructor.addParameter(VIEW, "source");//變量名為source的View
} else {
constructor.addParameter(CONTEXT, "context");//變量名為context的Context
}
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) {//如果有父類,則調用super
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");
}
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);//View的初始化
}
for (FieldCollectionViewBinding binding : collectionBindings) {
constructor.addStatement("$L", binding.render());//資源的初始化
}
if (!resourceBindings.isEmpty()) {
constructor.addCode("\n");
}
}
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));//通過不同的SDK版本實現Resource相關(Drawable,Color等)的初始化
}
}
return constructor.build();
}
private void addViewBinding(MethodSpec.Builder result, ViewBinding binding) {
if (binding.isSingleFieldBinding()) {//只有findView的情況,沒有監聽器
// Optimize the common case where there's a single binding directly to a field.
FieldViewBinding fieldBinding = binding.getFieldBinding();
CodeBlock.Builder builder = CodeBlock.builder()
.add("target.$L = ", fieldBinding.getName());//(target-->MainActivity) ($L-->mEditText)
boolean requiresCast = requiresCast(fieldBinding.getType());//是否需要強轉
if (!requiresCast && !fieldBinding.isRequired()) {
builder.add("source.findViewById($L)", binding.getId().code);//findViewById
} else {
builder.add("$T.find", UTILS);
builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView");
if (requiresCast) {
builder.add("AsType");
}
builder.add("(source, $L", binding.getId().code);
if (fieldBinding.isRequired() || requiresCast) {
builder.add(", $S", asHumanDescription(singletonList(fieldBinding)));
}
if (requiresCast) {
builder.add(", $T.class", fieldBinding.getRawType());
}
builder.add(")");//target.mEditText = Utils.findRequiredViewAsType(source, R.id.edit, "field 'mEditText'", EditText.class);
}
result.addStatement("$L", builder.build());
return;
}
List<MemberViewBinding> requiredBindings = binding.getRequiredBindings();
if (requiredBindings.isEmpty()) {
result.addStatement("view = source.findViewById($L)", binding.getId().code);
} else if (!binding.isBoundToRoot()) {
result.addStatement("view = $T.findRequiredView(source, $L, $S)", UTILS,
binding.getId().code, asHumanDescription(requiredBindings));//view = Utils.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', and method 'sayGetOffMe'");
}
addFieldBinding(result, binding);//強轉
addMethodBindings(result, binding);//各種監聽器
}
private void addFieldBinding(MethodSpec.Builder result, ViewBinding binding) {
FieldViewBinding fieldBinding = binding.getFieldBinding();
if (fieldBinding != null) {
if (requiresCast(fieldBinding.getType())) {
result.addStatement("target.$L = $T.castView(view, $L, $S, $T.class)",
fieldBinding.getName(), UTILS, binding.getId().code,
asHumanDescription(singletonList(fieldBinding)), fieldBinding.getRawType());// target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class);
} else {
result.addStatement("target.$L = view", fieldBinding.getName());
}
}
}
}
最后文件的生成是通過 com.squareup.javapoet.JavaFile 生成的。
官方 demo 的栗子
package com.example.butterknife.library;
import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.List;
import butterknife.BindView;
import butterknife.BindViews;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.OnItemClick;
import butterknife.OnLongClick;
import static android.widget.Toast.LENGTH_SHORT;
public class SimpleActivity extends Activity {
private static final ButterKnife.Action<View> ALPHA_FADE = new ButterKnife.Action<View>() {
@Override
public void apply(@NonNull View view, int index) {
AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
alphaAnimation.setFillBefore(true);
alphaAnimation.setDuration(500);
alphaAnimation.setStartOffset(index * 100);
view.startAnimation(alphaAnimation);
}
};
@BindView(R2.id.title)
TextView title;
@BindView(R2.id.subtitle)
TextView subtitle;
@BindView(R2.id.hello)
Button hello;
@BindView(R2.id.list_of_things)
ListView listOfThings;
@BindView(R2.id.footer)
TextView footer;
@BindViews({R2.id.title, R2.id.subtitle, R2.id.hello})
List<View> headerViews;
private SimpleAdapter adapter;
@OnClick(R2.id.hello)
void sayHello() {
Toast.makeText(this, "Hello, views!", LENGTH_SHORT).show();
ButterKnife.apply(headerViews, ALPHA_FADE);
}
@OnLongClick(R2.id.hello)
boolean sayGetOffMe() {
Toast.makeText(this, "Let go of me!", LENGTH_SHORT).show();
return true;
}
@OnItemClick(R2.id.list_of_things)
void onItemClick(int position) {
Toast.makeText(this, "You clicked: " + adapter.getItem(position), LENGTH_SHORT).show();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
ButterKnife.bind(this);
// Contrived code to use the bound fields.
title.setText("Butter Knife");
subtitle.setText("Field and method binding for Android views.");
footer.setText("by Jake Wharton");
hello.setText("Say Hello");
adapter = new SimpleAdapter(this);
listOfThings.setAdapter(adapter);
}
}
官方 demo 的 apt 生成類的栗子
// Generated code from Butter Knife. Do not modify!
package com.example.butterknife.library;
import android.support.annotation.CallSuper;
import android.support.annotation.UiThread;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import butterknife.Unbinder;
import butterknife.internal.DebouncingOnClickListener;
import butterknife.internal.Utils;
import java.lang.IllegalStateException;
import java.lang.Override;
public class SimpleActivity_ViewBinding<T extends SimpleActivity> implements Unbinder {
protected T target;
private View view2130968578;
private View view2130968579;
@UiThread
public SimpleActivity_ViewBinding(final T 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'"));
}
@Override
@CallSuper
public void unbind() {
T target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
target.title = null;
target.subtitle = null;
target.hello = null;
target.listOfThings = null;
target.footer = null;
target.headerViews = null;
view2130968578.setOnClickListener(null);
view2130968578.setOnLongClickListener(null);
view2130968578 = null;
((AdapterView<?>) view2130968579).setOnItemClickListener(null);
view2130968579 = null;
this.target = null;
}
}
ButterKnife
當要進行 bind 的時候,都需要在 Activity 或 View 等的初始化函數中進行綁定,這里就拿 Activity 為栗子進行分析:
public final class ButterKnife {
/**
* BindView annotated fields and methods in the specified {@link Activity}. The current content
* view is used as the view root.
*
* @param target Target activity for view binding.
*/
@NonNull
@UiThread
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);
}
}
@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.")) {//排除android和java的包
if (debug) {
Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
}
return null;
}
try {
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");//加載后面加"_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;
}
}
最終通過 ClassLoader 的方式將類加載出來,最后 constructor.newInstance 方法來調用該類的構造函數。
總結
平時了解到更多的是運行時注解,即聲明注解的生命周期為 RUNTIME ,然后在運行的時候通過反射完成注入,這種方式雖然簡單,但是設計到比較多的反射,必然多多少少會有性能的損耗。而 ButterKnife 用的 APT 編譯時解析技術,比較好的解決了反射這些問題。
APT 大概就是聲明的注解的生命周期為 CLASS ,然后繼承 AbstractProcessor 類。繼承這個類后,在編譯的時候,編譯器會掃描所有帶有你要處理的注解的類,然后再調用 AbstractProcessor 的 process 方法,對注解進行處理,那么就可以在處理的時候,動態生成綁定事件或者控件的 java 代碼,然后在運行的時候,直接調用 bind 方法完成綁定。
其實這種方式的好處是我們不用再一遍一遍地寫 findViewById 和 onClick 等代碼了,這個框架在編譯的時候幫我們自動生成了這些代碼,然后在運行的時候調用就行了。
來自:http://yydcdut.com/2017/04/19/butterknife-analyse/