用过ButterKnife源码的筒子们都知道,它的注解方式很吊,相比大家肯定想知道里面到底用了啥技术,能几行代码搞定这么多的玩意。
一般会有这样的代码:
public class LoginActivity extends AppCompatActivity {
@BindView(R.id.input_call)
public EditText inputCall;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
ButterKnife.bind(this);//框架的入口
inputCall.setText("123");
}
}
大家可以看到上面有BindView
的注解,然后下面调用了ButterKnife.bind(this);
,把当前的Activity给传进去了:
/**
* 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 bind(target, sourceView);
}
上面的方法把activity中顶层的DecorView
传到了bind
方法:
/**
* BindView annotated fields and methods in the specified {@code target} using the {@code source}
* {@link View} as the view root.
*
* @param target Target class for view binding.
* @param source View root on which IDs will be looked up.
*/
@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;
}
//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);
}
}
这里调用了findBindingConstructorForClass
方法后,最后调用了Unbinder
两个参数的构造函数,并且返回来了。
@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
//首先去判断BINDINGS中有没有该实例,有的话直接返回
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<?> 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中,方便下次使用
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
上面做的事情获到了*** _ViewBinding
类class字节码,然后获取到它的Constructor
实例,最后放到了BINDINGS
的map中,方便下次使用。那么此处的*** _ViewBinding
就是通过编译时期生成的LoginActivity_ViewBinding
类,关于如何生成LoginActivity_ViewBinding
类还得后面介绍APT(注解处理器)+javaPoet(自动生成代码)相关的代码:
public class LoginActivity_ViewBinding implements Unbinder {
private LoginActivity target;
@UiThread
public LoginActivity_ViewBinding(LoginActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public LoginActivity_ViewBinding(LoginActivity target, View source) {
this.target = target;
target.inputCall = Utils.findRequiredViewAsType(source, R.id.input_call, "field 'inputCall'", EditText.class);
}
@Override
@CallSuper
public void unbind() {
LoginActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.inputCall = null;
}
}
最终都会走第二个构造器的代码,看看findRequiredViewAsType
方法:
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 <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);
throw new IllegalStateException("View '"
+ name
+ "' with ID "
+ id
+ " for "
+ who
+ " was of the wrong type. See cause for more info.", e);
}
}
紧接着调用了findRequiredView
方法,然后强转了下:
public static View findRequiredView(View source, @IdRes int id, String who) {
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.");
}
最终也是通过view.findViewById找到该view,下面来看看LoginActivity_ViewBinding
如何在编译阶段能自动生成呢,编译阶段的话,需要去compile包下面的ButterKnifeProcessor
类里面去看,这里可以直接到该类的process
方法:
@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 javaFile = binding.brewJava(sdk, debuggable, useLegacyTypes);
try {
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return false;
}
上面方法先是通过findAndParseTargets
方法找到所有的呆生成的类,放到map中,然后在通过JavaFile
去生成类。
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
//省略了好多代码,这里只说BindView注解
// 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);
}
}
return bindingMap;
}
看到了没,该方法是找所有注解的,然后依次处理注解,这里调用了parseBindView
方法:
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> erasedTargetNames) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
//省去了一些判断错误的情况
// Assemble information on the field.
//获取到注解的id
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);
//最终以resourceId为key,然后以FieldViewBinding为value放到了builder中
builder.addField(resourceId, new FieldViewBinding(name, type, required));
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
}
上面的方法中,走的是getOrCreateBindingBuilder
方法:
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;
}
上面调用了newBuilder
方法,该方法是组成builder
对象的重要方法:
static Builder newBuilder(TypeElement enclosingElement) {
TypeMirror typeMirror = enclosingElement.asType();
boolean isView = isSubtypeOfType(typeMirror, VIEW_TYPE);
boolean isActivity = isSubtypeOfType(typeMirror, ACTIVITY_TYPE);
boolean isDialog = isSubtypeOfType(typeMirror, DIALOG_TYPE);
TypeName targetType = TypeName.get(typeMirror);
if (targetType instanceof ParameterizedTypeName) {
targetType = ((ParameterizedTypeName) targetType).rawType;
}
String packageName = getPackage(enclosingElement).getQualifiedName().toString();
String className = enclosingElement.getQualifiedName().toString().substring(
packageName.length() + 1).replace('.', '$');
//看到了没,这里就是生成类名的地方
ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");
boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog);
}
上面的代码挺简单的哈,生成类名,以及一些参数传递给builder。那么再回到parseBindView
方法的最后面,会看到最后有builder.addField方法,resourceId
作为key,value组合成一个FieldViewBinding
对象。现在咋们还需要回到process
方法。看到
JavaFile javaFile = binding.brewJava(sdk, debuggable, useLegacyTypes);
这句了没,这里就是去生成类的地方,直接找到BindingSet
的brewJava
方法:
JavaFile brewJava(int sdk, boolean debuggable, boolean useLegacyTypes) {
TypeSpec bindingConfiguration = createType(sdk, debuggable, useLegacyTypes);
return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
此处看到了调用了createType
方法,这个方法很关键,生成类和方法的地方:
private TypeSpec createType(int sdk, boolean debuggable, boolean useLegacyTypes) {
//生成类的地方
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(PUBLIC);
if (isFinal) {
result.addModifiers(FINAL);
}
if (parentBinding != null) {
result.superclass(parentBinding.bindingClassName);
} else {
result.addSuperinterface(UNBINDER);
}
if (hasTargetField()) {
result.addField(targetTypeName, "target", PRIVATE);
}
if (isView) {
result.addMethod(createBindingConstructorForView(useLegacyTypes));
} else if (isActivity) {
//看到了没,生成activity默认构造器的地方
result.addMethod(createBindingConstructorForActivity(useLegacyTypes));
} else if (isDialog) {
result.addMethod(createBindingConstructorForDialog(useLegacyTypes));
}
if (!constructorNeedsView()) {
// Add a delegating constructor with a target type + view signature for reflective use.
result.addMethod(createBindingViewDelegateConstructor(useLegacyTypes));
}
//最终会走统一的构造器的地方
result.addMethod(createBindingConstructor(sdk, debuggable, useLegacyTypes));
//生成unbind方法的地方
if (hasViewBindings() || parentBinding == null) {
result.addMethod(createBindingUnbindMethod(result, useLegacyTypes));
}
return result.build();
}
上面注释都写得很清楚,咋们主要看下统一生成构造器的地方:
private MethodSpec createBindingConstructor(int sdk, boolean debuggable, boolean useLegacyTypes) {
//省略代码
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, debuggable, useLegacyTypes);
}
//省略代码
return constructor.build();
}
下面看下addViewBinding
是如何调用的:
private void addViewBinding(MethodSpec.Builder result, ViewBinding binding, boolean debuggable,
boolean useLegacyTypes) {
if (binding.isSingleFieldBinding()) {
// Optimize the common case where there's a single binding directly to a field.
FieldViewBinding fieldBinding = requireNonNull(binding.getFieldBinding());
CodeBlock.Builder builder = CodeBlock.builder()
.add("target.$L = ", fieldBinding.getName());
boolean requiresCast = requiresCast(fieldBinding.getType());
if (!debuggable || (!requiresCast && !fieldBinding.isRequired())) {
if (requiresCast) {
builder.add("($T) ", fieldBinding.getType());
}
builder.add("source.findViewById($L)", binding.getId().code);
} else {
//这里是关键的地方,调用utils的方法,这里不太懂,为啥是调用utils的find的方法呢
builder.add("$T.find", UTILS);
builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView");
if (requiresCast) {
builder.add("AsType");
}
//代替第二个参数的地方,用id来替换
builder.add("(source, $L", binding.getId().code);
if (fieldBinding.isRequired() || requiresCast) {
builder.add(", $S", asHumanDescription(singletonList(fieldBinding)));
}
if (requiresCast) {
//传入类型,也就是我们的view类型的class类型
builder.add(", $T.class", fieldBinding.getRawType());
}
builder.add(")");
}
result.addStatement("$L", builder.build());
return;
}
List<MemberViewBinding> requiredBindings = binding.getRequiredBindings();
if (!debuggable || 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));
}
addFieldBinding(result, binding, debuggable);
addMethodBindings(result, binding, debuggable, useLegacyTypes);
}
看到了没,这里使用$S
来替换咋们需要的值,调用utils的方法这里是find方法。至此,再回到我们的 ButterKnifeProcessor
的process
方法,最后调用了javaFile.writeTo(filer);
方法,将LoginActivity_ViewBinding
生成成功,源码也就分析到这了。
分析不到位的地方,还请多指明,谢谢!!!
网友评论