主目录见:Android高级进阶知识(这是总目录索引)
前面我们已经讲完[编译期注解的使用例子]大家应该对这个流程比较熟悉了,我们今天要讲的butterknife的源码其实也是用的这个,不过里面细节还是比较多的,我今天会尽量围绕着主干讲,对于一些lint检查,R2文件的生成(这个主要是用插件完成)这些暂时会不讲。
一.目标
对于butterknife,如果使用的好的话可以帮我们省了非常多的工作,而且我们可以把这里面的知识点应用到我们的框架中去,毕竟看尽了代码才能融会贯通,今天我们目标就是:
1.复习编译期注解的流程;
2.了解butterknife中的设计思想;
3.能借鉴到自己的框架项目里面。
二.源码分析
看这个源码之前,我们首先要来看下代码的目录,让我们有个整体的认识,首先让我们祭出大图:
- butterknife:提供android程序使用的api的模块
- butterknife-annotations:java模块,里面放着butterknife支持的注解
- butterknife-compiler:java模块,编译注解处理器就在这里
- butterknife-gradle-plugin:这个是后面才有的,为了解决在library里面使用的问题
- butterknife-integration-test:这个项目的测试用例
- butterknife-lint:lint优化的代码都在这里
- sample:demo代码
看到了目录我们应该会有点熟悉的感觉才对,因为我们前一篇已经讲过,还有分析Eventbus3.0也有点这个蛛丝马迹。
1.生成后的源码
为了我们后面更容易地分析butterknife编译期注解的代码,我们先来看下生成的代码长得啥样,这样我们才能更好地知道我们注解处理器是怎么生成代码的:
// Generated code from Butter Knife. Do not modify!
package com.lenovohit.butterknifedemo;
import android.support.annotation.CallSuper;
import android.support.annotation.UiThread;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import butterknife.Unbinder;
import butterknife.internal.DebouncingOnClickListener;
import butterknife.internal.Utils;
import java.lang.IllegalStateException;
import java.lang.Override;
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
private View view2131427446;
private View view2131427447;
@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;
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);
view2131427446 = 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);
view2131427447 = 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() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.title = null;
target.subtitle = null;
target.hello = null;
target.listOfThings = null;
target.footer = null;
target.headerViews = null;
view2131427446.setOnClickListener(null);
view2131427446.setOnLongClickListener(null);
view2131427446 = null;
((AdapterView>) view2131427447).setOnItemClickListener(null);
view2131427447 = null;
}
}
这个例子我是用官网的例子生成的,因为官网上面最新的例子是支持的android studio3.0版本,所以我另外创建了一个项目放进去的。我们等会就可以对照这个来看源码了。
2.注解处理器
首先我们明确一下流程,注解处理器在编译时期会扫描到所有的带有编译期注解的注解例如@BindView @Onclick@OnLongClick等等,然后butterknife会使用javapoet来生成源码,最后我们会对生成的代码进行调用。
那么现在我们就来看看注解处理器的代码。那么我们直接到达butterknife-compiler包下面的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));
elementUtils = env.getElementUtils();
typeUtils = env.getTypeUtils();
filer = env.getFiler();
try {
trees = Trees.instance(processingEnv);
} catch (IllegalArgumentException ignored) {
}
}
我们看到这里首先获取了sdk的版本,然后初始化了elementUtils(用来处理Element的辅助类), typeUtils(用来处理TypeMirror的工具类),filer(用来生成java的源文件),同时实例化了trees对象(这个跟语法分析树有关)。然后我们看getSupportedAnnotationTypes()方法:
@Override public Set getSupportedAnnotationTypes() {
Set types = new LinkedHashSet<>();
for (Class extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}
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;
}
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 //
);
我们看到这个方法里面支持的注解很多这些注解都在butterknife-annotations这个java模块里面。看了这两个方法之后,我们就直接来看注解处理器的核心方法process()。
3.process
我们知道我们前面说了process一般分成两步:1.收集信息 2.生成代码。这边也不例外:
@Override public boolean process(Set extends TypeElement> elements, RoundEnvironment env) {
//收集信息
Map bindingMap = findAndParseTargets(env);
for (Map.Entry 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());
}
}
这边的步骤非常清楚,所以我们这边直接来看收集信息是收集了哪些信息:
private Map findAndParseTargets(RoundEnvironment env) {
Map builderMap = new LinkedHashMap<>();
Set erasedTargetNames = new LinkedHashSet<>();
// 建立view与R的id的关系
scanForRClasses(env);
//省略了其他注解的代码
...........
// Process each @BindView element.
// 解析BindView注解
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
try {
parseBindView(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindView.class, e);
}
}
..........
// Process each annotation that corresponds to a listener.
for (Class extends Annotation> listener : LISTENERS) {
findAndParseListener(env, listener, builderMap, erasedTargetNames);
}
// 将Map.Entry转化为Map
Deque> entries =
new ArrayDeque<>(builderMap.entrySet());
Map bindingMap = new LinkedHashMap<>();
while (!entries.isEmpty()) {
Map.Entry entry = entries.removeFirst();
TypeElement type = entry.getKey();
BindingSet.Builder builder = entry.getValue();
TypeElement parentType = findParentType(type, erasedTargetNames);
if (parentType == null) {
bindingMap.put(type, builder.build());
} else {
BindingSet parentBinding = bindingMap.get(parentType);
if (parentBinding != null) {
builder.setParent(parentBinding);
bindingMap.put(type, builder.build());
} else {
// Has a superclass binding but we haven't built it yet. Re-enqueue for later.
entries.addLast(entry);
}
}
}
return bindingMap;
}
这个收集信息的方法很长,我们这里就挑一个注解BindView来看,看看是怎么收集信息的。但是看这个之前我们先来看看scanForRClasses()方法是干什么的呢?
3.1 scanForRClasses
scanForRClasses主要是用来建立view与id的关系,为了是设置的@onclick等注解的视图唯一。
例如在上面所举的例子中,MainActivity_ViewBinding里会持有一个全局变量view2131427446,这个其实就是MainActivity的Button,后面的2131427446就是对应在R文件的id。
scanForRClasses里面涉及到语法分析树,我们这里简要说明下代码的流程就不细说了,因为这不是主干知识:程序首先会利用elementUtils获取到包名,然后利用RClassScanner类来查找R文件,在R文件里面用IdScanner来查找R文件中的id,在IdScanner中用VarScanner来查找Button的id,最后就得到Button对应于view2131427446。
3.2 parseBindView
因为我们这里以BindView为例,其他注解其实也是类似,所以我们分析一个注解就应该足够了。程序首先会遍历出来带BindView注解的Element,然后调用parseBindView():
private void parseBindView(Element element, Map builderMap,
Set erasedTargetNames) {
//得到包含该注解的TypeElement(类元素),例如MainActivity
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();
//判断被注解的元素是不是View的子类或者接口
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里面所带的id
int id = element.getAnnotation(BindView.class).value();
//这个地方一个类对应一个builder
BindingSet.Builder builder = builderMap.get(enclosingElement);
QualifiedId qualifiedId = elementToQualifiedId(element, id);
//这个地方是检测这个类对应的Builder 是否已经有绑定过这个id,因为一个类中的布局中的控件id只能是唯一的
if (builder != null) {
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);
}
//被注解的变量的名字,类型以及是否带有@optional注解
String name = simpleName.toString();
TypeName type = TypeName.get(elementType);
boolean required = isFieldRequired(element);
//builder添加这个变量
builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
}
这个方法比较长,首先我们来看第一个部分合法性检验,这里有两个检验方法,我们先来看isInaccessibleViaGeneratedCode()方法:
private boolean isInaccessibleViaGeneratedCode(Class extends Annotation> annotationClass,
String targetThing, Element element) {
boolean hasError = false;
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// Verify method modifiers.
Set modifiers = element.getModifiers();
//判断变量是不是private和static的
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;
}
//判断类是不是private的
// 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;
}
从这个检测方法可以看出,我们BindView这个注解不能用在private和static的变量上面,而且不能用在不是类且类为private的情况下。接着我们来看另外检测方法isBindingInWrongPackage():
private boolean isBindingInWrongPackage(Class extends Annotation> annotationClass,
Element element) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
String qualifiedName = enclosingElement.getQualifiedName().toString();
//类所在的包不能是以android.开头的
if (qualifiedName.startsWith("android.")) {
error(element, "@%s-annotated class incorrectly in Android framework package. (%s)",
annotationClass.getSimpleName(), qualifiedName);
return true;
}
//类所在的包不能是以java.开头的
if (qualifiedName.startsWith("java.")) {
error(element, "@%s-annotated class incorrectly in Java framework package. (%s)",
annotationClass.getSimpleName(), qualifiedName);
return true;
}
return false;
}
我们看到这里检测还是非常严谨的,我们的类所在的包名不能是android.和java.开头,也就是系统的包。接着我们看后面builder的创建和id唯一性的判断,首先我们要明确一下一个类是对应于一个builder的,首先我们来看下builder长啥样,这是BindingSet的内部类:
static final class Builder {
//添加有注解的对应类的类名
private final TypeName targetTypeName;
//注解处理器生成源码文件类的类名
private final ClassName bindingClassName;
//这些是标记,标识是不是final的,view,activity,dialog
private final boolean isFinal;
private final boolean isView;
private final boolean isActivity;
private final boolean isDialog;
private BindingSet parentBinding;
//一个id对应于一个ViewBinding.Builder(这个Builder里面包含对应的变量名称,类型,是否required,
//且如果有事件还可能对应的监听事件和监听方法相关信息)
private final Map viewIdMap = new LinkedHashMap<>();
private final ImmutableList.Builder collectionBindings =
ImmutableList.builder();
private final ImmutableList.Builder resourceBindings = ImmutableList.builder();
}
从我们上面注释可以看出这个Builder其实就是对应于我们要生成代码所需要的所有信息,我们只要把这些信息构造进去即可。我们知道我们builder不存在的时候我们会去创建一个,程序会调用getOrCreateBindingBuilder()方法,所以我们这里跟进这个方法看下:
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;
}
我们看到程序首先会从builderMap中查找这个类有没有对应的Builder,如果没有则调用newBuilder来创建,所以我们再跟进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;
}
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);
}
这个方法其实很简单,首先就是判断isView ,isActivity ,isDialog 是不是true,然后获取带有此注解的类的类名,接着获取要生成的源代码的类名例如:MainActivity_ViewBinding或者SimpleAdapter$ViewHolder_ViewBinding。到这里解析BindView注解的流程已经完毕了。但是我们知道,我们ButterKnife不一定只会用在视图上面,我们也可以用在方法上面,如@onClick@onLongClick等等。所以我们接下来要来分析这方面的代码。
4.事件的注解
事件的注解相对于控件的注解会比较麻烦一点,首先我们来看下我们注解在方法上面的样子:
然后我们看生成的代码的样子:
从两图对比我们可以很清楚地看到我们程序自动补足了setOnclickListener这些部分,sayHello变成doClick方法里面的方法调用。那么这些setOnclickListener和doClick还有DebouncingOnClickListener是哪里来的呢?我们先去看看@OnClick注解一下:
其他的注解类似,我们看到OnClick注解里面又有注解@ListernerClass而且method里面还有注解@ListernerMethod,所以我们能想到,我们程序肯定是获取了OnClick上面的注解然后进行用javaPoet拼接的嘛。带着这个想法我们来看下源码:
// Process each annotation that corresponds to a listener.
for (Class extends Annotation> listener : LISTENERS) {
findAndParseListener(env, listener, builderMap, erasedTargetNames);
}
我们看见这个代码是遍历所有的LISTENERS中所有的注解,然后调用findAndParseListener方法,那么我们直接来看这个方法:
private void findAndParseListener(RoundEnvironment env,
Class extends Annotation> annotationClass,
Map builderMap, Set erasedTargetNames) {
//得到带有此注解的元素然后遍历
for (Element element : env.getElementsAnnotatedWith(annotationClass)) {
//检查合法性问题
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseListenerAnnotation(annotationClass, element, builderMap, erasedTargetNames);
} catch (Exception e) {
StringWriter stackTrace = new StringWriter();
e.printStackTrace(new PrintWriter(stackTrace));
error(element, "Unable to generate view binder for @%s.\n\n%s",
annotationClass.getSimpleName(), stackTrace.toString());
}
}
}
上面的代码很简单,其实还是调用了parseListenerAnnotation方法,我们同样跟进去:
private void parseListenerAnnotation(Class extends Annotation> annotationClass, Element element,
Map builderMap, Set erasedTargetNames)
throws Exception {
// This should be guarded by the annotation's @Target but it's worth a check for safe casting.
//判断这个注解是不是加在方法上面的
if (!(element instanceof ExecutableElement) || element.getKind() != METHOD) {
throw new IllegalStateException(
String.format("@%s annotation must be on a method.", annotationClass.getSimpleName()));
}
//获取这个方法Element
ExecutableElement executableElement = (ExecutableElement) element;
//获取这个方法所在的类
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// Assemble information on the method.
//获取这个注解中value的值
Annotation annotation = element.getAnnotation(annotationClass);
Method annotationValue = annotationClass.getDeclaredMethod("value");
//因为value中的值是对应于控件的id,所以必须要为int
if (annotationValue.getReturnType() != int[].class) {
throw new IllegalStateException(
String.format("@%s annotation value() type not int[].", annotationClass));
}
//获取到这个注解对应的所有id
int[] ids = (int[]) annotationValue.invoke(annotation);
String name = executableElement.getSimpleName().toString();
boolean required = isListenerRequired(executableElement);
// Verify that the method and its containing class are accessible via generated code.
//合法性检测
boolean hasError = isInaccessibleViaGeneratedCode(annotationClass, "methods", element);
hasError |= isBindingInWrongPackage(annotationClass, element);
Integer duplicateId = findDuplicate(ids);
if (duplicateId != null) {
error(element, "@%s annotation for method contains duplicate ID %d. (%s.%s)",
annotationClass.getSimpleName(), duplicateId, enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
//获取注解(例如OnClick)上面的注解ListenerClass
ListenerClass listener = annotationClass.getAnnotation(ListenerClass.class);
if (listener == null) {
throw new IllegalStateException(
String.format("No @%s defined on @%s.", ListenerClass.class.getSimpleName(),
annotationClass.getSimpleName()));
}
for (int id : ids) {
if (id == NO_ID.value) {
//如果id不存在
//且id如果为1的话且是optional的那就是错误的
if (ids.length == 1) {
if (!required) {
error(element, "ID-free binding must not be annotated with @Optional. (%s.%s)",
enclosingElement.getQualifiedName(), element.getSimpleName());
hasError = true;
}
} else {
error(element, "@%s annotation contains invalid ID %d. (%s.%s)",
annotationClass.getSimpleName(), id, enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
}
}
ListenerMethod method;
//获取注解ListenerClass中对应的ListenerMethod
ListenerMethod[] methods = listener.method();
if (methods.length > 1) {
throw new IllegalStateException(String.format("Multiple listener methods specified on @%s.",
annotationClass.getSimpleName()));
} else if (methods.length == 1) {
//方法如果只有一个的话,那么callbacks必须为空不然就抛出错误
if (listener.callbacks() != ListenerClass.NONE.class) {
throw new IllegalStateException(
String.format("Both method() and callback() defined on @%s.",
annotationClass.getSimpleName()));
}
method = methods[0];
} else {
//反射调用callback方法
Method annotationCallback = annotationClass.getDeclaredMethod("callback");
Enum> callback = (Enum>) annotationCallback.invoke(annotation);
Field callbackField = callback.getDeclaringClass().getField(callback.name());
method = callbackField.getAnnotation(ListenerMethod.class);
////如果没有ListenerMethod.class注解 抛出异常
if (method == null) {
throw new IllegalStateException(
String.format("No @%s defined on @%s's %s.%s.", ListenerMethod.class.getSimpleName(),
annotationClass.getSimpleName(), callback.getDeclaringClass().getSimpleName(),
callback.name()));
}
}
// Verify that the method has equal to or less than the number of parameters as the listener.
List extends VariableElement> methodParameters = executableElement.getParameters();
//判断注解的方法的参数不能多于ButterKnife中的ListenerMethod中的方法的方法参数(为什么呢?
//因为最后生成的代码里面要去调用我们注解的方法,如果注解的方法参数多的话那么就导致有几个参数只能是外部的
//,所以显然是不合理的)
if (methodParameters.size() > method.parameters().length) {
error(element, "@%s methods can have at most %s parameter(s). (%s.%s)",
annotationClass.getSimpleName(), method.parameters().length,
enclosingElement.getQualifiedName(), element.getSimpleName());
hasError = true;
}
// Verify method return type matches the listener.
//判断注解的方法的返回值和ListenerMethod中的方法的返回值必须一样
TypeMirror returnType = executableElement.getReturnType();
if (returnType instanceof TypeVariable) {
TypeVariable typeVariable = (TypeVariable) returnType;
returnType = typeVariable.getUpperBound();
}
if (!returnType.toString().equals(method.returnType())) {
error(element, "@%s methods must have a '%s' return type. (%s.%s)",
annotationClass.getSimpleName(), method.returnType(),
enclosingElement.getQualifiedName(), element.getSimpleName());
hasError = true;
}
if (hasError) {
return;
}
//判断ListenerMethod中方法的方法参数是不是包含所有的注解方法的参数
Parameter[] parameters = Parameter.NONE;
if (!methodParameters.isEmpty()) {
parameters = new Parameter[methodParameters.size()];
BitSet methodParameterUsed = new BitSet(methodParameters.size());
String[] parameterTypes = method.parameters();
for (int i = 0; i < methodParameters.size(); i++) {
VariableElement methodParameter = methodParameters.get(i);
TypeMirror methodParameterType = methodParameter.asType();
if (methodParameterType instanceof TypeVariable) {
TypeVariable typeVariable = (TypeVariable) methodParameterType;
methodParameterType = typeVariable.getUpperBound();
}
for (int j = 0; j < parameterTypes.length; j++) {
if (methodParameterUsed.get(j)) {
continue;
}
if ((isSubtypeOfType(methodParameterType, parameterTypes[j])
&& isSubtypeOfType(methodParameterType, VIEW_TYPE))
|| isTypeEqual(methodParameterType, parameterTypes[j])
|| isInterface(methodParameterType)) {
parameters[i] = new Parameter(j, TypeName.get(methodParameterType));
methodParameterUsed.set(j);
break;
}
}
if (parameters[i] == null) {
StringBuilder builder = new StringBuilder();
builder.append("Unable to match @")
.append(annotationClass.getSimpleName())
.append(" method arguments. (")
.append(enclosingElement.getQualifiedName())
.append('.')
.append(element.getSimpleName())
.append(')');
for (int j = 0; j < parameters.length; j++) {
Parameter parameter = parameters[j];
builder.append("\n\n Parameter #")
.append(j + 1)
.append(": ")
.append(methodParameters.get(j).asType().toString())
.append("\n ");
if (parameter == null) {
builder.append("did not match any listener parameters");
} else {
builder.append("matched listener parameter #")
.append(parameter.getListenerPosition() + 1)
.append(": ")
.append(parameter.getType());
}
}
builder.append("\n\nMethods may have up to ")
.append(method.parameters().length)
.append(" parameter(s):\n");
for (String parameterType : method.parameters()) {
builder.append("\n ").append(parameterType);
}
builder.append(
"\n\nThese may be listed in any order but will be searched for from top to bottom.");
error(executableElement, builder.toString());
return;
}
}
}
//构造Builder对象
MethodViewBinding binding = new MethodViewBinding(name, Arrays.asList(parameters), required);
BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
for (int id : ids) {
QualifiedId qualifiedId = elementToQualifiedId(element, id);
if (!builder.addMethod(getId(qualifiedId), listener, method, binding)) {
error(element, "Multiple listener methods with return value specified for ID %d. (%s.%s)",
id, enclosingElement.getQualifiedName(), element.getSimpleName());
return;
}
}
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
}
我们看到这一串代码比较长,但是其实逻辑不难,上面的注释已经完全说的明白了,然后我们看到最后也是来构造这个Builder对象,我们看到里面出现了一个MethodViewBinding 对象,看名字我们就能想到,这个看你的存放方法相关的一些信息的嘛:
final class MethodViewBinding implements MemberViewBinding {
private final String name;
private final List parameters;
private final boolean required;
}
我们看到我们猜想是正确的,这个类存放了方法名,参数,和是否是required。然后最后放进builder中。到这里我们的信息收集已经说完了,下一篇我们将来说我们的代码生成,敬请期待哈。。。。
总结:ButterKnife的注解处理器主要分为信息收集和生成代码,我们这篇代码主要讲了信息收集(因为实在太多,不得不分出来),下一篇我们重点来讲生成代码,信息收集里面主要是为生成代码最准备,构造一个Builder对象,这个对象包含了生成代码需要的所有信息,大家一定记住!!!