Android ButterKnife 源码分析
ButterKnife 依赖
implementation 'com.jakewharton:butterknife:10.2.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.1'
ButterKnife 使用
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv_test)
TextView tvTest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
tvTest.setText("test");
}
@OnClick(R.id.tv_test)
public void tvTestClick(){
}
}
源码解析 ButterKnife -> bind
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return bind(target, sourceView);
}
target.getWindow() 拿到的是 PhoneWindow 对象
sourceView 是 PhoneWindow 通过 getDecorView() 方法拿到顶层的 DecorView
bind() 将当前 Activity 和 DecorView 作为参数传递给 bind()
bind
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);
}
}
首先通过 Activity 拿到对应的 Class,其次通过 Class 拿到一个类型为 Unbinder 的 Constructor 构造器对象,最后通过 newInstance 反射得到 Unbinder 对象并返回。
findBindingConstructorForClass
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
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<?> 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 ( LinkedHashMap ) 通过 Class 获取对应的 Constructor 构造器对象,如果存在则直接返回
解下来会通过 cls.getName() 获取 Activity 的类名,然后通过反射查找 ClassName_ViewBinding 的类 ( 例如 MainActivity_ViewBinding ) ,并通过这个类找到 Constructor 构造器对象,BINDINGS 加入缓存,最后返回这个构造器对象
MainActivity_ViewBinding
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
private View view7f08018e;
@UiThread
public MainActivity_ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public MainActivity_ViewBinding(final MainActivity target, View source) {
this.target = target;
View view;
view = Utils.findRequiredView(source, R.id.tv_test, "field 'tvTest' and method 'tvTestClick'");
target.tvTest = Utils.castView(view, R.id.tv_test, "field 'tvTest'", TextView.class);
view7f08018e = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.tvTestClick();
}
});
}
@Override
@CallSuper
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.tvTest = null;
view7f08018e.setOnClickListener(null);
view7f08018e = null;
}
}
ButterKnife 中 bindingClass.getConstructor(cls, View.class) 对应的就是 MainActivity_ViewBinding 中两个参数的构造方法
@UiThread
public MainActivity_ViewBinding(final MainActivity target, View source) {
this.target = target;
View view;
view = Utils.findRequiredView(source, R.id.tv_test, "field 'tvTest' and method 'tvTestClick'");
target.tvTest = Utils.castView(view, R.id.tv_test, "field 'tvTest'", TextView.class);
view7f08018e = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.tvTestClick();
}
});
}
Utils.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.");
}
通过 findViewById 获取对应的 View
Utils.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);
throw new IllegalStateException("View '"
+ name
+ "' with ID "
+ id
+ " for "
+ who
+ " was of the wrong type. See cause for more info.", e);
}
}
将得到的 View 转换成指定的 View
public MainActivity_ViewBinding(final MainActivity target, View source) {
...
View view;
view = Utils.findRequiredView(source, R.id.tv_test, "field 'tvTest' and method 'tvTestClick'");
target.tvTest = Utils.castView(view, R.id.tv_test, "field 'tvTest'", TextView.class);
...
}
通过 findRequiredView 方法拿到 R.id.tv_test 的 View,然后通过 castView 将 View 转换成 TextVIew
MainActivity_ViewBinding 是如何创建的?首先我们需要了解 2 个知识点
- 注解( annotation )
- 注解处理器( annotationProcessor )
注解( annotation )
注解其实并不复杂,例如( @A「 A 洗脚店 」、@B「 B 洗脚店」与 @Target「使用范围:方法、变量等」、@Retention「生命周期:类、源文件等」)类似一个标签表明它的作用。给大家推荐一篇文章 注解( annotation )
注解处理器( annotationProcessor )
annotationProcessor 可以理解成一种处理注解的工具( 扫描检测 ),处理的核心就是 AbstractProcessor 。ButterKnife 中处注解的类为 ButterKnifeProcessor
如何查看 ButterKnifeProcessor 类 ?
- 在 gradle 中将 annotationProcessor 改为 implementation,即可在 External Libraries 中查看源码
- 在 GitHub 中查看源码 ButterKnifeProcessor
ButterKnifeProcessor
@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
...
@Override public synchronized void init(ProcessingEnvironment env) {
...
}
private Set<Class<? extends Annotation>> getSupportedAnnotations() {
...
}
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
...
}
...
}
ButterKnifeProcessor -> init
@Override public synchronized void init(ProcessingEnvironment env) {
super.init(env);
String sdk = env.getOptions().get(OPTION_SDK_INT);
if (sdk != null) {
try {
this.sdk = Integer.parseInt(sdk);
} catch (NumberFormatException e) {
env.getMessager()
.printMessage(Kind.WARNING, "Unable to parse supplied minSdk option '"
+ sdk
+ "'. Falling back to API 1 support.");
}
}
debuggable = !"false".equals(env.getOptions().get(OPTION_DEBUGGABLE));
typeUtils = env.getTypeUtils();
filer = env.getFiler();
try {
trees = Trees.instance(processingEnv);
} catch (IllegalArgumentException ignored) {
try {
// Get original ProcessingEnvironment from Gradle-wrapped one or KAPT-wrapped one.
for (Field field : processingEnv.getClass().getDeclaredFields()) {
if (field.getName().equals("delegate") || field.getName().equals("processingEnv")) {
field.setAccessible(true);
ProcessingEnvironment javacEnv = (ProcessingEnvironment) field.get(processingEnv);
trees = Trees.instance(javacEnv);
break;
}
}
} catch (Throwable ignored2) {
}
}
}
ProcessingEnvironment
public interface ProcessingEnvironment {
//选取重要的解读
...
Elements getElementUtils();
Types getTypeUtils();
Filer getFiler();
...
}
-
Elements getElementUtils 是处理 Element 的工具类,Element 可以理解成扫描到的源文件,源文件中独立的部分可以认作特立的 Element,如包( PackageElement )、类型( TypeElement )
-
Types getTypeUtils() 获取 TypeElement 的信息
-
Filer getFiler() 创建文件
init 方法主要用来初始化工具类
ButterKnifeProcessor -> getSupportedAnnotations
private Set<Class<? extends Annotation>> getSupportedAnnotations() {
Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
annotations.add(BindAnim.class);
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(BindFont.class);
annotations.add(BindInt.class);
annotations.add(BindString.class);
annotations.add(BindView.class);
annotations.add(BindViews.class);
annotations.addAll(LISTENERS);
return annotations;
}
getSupportedAnnotations 返回支持注解的类型
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 = 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 false;
}
process -> findAndParseTargets
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
...
// 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 处理了每个自定义的注解,虽然方法不一样,但是处理的逻辑是相同的,这里抽取 BindView 来分析
findAndParseTargets -> parseBindView(上部分)
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> erasedTargetNames) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// 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();
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;
}
...
}
findAndParseTargets -> parseBindView -> isInaccessibleViaGeneratedCode
private boolean isInaccessibleViaGeneratedCode(Class<? extends Annotation> annotationClass,
String targetThing, Element element) {
boolean hasError = false;
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// Verify field or method modifiers.
Set<Modifier> modifiers = element.getModifiers();
if (modifiers.contains(PRIVATE) || modifiers.contains(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) {
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)) {
error(enclosingElement, "@%s %s may not be contained in private classes. (%s.%s)",
annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
return hasError;
}
验证当前类或者方法是否是 PRIVATE|STATIC,验证父类是否是 Class 且是否是 PRIVATE
findAndParseTargets -> parseBindView -> isBindingInWrongPackage
private boolean isBindingInWrongPackage(Class<? extends Annotation> annotationClass,
Element element) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
String qualifiedName = enclosingElement.getQualifiedName().toString();
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;
}
验证当前方法是否以「 android. 」|「 java. 」开头
以上很多验证信息都是验证 Element 是否继承自 View,如果不是继承自View 就不会走后面的流程
findAndParseTargets -> parseBindView(下部分)
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> erasedTargetNames) {
...
// 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);
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));
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
}
element.getAnnotation(BindView.class).value() 通过@BindView 注解获取到对应 View 的 id。通过 builderMap.get(enclosingElement) 根据我们对应的元素查找 Builder。如果存在,就会判断有没有重复的引用;如果不存在,就会去创建一个新的 Builder。最后通过 builder.addField() 添加到集合中
findAndParseTargets -> parseBindView -> 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;
}
getOrCreateBindingBuilder -> newBuilder
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;
}
ClassName bindingClassName = getBindingClassName(enclosingElement);
boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
return new Builder(targetType, bindingClassName, enclosingElement, isFinal, isView, isActivity,
isDialog);
}
newBuilder -> getBindingClassName
static ClassName getBindingClassName(TypeElement typeElement) {
String packageName = getPackage(typeElement).getQualifiedName().toString();
String className = typeElement.getQualifiedName().toString().substring(
packageName.length() + 1).replace('.', '$');
return ClassName.get(packageName, className + "_ViewBinding");
}
BindingSet 就确定了生成 MainActivity_ViewBinding 类的信息
findAndParseTargets -> parseBindView -> addField
void addField(Id id, FieldViewBinding binding) {
getOrCreateViewBindings(id).setFieldBinding(binding);
}
addField -> getOrCreateViewBindings
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;
}
最终将 id 与 view 进行绑定,加入集合,findAndParseTargets 的流程就结束了
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 = 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 false;
}
process -> brewJava
JavaFile brewJava(int sdk, boolean debuggable) {
TypeSpec bindingConfiguration = createType(sdk, debuggable);
return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
brewJava -> createType
private TypeSpec createType(int sdk, boolean debuggable) {
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(PUBLIC)
.addOriginatingElement(enclosingElement);
if (isFinal) {
result.addModifiers(FINAL);
}
if (parentBinding != null) {
result.superclass(parentBinding.getBindingClassName());
} else {
result.addSuperinterface(UNBINDER);
}
if (hasTargetField()) {
result.addField(targetTypeName, "target", PRIVATE);
}
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, debuggable));
if (hasViewBindings() || parentBinding == null) {
result.addMethod(createBindingUnbindMethod(result));
}
return result.build();
}
if (parentBinding != null) {
result.superclass(parentBinding.getBindingClassName());
} else {
result.addSuperinterface(UNBINDER);
}
这段代码的意思就是如果存在父类就继承父类,如果不存在就实现 Unbinder 接口
public class MainActivity_ViewBinding implements Unbinder {
}
上面的方法大致生成这段 Java 代码,下面重点看 createBindingConstructor() 创建绑定的构造器方法
createBindingConstructor
private MethodSpec createBindingConstructor(int sdk, boolean debuggable) {
...
for (ViewBinding binding : viewBindings) {
addViewBinding(constructor, binding, debuggable);
}
...
}
addViewBinding
private void addViewBinding(MethodSpec.Builder result, ViewBinding binding, boolean debuggable) {
...
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 {
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(")");
}
result.addStatement("$L", builder.build());
return;
}
...
}
static boolean requiresCast(TypeName type) {
return !VIEW_TYPE.equals(type.toString());
}
测试代码中创建的为 TextView,所以 requiresCast 会返回 true,那么会执行 else 部分的代码
- builder.add("$T.find", UTILS) -> UTILS.find
- builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView") ->
RequiredView - builder.add("(source, $L", binding.getId().code) -> (source,R.id.tv_test
- builder.add(", $S", asHumanDescription(singletonList(fieldBinding))) -> , Description
- builder.add(")") -> )
完整的字符串就是 "UTILS.find" + "RequiredView" + " (source,R.id.tv_test" +", Description" +")" -> Utils.findRequiredView(source, R.id.tv_test, "Description"),MainActivity_ViewBinding 对应的就是 Utils.findRequiredView(source, R.id.tv_test, "field 'tvTest' and method 'tvTestClick'");
process 流程就分析到这里
参考文献
总结
- ButterKnife 通过注解处理器( annotationProcessor ),经过 ButterKnifeProcessor
- init 初始工具类
- getSupportedAnnotations 获取支持的注解列表
- process 扫描处理所有支持的注解并加入集合,便利集合生成 java 代码,并将 java 代码生成对应的 class 文件
- 在 Activity 中使用 ButterKnife.bind(this) 方法,加载对应的 _ViewBinding 类,达道省去写 findViewById 的目的。