序言
继上次介绍了EventBus之后,今天我们来介绍另外一个好用的三方库ButterKnife,一个依赖注入框架。该库的作者是被称为Android之神的JakeWarton,我们所熟知的OKHttp、Retrofit、ButterKnife、RxAndroid、RxBinding都是他的杰作。
在使用ButterKnife之前,我们写过最多的代码大概是:
TextView tv = (TextView) findViewById(R.id.tv);
而有了ButterKnife之后,我们就可以告别findViewById(),ButterKnife会自动帮我们生成这些代码。接下来,抱着膜拜大神的态度,让我们一起学习ButterKnife。
ButterKnife的使用
ButterKnife的使用流程如下:
- 在工程目录下的build.gradle文件中添加ButterKnife插件路径
classpath "com.jakewharton:butterknife-gradle-plugin:8.6.0"
- 在module的build.gradle文件中添加依赖和注解解析器:
api 'com.jakewharton:butterknife:8.8.1'
kapt 'com.jakewharton:butterknife-compiler:8.6.0'
- 接下来在Activity或者Fragment的onCreate/onCreateView方法中绑定当前对象:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Unbinder binder = ButterKnife.bind(this);
}
- 然后在需要注入的View上加注解,此处以BindView注解为例:
@BindView(R.id.rbStock)
RadioButton rbStock;
//省略其他控件
- 现在,就可以在代码中使用这些控件,而不需要通过findViewById初始化这些控件。
- 最后,在Activity的onDestroy方法中从ButterKnife解绑:
@Override
protected void onDestroy() {
super.onDestroy();
binder.unbind();
}
到这里,ButterKnife的使用方法介绍完了,可以看到使用过程比较简单。
源码分析
1. ButterKnife绑定Activity/Fragment
我们从ButterKnife绑定Activity入手,来分析原理。
这里,我们用ButterKnife绑定项目的MainActivity:
ButterKnife.bind(this);
跟踪ButterKnife的bind方法:
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
这里会获取Activity对应Window的DecorView,然后调用createBinding方法:
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 {
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方法:
@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;
}
该方法接收一个Class对象,即我们的MainActivity对应的Class对象。首先会从BINDINGS中寻找是否存在构造器,BINDINGS定义如下:
@VisibleForTesting
static final Map, Constructor extends Unbinder>> BINDINGS = new LinkedHashMap<>();
它是一个Map,键是一个Class对象,值是一个构造器,至于是什么的构造器下面会揭晓答案。
继续上面的分析,如果在BINDINGS中找到构造器,则直接返回构造器。如果没有找到,就校验这个Class对象是否合法,如果合法,就执行下面一句代码:
Class> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
加载一个名为MainActivity_ViewBinding
的类,并且获取它的构造器,并保存在BINDINGS中,下次再bind这个类的时候,就直接从BINDINGS中获取构造器,提升性能。
而MainActivity_ViewBinding是通过APT(编译时解析技术)生成的类。后面会介绍具体的生成过程。
再回到createBinding方法,获取到MainActivity_ViewBinding的构造器之后,会调用构造器的newInstance方法构建一个MainActivity_ViewBinding对象。我们看一下MainActivity_ViewBinding的定义:
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.rbStock = Utils.findRequiredViewAsType(source, R.id.rbStock, "field 'rbStock'", RadioButton.class);
//省略其他控件初始化代码
}
@Override
@CallSuper
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.rbStock = null;
//省略重复其他控置空代码
}
}
这个类的构造方法中初始化了带@BindView注解的控件,我们看一下具体初始化过程:
public static T findRequiredViewAsType(View source, @IdRes int id, String who,
Class 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;
}
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.");
}
在findRequiredView方法中,我们看见了熟悉的findViewById方法,到这里相信大家都清楚了:ButterKnife的原理是通过APT技术在编译过程生成了一个XX_ViewBinding类,之后在绑定的时候,会创建XX_ViewBinding对象,而XX_ViewBinding的构造方法中会帮我们出初始化这些注解修饰的控件。
MainActivity_ViewBinding实现了Unbinder接口,我们看一下接口定义:
public interface Unbinder {
@UiThread void unbind();
Unbinder EMPTY = new Unbinder() {
@Override public void unbind() { }
};
}
很简单,只有一个unbind方法,而MainActivity_ViewBinding在unbind方法中把注解对应的控件和target都置为null,释放资源。就是使用过程第6步在onDestroy中会调用该方法。
2. 编译时生成_ViewBinding类
接下来,我们看一下怎样通过APT技术生成MainActivity_ViewBinding类。了解APT的同学会知道,主要是通过AbstractProcessor类在编译过程中处理注解。
下面,我们就看一下ButterKnife对应注解处理类ButterKnifeProcessor
:
@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
private static final String OPTION_SDK_INT = "butterknife.minSdk";
static final Id NO_ID = new Id(-1);
static final String VIEW_TYPE = "android.view.View";
static final String ACTIVITY_TYPE = "android.app.Activity";
static final String DIALOG_TYPE = "android.app.Dialog";
private static final String COLOR_STATE_LIST_TYPE = "android.content.res.ColorStateList";
private static final String BITMAP_TYPE = "android.graphics.Bitmap";
private static final String DRAWABLE_TYPE = "android.graphics.drawable.Drawable";
private static final String TYPED_ARRAY_TYPE = "android.content.res.TypedArray";
private static final String NULLABLE_ANNOTATION_NAME = "Nullable";
private static final String STRING_TYPE = "java.lang.String";
private static final String LIST_TYPE = List.class.getCanonicalName();
private static final List> LISTENERS = Arrays.asList(OnCheckedChanged.class, OnClick.class, OnEditorAction.class, OnFocusChange.class, OnItemClick.class, OnItemLongClick.class, OnItemSelected.class, OnLongClick.class, OnPageChange.class, OnTextChanged.class, OnTouch.class);
private static final List SUPPORTED_TYPES = Arrays.asList("array", "attr", "bool", "color", "dimen", "drawable", "id", "integer", "string");
private Elements elementUtils;
private Types typeUtils;
private Filer filer;
private Trees trees;
private int sdk = 1;
private final Map symbols = new LinkedHashMap();
public ButterKnifeProcessor() {
}
//初始化一些参数和工具类
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
String sdk = (String)env.getOptions().get("butterknife.minSdk");
if (sdk != null) {
try {
this.sdk = Integer.parseInt(sdk);
} catch (NumberFormatException var5) {
env.getMessager().printMessage(Kind.WARNING, "Unable to parse supplied minSdk option '" + sdk + "'. Falling back to API 1 support.");
}
}
this.elementUtils = env.getElementUtils();
this.typeUtils = env.getTypeUtils();
this.filer = env.getFiler();
try {
this.trees = Trees.instance(this.processingEnv);
} catch (IllegalArgumentException var4) {
;
}
}
//获取该Processor能处理的注解集合
private Set> getSupportedAnnotations() {
Set> annotations = new LinkedHashSet();
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(BindInt.class);
annotations.add(BindString.class);
annotations.add(BindView.class);
annotations.add(BindViews.class);
annotations.addAll(LISTENERS);
return annotations;
}
//主要的处理方法,分析将从这里开始
public boolean process(Set extends TypeElement> elements, RoundEnvironment env) {
Map bindingMap = this.findAndParseTargets(env);
Iterator var4 = bindingMap.entrySet().iterator();
while(var4.hasNext()) {
Entry entry = (Entry)var4.next();
TypeElement typeElement = (TypeElement)entry.getKey();
BindingSet binding = (BindingSet)entry.getValue();
JavaFile javaFile = binding.brewJava(this.sdk);
try {
javaFile.writeTo(this.filer);
} catch (IOException var10) {
this.error(typeElement, "Unable to write binding for type %s: %s", typeElement, var10.getMessage());
}
}
return false;
}
//省略很多方法
}
我们就直接从process方法开始分析,这个方法的逻辑比较简单,通过findAndParseTargets方法找到所有使用ButterKnife的类,然后根据注解信息生成对应的_ViewBinding类。
可以把这段代码分为两个过程:处理注解并记录信息与生成_ViewBinding类。
a. 处理注解并记录信息
先看一下findAndParseTargets方法:
private Map findAndParseTargets(RoundEnvironment env) {
Map builderMap = new LinkedHashMap();
Set erasedTargetNames = new LinkedHashSet();
this.scanForRClasses(env);
//省略处理其他注解的代码
var4 = env.getElementsAnnotatedWith(BindView.class).iterator();
while(var4.hasNext()) {
element = (Element)var4.next();
try {
this.parseBindView(element, builderMap, erasedTargetNames);
} catch (Exception var12) {
this.logParsingError(element, BindView.class, var12);
}
}
//省略处理其他注解的代码
var4 = LISTENERS.iterator();
while(var4.hasNext()) {
Class extends Annotation> listener = (Class)var4.next();
this.findAndParseListener(env, listener, builderMap, erasedTargetNames);
}
Deque> entries = new ArrayDeque(builderMap.entrySet());
LinkedHashMap bindingMap = new LinkedHashMap();
while(!entries.isEmpty()) {
Entry entry = (Entry)entries.removeFirst();
TypeElement type = (TypeElement)entry.getKey();
Builder builder = (Builder)entry.getValue();
TypeElement parentType = this.findParentType(type, erasedTargetNames);
if (parentType == null) {
bindingMap.put(type, builder.build());
} else {
BindingSet parentBinding = (BindingSet)bindingMap.get(parentType);
if (parentBinding != null) {
builder.setParent(parentBinding);
bindingMap.put(type, builder.build());
} else {
entries.addLast(entry);
}
}
}
return bindingMap;
}
这个方法很长,主要的作用就是找到项目中所有使用ButterKnife注解的类,分别处理他们,最后把他们的信息保存在一个Map中:
LinkedHashMap bindingMap = new LinkedHashMap();
这个Map以ElementType(使用注解的类)为key,值则是一个BindingSet对象(记录了类中用到的注解及作用对象等信息)。后面就是通过这个Map生成对应的_ViewBinding类。
接下来,我们分析一下注解BindView的处理过程,主要是通过parseBindView方法处理:
private void parseBindView(Element element, Map builderMap, Set erasedTargetNames) {
TypeElement enclosingElement = (TypeElement)element.getEnclosingElement();
//检查是否满足使用注解的条件
boolean hasError = this.isInaccessibleViaGeneratedCode(BindView.class, "fields", element) || this.isBindingInWrongPackage(BindView.class, element);
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, "android.view.View") && !this.isInterface(elementType)) {
if (elementType.getKind() == TypeKind.ERROR) {
this.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 {
this.error(element, "@%s fields must extend from View or be an interface. (%s.%s)", BindView.class.getSimpleName(), qualifiedName, simpleName);
hasError = true;
}
}
//满足条件,开始解析注解
if (!hasError) {
int id = ((BindView)element.getAnnotation(BindView.class)).value();
//从缓存builderMap中查找注解使用类对应的builder对象
Builder builder = (Builder)builderMap.get(enclosingElement);
QualifiedId qualifiedId = this.elementToQualifiedId(element, id);
String existingBindingName;
if (builder != null) {
existingBindingName = builder.findExistingBindingName(this.getId(qualifiedId));
if (existingBindingName != null) {
this.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对象,则创建这个对象,并且保存在builderMap中
builder = this.getOrCreateBindingBuilder(builderMap, enclosingElement);
}
existingBindingName = simpleName.toString();
TypeName type = TypeName.get(elementType);
boolean required = isFieldRequired(element);
//把注解BindView修饰的字段添加到builder中
builder.addField(this.getId(qualifiedId), new FieldViewBinding(existingBindingName, type, required));
erasedTargetNames.add(enclosingElement);
}
}
代码中关键的地方给出了注释,各位同学可以根据注释自行理解代码。其他注解的处理方式跟注解BindView类似,这里不做介绍。就这样,把类和它对应的注解信息都保存到Map中。
b. 生成_ViewBinding类
我们再来看一下process方法:
public boolean process(Set extends TypeElement> elements, RoundEnvironment env) {
Map bindingMap = this.findAndParseTargets(env);
Iterator var4 = bindingMap.entrySet().iterator();
while(var4.hasNext()) {
Entry entry = (Entry)var4.next();
TypeElement typeElement = (TypeElement)entry.getKey();
BindingSet binding = (BindingSet)entry.getValue();
JavaFile javaFile = binding.brewJava(this.sdk);
try {
javaFile.writeTo(this.filer);
} catch (IOException var10) {
this.error(typeElement, "Unable to write binding for type %s: %s", typeElement, var10.getMessage());
}
}
return false;
}
通过第一步得到了bindingMap对象,接下来遍历bindingMap,通过BindingSet的brewJava方法生成对应的ViewBinding类:
JavaFile brewJava(int sdk) {
return JavaFile.builder(this.bindingClassName.packageName(), this.createType(sdk)).addFileComment("Generated code from Butter Knife. Do not modify!", new Object[0]).build();
}
static BindingSet.Builder newBuilder(TypeElement enclosingElement) {
TypeMirror typeMirror = enclosingElement.asType();
boolean isView = ButterKnifeProcessor.isSubtypeOfType(typeMirror, "android.view.View");
boolean isActivity = ButterKnifeProcessor.isSubtypeOfType(typeMirror, "android.app.Activity");
boolean isDialog = ButterKnifeProcessor.isSubtypeOfType(typeMirror, "android.app.Dialog");
TypeName targetType = TypeName.get(typeMirror);
if (targetType instanceof ParameterizedTypeName) {
targetType = ((ParameterizedTypeName)targetType).rawType;
}
String packageName = MoreElements.getPackage(enclosingElement).getQualifiedName().toString();
String className = enclosingElement.getQualifiedName().toString().substring(packageName.length() + 1).replace('.', '$');
ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding", new String[0]);
boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
return new BindingSet.Builder((TypeName)targetType, bindingClassName, isFinal, isView, isActivity, isDialog);
}
这里就不深入介绍生成ViewBinding类的过程,它使用到了javapoet技术,javapoet技术是square公司开源的,专门用来生成java源码,很多使用APT技术的库都是使用到javapoet技术。
总结
到这里,ButterKnife的原理我们就介绍完了,各位同学按照本文思路,对着ButterKnife源码进行学习。
如果各位同学使用过kotlin,就知道kotlin中不需要ButterKnife,kotlin提供了插件,可以直接获取xml中的控件,还提供了一个功能强大又易用的库anko,有兴趣的小伙伴可以了解一下。