Butterknife的源码之前只是单纯的知道大致的一个思路,没有具体看过他的实现步骤。因作此文已记之。
Ox1 init :对外暴露的初始函数
@Override public synchronized void init(ProcessingEnvironment env) {
super.init(env);
//获取sdk版本 ,注解器的配置在 getSupportedOptions() 中查看
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.");
}
}
//是否debug模式
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) {
}
}
//配置了注解处理器所需要的入参
@Override public Set<String> getSupportedOptions() {
return ImmutableSet.of(OPTION_SDK_INT, OPTION_DEBUGGABLE);
}
getSupportedIptions的配置项在主工程的build.gradle
android {
javaCompileOptions {
annotationProcessorOptions {
arguments = [debug: "2333"] //参数只能是字符串
}
}
}
总结以上代码:
- 获取配置项
- 获取相关的工具类的实例
Ox2 process :处理注解的主体方法
@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();
//生成java代码
JavaFile javaFile = binding.brewJava(sdk, debuggable);
try {
//创建java文件
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return false;
}
以上的流程有两个疑惑点:
- BindingSet:这个类是作用是?
- findAndParseTargets:如何获取到注解内容的结果的?
我们先来看看BindingSet的核心代码: constructor & brewJava
//通过Budilder 构造,对参数进行配置
private BindingSet(TypeName targetTypeName, ClassName bindingClassName, boolean isFinal,
boolean isView, boolean isActivity, boolean isDialog, ImmutableList<ViewBinding> viewBindings,
ImmutableList<FieldCollectionViewBinding> collectionBindings,
ImmutableList<ResourceBinding> resourceBindings, BindingSet parentBinding) {
this.isFinal = isFinal;
this.targetTypeName = targetTypeName;
this.bindingClassName = bindingClassName;
this.isView = isView;
this.isActivity = isActivity;
this.isDialog = isDialog;
this.viewBindings = viewBindings;
this.collectionBindings = collectionBindings;
this.resourceBindings = resourceBindings;
this.parentBinding = parentBinding;
}
//根据上面的配置,生成了相对应代码
JavaFile brewJava(int sdk, boolean debuggable) {
return JavaFile.builder(bindingClassName.packageName(), createType(sdk, debuggable))
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
总结以上代码:
BindingSet 大致的作用就是
- 一个文件所需要的所有绑定关系
- 生成的代码的函数(通过JavaPoet)
所以剩下的就在 findAndParseTargets 这个方法内了
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
scanForRClasses(env);
// Process each @BindColor element.
for (Element element : env.getElementsAnnotatedWith(BindColor.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceColor(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindColor.class, e);
}
}
// Process each @BindView element.
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);
}
}
(以下省略其他 类似 @BindXXX 代码)
...
// Process each annotation that corresponds to a listener.
for (Class<? extends Annotation> listener : LISTENERS) {
findAndParseListener(env, listener, builderMap, erasedTargetNames);
}
// Associate superclass binders with their subclass binders. This is a queue-based tree walk
// which starts at the roots (superclasses) and walks to the leafs (subclasses).
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;
总结以上代码:
- 处理注解的信息
- 将结果组合成bindingMap
以上的 parseXXX 方法实现逻辑大体一致,因此我们只需要看其中的一个细分方法就ok了
Ox3 parseBindView :处理 @BindView注解 信息
为了让大家更好的理解,我们先写一个demo
package com.example
public class MainActivity extends AppCompatActivity {
@BindView(R.id.text)
TextView tvTest
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
以下将会以上面的demo的情况来进行讲解
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> erasedTargetNames) {
//获取 MainActivity
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
//判断 修饰符不为private 字段;注解不使用在Android、java的包内
boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
|| isBindingInWrongPackage(BindView.class, element);
//获取 TextView
TypeMirror elementType = element.asType();
//todo
if (elementType.getKind() == TypeKind.TYPEVAR) {
TypeVariable typeVariable = (TypeVariable) elementType;
elementType = typeVariable.getUpperBound();
}
//获取 com.example.MainActivity
Name qualifiedName = enclosingElement.getQualifiedName();
//获取 tvTest
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;
}
//获取注解上的id
int id = element.getAnnotation(BindView.class).value();
BindingSet.Builder builder = builderMap.get(enclosingElement);
QualifiedId qualifiedId = elementToQualifiedId(element, 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);
}
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);
}