源碼剖析——編譯期解析注解、生成java代碼流程
在上一篇 ButterKnife的工作流程 中我們分析了ButterKnife.bind()的流程乔煞,那么編譯期是如何生成MainActivity$$ViewBinder.java呢?答案將在本篇揭曉烁涌。
編譯時 Annotation 指 @Retention 為 CLASS 的 Annotation袖牙,甴 apt(Annotation Processing Tool) 解析自動解析。需要做的:
1惨远、自定義類繼承自 AbstractProcessor
2、重寫其中的 process 函數(shù)
其實(shí)就是 apt(Annotation Processing Tool) 在編譯時自動查找所有繼承自 AbstractProcessor 的類话肖,然后調(diào)用他們的 process 方法去處理北秽。
ButterKnife中繼承AbstractProcessor的是ButterKnifeProcessor類:
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 //
);
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<String>();
/** 添加支持掃描的注解類型 **/
types.add(Bind.class.getCanonicalName());
for (Class<? extends Annotation> listener : LISTENERS) {
types.add(listener.getCanonicalName());
}
types.add(BindBool.class.getCanonicalName());
types.add(BindColor.class.getCanonicalName());
types.add(BindDimen.class.getCanonicalName());
types.add(BindDrawable.class.getCanonicalName());
types.add(BindInt.class.getCanonicalName());
types.add(BindString.class.getCanonicalName());
return types;
}
主要處理邏輯是下面這個方法:
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
/** 查找并解析注解 **/
Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);
for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingClass bindingClass = entry.getValue();
try {
/** 通過bindingClass寫進(jìn)文件,從而生成輔助類最筒。**/
JavaFileObject jfo = filer.createSourceFile(bindingClass.getFqcn(), typeElement);
Writer writer = jfo.openWriter();
writer.write(bindingClass.brewJava());
writer.flush();
writer.close();
} catch (IOException e) {
error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
e.getMessage());
}
}
return true;
}
來看findAndParseTargets()方法:
private Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingClass> targetClassMap = new LinkedHashMap<TypeElement, BindingClass>();
Set<String> erasedTargetNames = new LinkedHashSet<String>();
// Process each @Bind element.
/** 解析每個@Bind元素 **/
for (Element element : env.getElementsAnnotatedWith(Bind.class)) {
try {
parseBind(element, targetClassMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, Bind.class, e);
}
}
// Process each annotation that corresponds to a listener.
/** 解析每個監(jiān)聽器方法 **/
for (Class<? extends Annotation> listener : LISTENERS) {
findAndParseListener(env, listener, targetClassMap, erasedTargetNames);
}
// Process each @BindBool element.
/** 解析每個@BindBool元素 **/
for (Element element : env.getElementsAnnotatedWith(BindBool.class)) {
try {
parseResourceBool(element, targetClassMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindBool.class, e);
}
}
/** 解析@BindColor **/
/** 解析@BindDimen **/
// ……
}
我們來看parseBind()方法:
private void parseBind(Element element, Map<TypeElement, BindingClass> targetClassMap,
Set<String> erasedTargetNames) {
// Verify common generated code restrictions.
/**
* isInaccessibleViaGeneratedCode()中驗(yàn)證了:
* 1贺氓、修飾符不能為private或static;2床蜘、不能用于非Class類辙培;3、當(dāng)前類修飾符不能為private
*
* isBindingInWrongPackage()驗(yàn)證了邢锯,注解Class不能位于Android framework package或Java framework package扬蕊。
*/
if (isInaccessibleViaGeneratedCode(Bind.class, "fields", element)
|| isBindingInWrongPackage(Bind.class, element)) {
return;
}
TypeMirror elementType = element.asType();
if (elementType.getKind() == TypeKind.ARRAY) {
/** Array類型 **/
parseBindMany(element, targetClassMap, erasedTargetNames);
} else if (LIST_TYPE.equals(doubleErasure(elementType))) {
/** //list類型,@Bind({ R.id.consume_checkbox, R.id.expired_checkbox, R.id.latest_push_checkbox}) List<CheckedTextView> checkedTextViews; **/
parseBindMany(element, targetClassMap, erasedTargetNames);
} else if (isSubtypeOfType(elementType, ITERABLE_TYPE)) {
/** java.lang.Iterable<?>的子類型 **/
error(element, "@%s must be a List or array. (%s.%s)", Bind.class.getSimpleName(),
((TypeElement) element.getEnclosingElement()).getQualifiedName(),
element.getSimpleName());
} else {
/** 解析單個@Bind **/
parseBindOne(element, targetClassMap, erasedTargetNames);
}
}
我們先來看parseBindOne()方法:
private void parseBindOne(Element element, Map<TypeElement, BindingClass> targetClassMap,
Set<String> erasedTargetNames) {
boolean hasError = false;
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// Verify that the target type extends from View.
TypeMirror elementType = element.asType();
if (elementType.getKind() == TypeKind.TYPEVAR) {
TypeVariable typeVariable = (TypeVariable) elementType;
elementType = typeVariable.getUpperBound();
}
/** 必須為view類型的子類或者是接口 **/
if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
Bind.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName());
hasError = true;
}
// Assemble information on the field.
/** 只能有一個資源id丹擎。**/
int[] ids = element.getAnnotation(Bind.class).value();
if (ids.length != 1) {
error(element, "@%s for a view must only specify one ID. Found: %s. (%s.%s)",
Bind.class.getSimpleName(), Arrays.toString(ids), enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
if (hasError) {
return;
}
int id = ids[0];
BindingClass bindingClass = targetClassMap.get(enclosingElement);
if (bindingClass != null) {
ViewBindings viewBindings = bindingClass.getViewBinding(id);
if (viewBindings != null) {
Iterator<FieldViewBinding> iterator = viewBindings.getFieldBindings().iterator();
if (iterator.hasNext()) {/** 當(dāng)前資源id已經(jīng)綁定過 **/
FieldViewBinding existingBinding = iterator.next();
error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
Bind.class.getSimpleName(), id, existingBinding.getName(),
enclosingElement.getQualifiedName(), element.getSimpleName());
return;
}
}
} else {
/** 從緩存中獲取或創(chuàng)建BindingClass **/
bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
}
String name = element.getSimpleName().toString();
String type = elementType.toString();
boolean required = isRequiredBinding(element);
FieldViewBinding binding = new FieldViewBinding(name, type, required);
bindingClass.addField(id, binding);
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement.toString());
}
然后進(jìn)入getOrCreateTargetClass()方法:
private BindingClass getOrCreateTargetClass(Map<TypeElement, BindingClass> targetClassMap,
TypeElement enclosingElement) {
/** 從緩存中獲取 **/
BindingClass bindingClass = targetClassMap.get(enclosingElement);
if (bindingClass == null) {/** 為空尾抑,則創(chuàng)建 **/
String targetType = enclosingElement.getQualifiedName().toString();
String classPackage = getPackageName(enclosingElement);
/** 生成的輔助類名為:className + "$$ViewBinder" **/
String className = getClassName(enclosingElement, classPackage) + SUFFIX;
bindingClass = new BindingClass(classPackage, className, targetType);
/** 放入緩存 **/
targetClassMap.put(enclosingElement, bindingClass);
}
return bindingClass;
}
上面主要是解析了注解中的@Bind(其他流程類似),然后放到targetClassMap中蒂培。
一開始我們分析知道最終是通過BindingClass寫入文件再愈,生成輔助類的,那么我們接著來看BindingClass:
String brewJava() {
StringBuilder builder = new StringBuilder();
builder.append("http:// Generated code from Butter Knife. Do not modify!\n");
builder.append("package ").append(classPackage).append(";\n\n");
if (!resourceBindings.isEmpty()) {
builder.append("import android.content.res.Resources;\n");
}
if (!viewIdMap.isEmpty() || !collectionBindings.isEmpty()) {
builder.append("import android.view.View;\n");
}
builder.append("import butterknife.ButterKnife.Finder;\n");
if (parentViewBinder == null) {
builder.append("import butterknife.ButterKnife.ViewBinder;\n");
}
builder.append('\n');
builder.append("public class ").append(className);
builder.append("<T extends ").append(targetClass).append(">");
/** parentViewBinder不為空則繼承parentViewBinder护戳,為空則實(shí)現(xiàn)ViewBinder **/
if (parentViewBinder != null) {
builder.append(" extends ").append(parentViewBinder).append("<T>");
} else {
builder.append(" implements ViewBinder<T>");
}
builder.append(" {\n");
/** 生成綁定方法 **/
emitBindMethod(builder);
builder.append('\n');
/** 生成解綁方法 **/
emitUnbindMethod(builder);
builder.append("}\n");
return builder.toString();
}
這樣就生成了我們在第一篇中所看到的MainActivity$$ViewBinder.java類:
import android.view.View;
import butterknife.ButterKnife.Finder;
import butterknife.ButterKnife.ViewBinder;
public class MainActivity$$ViewBinder<T extends com.spirittalk.rxjavatraining.MainActivity> implements ViewBinder<T> {
@Override public void bind(final Finder finder, final T target, Object source) {
View view;
view = finder.findRequiredView(source, 2131492970, "field 'mButton' and method 'clickButton'");
target.mButton = finder.castView(view, 2131492970, "field 'mButton'");
view.setOnClickListener(
new butterknife.internal.DebouncingOnClickListener() {
@Override public void doClick(
android.view.View p0
) {
target.clickButton();
}
});
}
@Override public void unbind(T target) {
target.mButton = null;
}
}
轉(zhuǎn)載請標(biāo)明出處:http://www.reibang.com/p/4c38616af3a5