注:所有分析基于butterknife:8.4.0
源码目录:https://github.com/JakeWharton/butterknife
其中最主要的3个模块是:
一、首先看一下使用
在MainActivity中绑定TextView
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv)
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
}
Rebuild一下在build/generared/source/apt中生成辅助文件,看一下生成的辅助类
import android.support.annotation.CallSuper;
import android.support.annotation.UiThread;
import android.view.View;
import android.widget.TextView;
import butterknife.Unbinder;
import butterknife.internal.Utils;
import java.lang.IllegalStateException;
import java.lang.Override;
public class MainActivity_ViewBinding<T extends MainActivity> implements Unbinder {
protected T target;
@UiThread
public MainActivity_ViewBinding(T target, View source) {
this.target = target;
target.tv = Utils.findRequiredViewAsType(source, R.id.tv, "field 'tv'", TextView.class);
}
@Override
@CallSuper
public void unbind() {
T target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
target.tv = null;
this.target = null;
}
}
二、源码分析
首先大概说一下流程吧。
Butterknife并没有使用反射机制,而是使用编译时注解动态绑定。
其中最要的是这注解处理器ButterKnifeProcessor
源码:
先来看一下@Bindview 注解
@Retention(CLASS) @Target(FIELD)
public @interface BindView {
/** View ID to which the field will be bound. */
@IdRes int value();
}
//@Retention(CLASS)表明是编译时注解
//@Target(FIELD)表明应用于成员变量
使用@BindView 绑定TextView控件,后文会用到这些代码
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv)
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
}
1、ButterKnifeProcessor源码分析
它的主要处理逻辑都在process中,如下所示
public final class ButterKnifeProcessor extends AbstractProcessor {
....
@Override public boolean process(Set extends TypeElement> elements, RoundEnvironment env) {
//1、查找和解析注解,将结果放在bindingMap中
Map bindingMap = findAndParseTargets(env);
for (Map.Entry entry : bindingMap.entrySet()) {
//2、遍历所有注解
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
//3、根据注解生成java文件
JavaFile javaFile = binding.brewJava(sdk, debuggable, useAndroidX);
try {
//4、写入java文件
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return false;
}
}
主要流程是:
扫描所有Butterknife注解
生成辅助类
将辅助类写到文件夹中
接下来查看findAndParseTargets()方法
private Map findAndParseTargets(RoundEnvironment env) {
Map builderMap = new LinkedHashMap<>();
Set erasedTargetNames = new LinkedHashSet<>();
....
// Process each @BindView element.
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
// 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);
}
}
findAndParseTargets方法会查找所有的butterknife注解来进行解析,前面我们使用了@BindView注解,因此这里只截取@Bindview 注解的部分。
private void parseBindView(Element element, Map builderMap,
Set erasedTargetNames) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// Start by verifying common generated code restrictions.
boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
|| isBindingInWrongPackage(BindView.class, element);
//方法修饰符不能为private和static 包含类型不能为非class 包含类型不能为private
// Verify that the target type extends from View.
//验证target继承自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;
}
}
if (hasError) {
return;
}
// Assemble information on the field.
//获取注解标注的值
int id = element.getAnnotation(BindView.class).value();
BindingSet.Builder builder = builderMap.get(enclosingElement);
Id resourceId = elementToId(element, BindView.class, id);
//判断 BindingSet.Builder 是否存在,若存在则复用,不存在,则创建
if (builder != null) {
String existingBindingName = builder.findExistingBindingName(resourceId);
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);
}
String name = simpleName.toString();
TypeName type = TypeName.get(elementType);
boolean required = isFieldRequired(element);
builder.addField(resourceId, new FieldViewBinding(name, type, required));
//将注解修饰的类型信息存储到FieldViewBinding中,注解所修饰的类型信息存储在BindingSet中
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
}
接下来查看如何根据注解生成java文件:
JavaFile brewJava(int sdk, boolean debuggable, boolean useAndroidX) {
TypeSpec bindingConfiguration = createType(sdk, debuggable, useAndroidX);
return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
//将使用注解的类生成一个javafile
接下来将javafile输出成java文件
/** Writes this to {@code filer}. */
public void writeTo(Filer filer) throws IOException {
String fileName = packageName.isEmpty()
? typeSpec.name
: packageName + "." + typeSpec.name;
List originatingElements = typeSpec.originatingElements;
JavaFileObject filerSourceFile = filer.createSourceFile(fileName,
originatingElements.toArray(new Element[originatingElements.size()]));
try (Writer writer = filerSourceFile.openWriter()) {
writeTo(writer);
} catch (Exception e) {
try {
filerSourceFile.delete();
} catch (Exception ignored) {
}
throw e;
}
}
在build/generared/source/apt目录下可以找到生成的java文件,这里生成的文件名叫MainActivity_ViewBinding
2、Butterknife的bind方法
在使用Butterknife时,我们需要用Butterknife的bind()方法绑定上下文。看看bind方法做了什么
@NonNull @UiThread
//可以看出必须在UI线程调用
public static Unbinder bind(@NonNull Activity target) {
//得到activity的decorview
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 {
//生成constructor实例
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);
}
}
可以看出调用了findBindingConstructorForClass,并返回了constructor.newInstance(target, source);
看看findBindingConstructorForClass方法
@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是什么
@VisibleForTesting
//class为key,Constructor为value
static final Map<Class>, Constructor extends Unbinder>> BINDINGS = new LinkedHashMap<>();
lass为key,Constructor为value的map
首先会从BINDINGS 中获取对应的Constructor,如果没有获取到,则会进行反射创建。这个类就是我们以前生成的MainActivity_ViewBinding,虽然反射会影响一些性能,但是因为有Binding是存在(一个类只会在第一次反射生成,以后会在Binders中去取)也可以解决一些性能问题。
反射调用后,会将结果存储到BINDINGS 中
3、生成的辅助类分析MainActivity_ViewBinding
ublic class MainActivity_ViewBinding<T extends MainActivity> implements Unbinder {
protected T target;
//target是activity,source是activity的Decorview
@UiThread
public MainActivity_ViewBinding(T target, View source) {
this.target = target;
//重点在这里
target.tv = Utils.findRequiredViewAsType(source, R.id.tv, "field 'tv'", TextView.class);
}
@Override
@CallSuper
//释放对象,避免内存泄露
public void unbind() {
T target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
target.tv = null;
this.target = null;
}
}
接下来看看Utils.findRequiredViewAsType方法
public static T findRequiredViewAsType(View source, @IdRes int id, String who,
Class cls) {
//找到view
View view = findRequiredView(source, id, who);
//对view进行类型转换
return castView(view, id, who, cls);
}
看看findRequiredView
public static View findRequiredView(View source, @IdRes int id, String who) {
//调用decorview的findviewByID方法,这大家比较熟悉了
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.");
}
接下来查看castView
public static T castView(View view, @IdRes int id, String who, Class cls) {
try {
//对view进行类型转换
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);
}
}
进行类型转换,转换成TextView 。这个返回的TextView会赋值给Target。也就是MainActivity,这样我们就能在MainActivity中使用这个Textview了。
至此,ButterKnife的@bindview基本分析完了。其他注解 的基本流程类同。
最后,总结一下:
ButterKnifeProcessor扫描java代码中的所有ButterKnife注解
ButterKnifeProcessor 根据扫描结果,给对应的activity生成ClassName$$_ViewBinder类
调用Bind方法加载 ViewBinder类,实现动态绑定
在下能力有限,如有错误还请更正~~~~