前言
Jake Wharton大神的Butterknife可谓是造福广大Android开发者, 再也不用重复写findViewById和setOnClickListener了.但是用了这么久的Butterknife, 一直以为用的是反射, 直到看了源码...(路漫漫其修远兮,吾将上下而求索)
Butterknife的使用
Git地址: https://github.com/JakeWharton/butterknife, 里面有具体的引用及使用说明, 这里不再介绍.
Butterknife的原理
谈到Butterknife的原理, 不得不先提一下运行时注解和编译时注解的区别:
- 编译时注解: (代码生成)在编译时扫描所有注解,对注解进行处理,在项目中生成一份代码,然后在项目运行时直接调用;
- 运行时注解: (代码注入)在代码中通过注解进行标记,在运行时通过反射在原代码的基础上完成注入;
Butterknife就是用的编译时注解(Annotation Processing Tool),简称APT技术. 关于APT, 可以戳这里,里面有demo,介绍了编译时自动生成代码的整个过程. 看完这篇文章再看Butterknife就有章可循了.
Butterknife源码解析
了解APT之后就知道了最重要的有两步,一编译时自动生成代码,二运行时进行代码绑定.下面以Butterknife最新版本8.8.1的源码进行说明;
编译时自动生成代码
首先来看ButterKnifeProcessor类,继承AbstractProcessor,需要复写的方法:
init(): 主要初始化一些工具类
@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
... //获取sdk和是否debug
elementUtils = env.getElementUtils();//获取元素相关信息的工具类
typeUtils = env.getTypeUtils();//处理TypeMirror的工具类
filer = env.getFiler();//生成java文件的工具类
try {
trees = Trees.instance(processingEnv);
} catch (IllegalArgumentException ignored) {
}
}
getSupportedAnnotationTypes(): 返回支持的注解类型,包括BindAnim,BindArray等注解,以及OnClick,OnCheckedChanged等监听.
private Set> getSupportedAnnotations() {
Set> 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;
}
process():生成代码的核心部分,分两步,扫描处理代码中所有的注解,然后通过javapoet生成java文件. 返回值表示这组注解是否被这个 Processor 接受,如果接受(返回true)后续子的 processor 不会再对这个注解进行处理.
@Override public boolean process(Set extends TypeElement> elements, RoundEnvironment env) {
//扫描代码中所有的注解,存到map中
Map bindingMap = findAndParseTargets(env);
for (Map.Entry entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
//生成java文件
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;
}
RoundEnvironment表示当前或之前的运行环境,可以通过该对象查找注解.
TypeElement代表源代码中的元素类型, 如包,类,属性,方法, 源代码中每一部分都是一种类型.包为PackageElement,类为TypeElement,属性为VariableElement,方法为ExecuteableElement ,都是Element的子类.
然后看下findAndParseTargets方法是如何扫描所有注解的.
private Map findAndParseTargets(RoundEnvironment env) {
Map builderMap = new LinkedHashMap<>();
Set erasedTargetNames = new LinkedHashSet<>();
//建立view与id的关系
scanForRClasses(env);
// Process each @BindView element.
//env.getElementsAnnotatedWith(BindView.class))获取所有注解是BindView的元素
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;
}
首先看scanForRClasses()如何建立view与id的关系
private void scanForRClasses(RoundEnvironment env) {
if (trees == null) return;
//R文件扫描器,扫描代码中所有的R文件
RClassScanner scanner = new RClassScanner();
for (Class extends Annotation> annotation : getSupportedAnnotations()) {
for (Element element : env.getElementsAnnotatedWith(annotation)) {
JCTree tree = (JCTree) trees.getTree(element, getMirror(element, annotation));
if (tree != null) { // tree can be null if the references are compiled types and not source
//获取R文件的包名
String respectivePackageName =
elementUtils.getPackageOf(element).getQualifiedName().toString();
scanner.setCurrentPackageName(respectivePackageName);
tree.accept(scanner);
}
}
}
for (Map.Entry> packageNameToRClassSet : scanner.getRClasses().entrySet()) {
String respectivePackageName = packageNameToRClassSet.getKey();
for (String rClass : packageNameToRClassSet.getValue()) {
//解析R文件
parseRClass(respectivePackageName, rClass);
}
}
}
parseRClass()利用IdScanner寻找R文件内部类,如array,attr,string等
private void parseRClass(String respectivePackageName, String rClass) {
Element element;
try {
element = elementUtils.getTypeElement(rClass);
} catch (MirroredTypeException mte) {
element = typeUtils.asElement(mte.getTypeMirror());
}
JCTree tree = (JCTree) trees.getTree(element);
if (tree != null) { // tree can be null if the references are compiled types and not source
//利用IdScanner寻找R文件内部类,如array,attr,string等
IdScanner idScanner = new IdScanner(symbols, elementUtils.getPackageOf(element)
.getQualifiedName().toString(), respectivePackageName);
tree.accept(idScanner);
} else {
parseCompiledR(respectivePackageName, (TypeElement) element);
}
}
在IdScanner类内部利用VarScanner扫描R文件内部类(id,string等)的属性(键值对:资源名和id)
private static class IdScanner extends TreeScanner {
private final Map ids;
private final String rPackageName;
private final String respectivePackageName;
IdScanner(Map ids, String rPackageName, String respectivePackageName) {
this.ids = ids;
this.rPackageName = rPackageName;
this.respectivePackageName = respectivePackageName;
}
@Override public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
for (JCTree tree : jcClassDecl.defs) {
if (tree instanceof ClassTree) {
ClassTree classTree = (ClassTree) tree;
String className = classTree.getSimpleName().toString();
if (SUPPORTED_TYPES.contains(className)) {
ClassName rClassName = ClassName.get(rPackageName, "R", className);
VarScanner scanner = new VarScanner(ids, rClassName, respectivePackageName);
((JCTree) classTree).accept(scanner);
}
}
}
}
}
在VarScanner类内部记录资源名称及id
private static class VarScanner extends TreeScanner {
private final Map ids;
private final ClassName className;
private final String respectivePackageName;
private VarScanner(Map ids, ClassName className,
String respectivePackageName) {
this.ids = ids;
this.className = className;
this.respectivePackageName = respectivePackageName;
}
@Override public void visitVarDef(JCTree.JCVariableDecl jcVariableDecl) {
if ("int".equals(jcVariableDecl.getType().toString())) {
int id = Integer.valueOf(jcVariableDecl.getInitializer().toString());
String resourceName = jcVariableDecl.getName().toString();
QualifiedId qualifiedId = new QualifiedId(respectivePackageName, id);
ids.put(qualifiedId, new Id(id, className, resourceName));
}
}
}
到此就建立了所有view与id的关系,然后看如何处理注解, 以BindView为例(其他类似). 将每一个BindView注解的元素存放到被绑定类的成员变量中.
private void parseBindView(Element element, Map builderMap,
Set erasedTargetNames) {
//获取BindView注解元素的父元素, 如MainActivity
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// Start by verifying common generated code restrictions.
//isInaccessibleViaGeneratedCode检查父元素是否是类且非private
//isBindingInWrongPackage检查父元素是否是系统相关的类
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();
//判断BindView注解的元素是view的子类或接口,否则有错return
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;
}
// Assemble information on the field.
//获取BindView注解的value,及id
int id = element.getAnnotation(BindView.class).value();
//builderMap缓存所有被绑定类的信息, 键是父元素(如MainActivity),值是键对应的被绑定类的信息(如MainActivity_ViewBinding)
//获取BindingSet.Builder, 有则使用缓存,无则创建
BindingSet.Builder builder = builderMap.get(enclosingElement);
QualifiedId qualifiedId = elementToQualifiedId(element, id);
if (builder != null) {
//从被绑定类的成员变量中查找这个id,不为空,说明有多个view绑定了同一个id, 抛异常
String existingBindingName = builder.findExistingBindingName(getId(qualifiedId));
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(getId(qualifiedId), new FieldViewBinding(name, type, required));
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
}
再看如何创建被绑定类BindingSet
private BindingSet.Builder getOrCreateBindingBuilder(
Map builderMap, TypeElement enclosingElement) {
BindingSet.Builder builder = builderMap.get(enclosingElement);
if (builder == null) {
builder = BindingSet.newBuilder(enclosingElement);
builderMap.put(enclosingElement, builder);
}
return builder;
}
static Builder newBuilder(TypeElement enclosingElement) {
//获取注解父元素的类型,View/Activity/Dialog
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('.', '$');
//获取绑定的类名,加后缀_ViewBinding(后面介绍生成加后缀_ViewBinding的java文件)
ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");
boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog);
}
到此完成了扫描所有注解的工作,再看如何生成java文件.就是process()中的binding.brewJava(sdk, debuggable);
JavaFile brewJava(int sdk, boolean debuggable) {
//bindingClassName就是加了后缀_ViewBinding的类
return JavaFile.builder(bindingClassName.packageName(), createType(sdk, debuggable))
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
private TypeSpec createType(int sdk, boolean debuggable) {
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());
} 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();
}
这里用到了javapoet,感兴趣的可以戳这里, 通过TypeSpec生成类,并一步步地添加构造方法等.
编译之后就会在build目录下会产生原Activity.class以及原Activity_ViewBinding.class两份代码,.来看下_ViewBinding代码的结构,以官方SimpleActivity_ViewBinding为例.在构造函数中生成控件以及事件监听,在unbind对所有控件,事件以及原Activity的引用置空,等待GC回收.
public class SimpleActivity_ViewBinding implements Unbinder {
protected T target;
private View view2130968578;
private View view2130968579;
@UiThread
public SimpleActivity_ViewBinding(final T target, View source) {
this.target = target;
View view;
//target就是SimpleActivity,所以SimpleActivity中的title,subtitle等被BindView注解的元素都不能是private的.
target.title = Utils.findRequiredViewAsType(source, R.id.title, "field 'title'", TextView.class);
target.subtitle = Utils.findRequiredViewAsType(source, R.id.subtitle, "field 'subtitle'", TextView.class);
view = Utils.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', and method 'sayGetOffMe'");
target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class);
view2130968578 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.sayHello();
}
});
view.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View p0) {
return target.sayGetOffMe();
}
});
view = Utils.findRequiredView(source, R.id.list_of_things, "field 'listOfThings' and method 'onItemClick'");
target.listOfThings = Utils.castView(view, R.id.list_of_things, "field 'listOfThings'", ListView.class);
view2130968579 = view;
((AdapterView>) view).setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> p0, View p1, int p2, long p3) {
target.onItemClick(p2);
}
});
target.footer = Utils.findRequiredViewAsType(source, R.id.footer, "field 'footer'", TextView.class);
target.headerViews = Utils.listOf(
Utils.findRequiredView(source, R.id.title, "field 'headerViews'"),
Utils.findRequiredView(source, R.id.subtitle, "field 'headerViews'"),
Utils.findRequiredView(source, R.id.hello, "field 'headerViews'"));
}
@Override
@CallSuper
public void unbind() {
T target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
target.title = null;
target.subtitle = null;
target.hello = null;
target.listOfThings = null;
target.footer = null;
target.headerViews = null;
view2130968578.setOnClickListener(null);
view2130968578.setOnLongClickListener(null);
view2130968578 = null;
((AdapterView>) view2130968579).setOnItemClickListener(null);
view2130968579 = null;
this.target = null;
}
}
编译时自动生成代码讲完了, 然后来看代码的绑定.
代码绑定
用过Butterknife的朋友都知道是通过ButterKnife.bind(this)进行绑定(以Activity为例).
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
//获取跟布局view
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
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());
//找到被绑定类(如SimpleActivity_ViewBinding)的构造函数
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与Activity_ViewBinding的绑定,即在ButterKnife.bind(this)时就完成原Activity注解控件及事件的初始化,原Activity就可以直接调用了.
如理解有误,欢迎指正.最后附上相关技术的文章:
APT----《android-apt》
Javapoet----《javapoet——让你从重复无聊的代码中解放出来》