四、ButterKnife 源码分析:
1、代码结构
核心代码:
butterknife 提供一些api
butterknife-annotations 提供一些自定义注解
butterknife-compiler 注解解析器
2、BindView 注解
@Retention(CLASS)
@Target(FIELD)
public @interface BindView {
/** View ID to which the field will be bound. */
@IdRes int value();
}
复制代码
@Retention(CLASS)说明是编译时注解。@Target(FIELD)说明注解作用于成员变量。 如何将注解解析生成Java文件呢?
3、注解解析器 ButterKnifeProcessor:
@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
//(1)查找所有的注解并进行解析
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
//(2)进行遍历,获取注解的值
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
//(3)将注解的类生成一个JavaFile,并输出为一个Java文件
JavaFile javaFile = binding.brewJava(sdk, debuggable, useAndroidX);
try {
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return false;
}
}
复制代码
(1)findAndParseTargets:
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
......
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
processing rounds
try {
parseBindView(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindView.class, e);
}
}
}
复制代码
内部调用了parseBindView方法
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> erasedTargetNames) {
......
//获取注解标注的值
int id = element.getAnnotation(BindView.class).value();
BindingSet.Builder builder = builderMap.get(enclosingElement);
Id resourceId = elementToId(element, BindView.class, id);
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);
//将值存在到field上
builder.addField(resourceId, new FieldViewBinding(name, type, required));
}
复制代码
解析注解的过程:将注解进行解析,获取到注解的值,然后将注解的类生成一个Java文件。Java文件在哪?
4、编译后生成文件: 通过注解解析器将注解解析生成java 文件
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
@UiThread
public MainActivity_ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public MainActivity_ViewBinding(MainActivity target, View source) {
this.target = target;
target.mTextView = Utils.findRequiredViewAsType(source, R.id.tv_title, "field 'mTextView'", TextView.class);
}
@Override
@CallSuper
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.mTextView = null;
}
}
复制代码
(1)查找id为tv_title的TextView:
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);
}
复制代码
public static View findRequiredView(View source, @IdRes int id, String who) {
View view = source.findViewById(id);
if (view != null) {
return view;
}
...
}
复制代码
最终还是通过source.findViewById 去查找view。传入的source的View当前的Activity的DecorView。
5、ButterKnife.bind(this):
ButterKnife的bind方法:
@NonNull @UiThread
public static Unbinder bind(@NonNull Dialog target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
复制代码
获取到了当前Activit的DecorView,然后调用了createBinding方法。
private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
......
//去获取constructor 对象
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
//通过反射区实例化constructor对象
try {
return constructor.newInstance(target, source);
} catch (IllegalAccessException e) {
......
}
复制代码
findBindingConstructorForClass方法:
@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
//(1)BINDINGS是一个LinkedHashMap的集合,先在集合取cls对应的Constructor实例
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
//(2)取到了就直接返回
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 {
//(3)获取不到就通过反射就创建,就是刚刚的MainActivity_ViewBinding的辅助类
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);
}
//将该Constructor的实例作为value,存入集合中
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
复制代码
总结:在MainActivity中通过BindView对TextView进行注解
(1)在编译时期通过注解解析器ButterKnifeProcessor对注解进行解析,获取到注解标注的值,然后生成一个MainActivity_ViewBinding的类。
(2)通过调用ButterKnife.bind(this)来绑定上下文。会从一个LinkedHashMap的集合中取对应MainActivity的Constructor对象,如果取不到就通过反射创建,然后存到集合中。
(3)同时通过反射创建了MainActivity_ViewBinding的对象,此时传入了MainActivity上下文对象,在MainActivity_ViewBinding中可以通过DecorView 去获取被注解的TextView。 |