前言
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<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;
}
process():生成代碼的核心部分,分兩步,掃描處理代碼中所有的注解,然后通過javapoet生成java文件. 返回值表示這組注解是否被這個 Processor 接受,如果接受(返回true)后續子的 processor 不會再對這個注解進行處理.
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
//掃描代碼中所有的注解,存到map中
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
for (Map.Entry<TypeElement, BindingSet> 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<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
Set<TypeElement> 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<String, Set<String>> 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<QualifiedId, Id> ids;
private final String rPackageName;
private final String respectivePackageName;
IdScanner(Map<QualifiedId, Id> 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<QualifiedId, Id> ids;
private final ClassName className;
private final String respectivePackageName;
private VarScanner(Map<QualifiedId, Id> 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<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> 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<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;
}
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<T extends SimpleActivity> 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——讓你從重復無聊的代碼中解放出來》