主目錄見: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<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}
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;
}
private static final List<Class<? extends Annotation>> 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<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());
}
}
這邊的步驟非常清楚,所以我們這邊直接來看收集信息是收集了哪些信息:
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
Set<TypeElement> 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<TypeElement, BindingSet.Builder>轉化為Map<TypeElement, BindingSet>
Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries =
new ArrayDeque<>(builderMap.entrySet());
Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();
while (!entries.isEmpty()) {
Map.Entry<TypeElement, BindingSet.Builder> 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<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> 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<Modifier> 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<Id, ViewBinding.Builder> viewIdMap = new LinkedHashMap<>();
private final ImmutableList.Builder<FieldCollectionViewBinding> collectionBindings =
ImmutableList.builder();
private final ImmutableList.Builder<ResourceBinding> resourceBindings = ImmutableList.builder();
}
從我們上面注釋可以看出這個Builder其實就是對應于我們要生成代碼所需要的所有信息,我們只要把這些信息構造進去即可。我們知道我們builder不存在的時候我們會去創建一個,程序會調用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;
}
我們看到程序首先會從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<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> 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<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> 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<Parameter> parameters;
private final boolean required;
}
我們看到我們猜想是正確的,這個類存放了方法名,參數,和是否是required。然后最后放進builder中。到這里我們的信息收集已經說完了,下一篇我們將來說我們的代碼生成,敬請期待哈。。。。
總結:ButterKnife的注解處理器主要分為信息收集和生成代碼,我們這篇代碼主要講了信息收集(因為實在太多,不得不分出來),下一篇我們重點來講生成代碼,信息收集里面主要是為生成代碼最準備,構造一個Builder對象,這個對象包含了生成代碼需要的所有信息,大家一定記住!!!