ButterKnife原理
ButterKnife自定义了很多我们常用的注解,比如@BindView和@OnClick。现在先来看@BindView的源码,如下所示:
@Retention(RUNTIME) @Target(FIELD)
public @interface BindView {
/** View ID to which the field will be bound. */
@IdRes int value();
}
Retention(CLASS)表明@BindView 注解是编译时注解,@Target(FIELD)则表明@BindView注解应用于成员变量。
接下来使用@BindView注解来绑定TextView 控件,后文会用到这些代码,如下所示:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv_text)
TextView tvText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
}
ButterKnifeProcessor源码分析
要处理注解需要注解处理器,ButterKnife的注解处理器是ButterKnifeProcessor,它的主要处理逻辑都在process方法中,源码如下所示:
@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
......
@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
// findAndParseTargets方法主要用于 查找和解析注解。
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
// 遍历findAndParseTargets方法返回的Map集合
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
// 得到BindingSet的值,并调用了它的brewJava方法
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 true;
}
......
}
ButterKnifeProcessor继承自AbstractProcessor,我们再来看看findAndParseTargets方法:
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
......
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
try {
parseBindView(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindView.class, e);
}
}
......
return bindingMap;
}
findAndParseTargets 方法会查找所有 ButterKnife 的注解来进行解析,这里只看处理@BindView注解的部分。然后查看parseBindView方法,如下所示
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> erasedTargetNames) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
/**
* isInaccessibleViaGeneratedCode方法里面检查了3个点,它们分别是:
* 1、方法修饰符不能为private和static;
* 2、包含类型不能为非Class;
* 3、包含类的修饰符不能是private。
*/
*/
boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
|| isBindingInWrongPackage(BindView.class, element);
......
if (hasError) {
return;
}
// 获取注解的标注的值。
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);
// 将注解修饰的类型的信息存储在 FieldViewBinding中,并将FieldViewBinding传入 BindingSet.Builder的addField方法中。
builder.addField(resourceId, new FieldViewBinding(name, type, required));
erasedTargetNames.add(enclosingElement);
}
可以看出,该方法主要用于检查类型和修饰符以及包名等,然后将注解修饰的类型的信息进行存储。
接下来我们回到process方法,brewJava方法将使用注解的类生成一个JavaFile,然后将该JavaFile输出成Java文件。(javaFile.writeTo(filer))
在build-generared-source-apt目录下可以找到生成的Java文件,这里生成的文件名为MainActivity_ViewBinding。
ButterKnife的bind方法
为了使用ButterKnife,我们需要用ButterKnife.bind()方法来绑定上下文。现在先来查看bind方法:
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return bind(target, sourceView);
}
bind 方法有很多重载方法,上面的代码只是其中的一种,也就是传入 Activity的情况。得到Activity的DecorView,并将DecorView和Activity传入bind方法中:
@NonNull @UiThread
public static Unbinder bind(@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;
}
try {
// MainActivity_ViewBinding实例,并将Activity和其DecorView作为参数传入构造函数
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方法获取Activity构造函数,方法如下所示:
@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
// 先从BINDINGS中获取对应Class的Constructor实例,BINDINGS是一个Class为key、Constructor为value的Map。
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null || BINDINGS.containsKey(cls)) {
if (debug) Log.d(TAG, "HIT: Cached in binding map.");
return bindingCtor;
}
String clsName = cls.getName();
if (clsName.startsWith("android.") || clsName.startsWith("java.")
|| clsName.startsWith("androidx.")) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return null;
}
try {
// 通过反射来生成Class类,这个Class类就是我们此前生成的MainActivity_ViewBinding。
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
// 调用getConstructor方法将Class转换为Constructor,
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);
}
// 处将Constructor作为value,Class作为key存储在BINDINGS中,最后返回该Constructor。
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
代码注释中使用到了反射的方式来创建MainActivity_ViewBinding,反射会影响性能,但是因为有 BINDERS的存在(一个类只会在第一次反射生成,以后会从BINDERS中去取),也可以解决一些性能问题。
生成的辅助类分析
MainActivity_ViewBinding.java,在appuildgeneratedap_generated_sourcesdebugoutcomlegendutterknife文件夹下,代码如下所示:
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
// source值就是MainActivity的 DecorView,而target值为MainActivity
@UiThread
public MainActivity_ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public MainActivity_ViewBinding(MainActivity target, View source) {
this.target = target;
// 调用了Utils的findRequiredViewAsType方法并将source值、R.id.tv_text等参数传入,findRequiredViewAsType方法
target.tvText = Utils.findRequiredViewAsType(source, R.id.tv_text, "field 'tvText'", TextView.class);
}
@Override
@CallSuper
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.tvText = null;
}
}
在前面我们已经知道在 bind方法中调用 newInstance 方法生成 MainActivity_ViewBinding实例时传入的source值是MainActivity的DecorView。而target值为MainActivity。
注释处,findRequiredViewAsType传入source的值和R.id.tv_text等参数,让我们看看该方法:
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;
}
......
}
findRequiredView方法调用了DecorView的findViewById方法并将R.id.tv_text对应的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);
......
}
}
castView方法会将View强制转换为TextView并返回。再次回到MainActivity_ViewBinding辅助类,这个返回的TextView会赋值给target,也就是MainActivity,这样我们在MainActivity中就可以使用这个TextView。
总结
ButterKnifeProcessor主要用于查找所有的注解,并生成相应的xxxx_ViewBingding类,然后在ButterKnife的bind方法来通过反射创建xxx_ViewBinding类的同时,将Activity的引用和DecorView的引用通过构造函数的方式传入,通过DecorView的findViewById方法拿到View的实例,然后在castView方法中进行强转并返回。