ButterKnife源码简析
<p>ButterKnife 是一个 Android 视图快速注入库,它通过给 View 字段添加注解,可以让我们丢掉 findViewById() 来获取 View 的方法,从而简化了代码。</p> <h2>编译时注解</h2> <h3>概述</h3> <p>编译时注解的核心依赖 APT ( Annotation Processing Tools ) 实现,原理是在某些代码元素上(如类型、函数、字段等)添加注解,在编译时编译器会检查 AbstractProcessor 的子类,并且调用该类型的 process 函数,然后将添加了注解的所有元素都传递到 process 函数中,使得开发人员可以在编译器进行相应的处理,例如,根据注解生成新的Java类,这也就是EventBus,Retrofit,Dragger等开源库的基本原理。</p> <h3>创建</h3> <p>创建一个 Java Library</p> <p>创建一个 annotationcompiler 的 java module</p> <p>配置 gradle</p> <pre> <code class="language-java">sourceCompatibility = "1.7" targetCompatibility = "1.7" </code></pre> <p>同时需要在 app 的 module 中配置一下 Java7</p> <pre> <code class="language-java">compileOptions { sourceCompatibility JavaVersion.VERSION_1_7 targetCompatibility JavaVersion.VERSION_1_7 } </code></pre> <p>创建 Annotation</p> <pre> <code class="language-java">package com.yydcdut.annotation; public @interface InjectView { int value(); } </code></pre> <p>创建 AbstractProcessor</p> <pre> <code class="language-java">package com.yydcdut.process; @SupportedAnnotationTypes("com.yydcdut.annotation.InjectView") public class ViewInjectProcessor extends AbstractProcessor { /** * 初始化 * * @param processingEnvironment 提供了一些实用的工具类Elements, Types和Filer */ @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); } /** * 指定使用的java版本 * * @return */ @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } /** * 指定哪些注解应该被注解处理器注册 * * @return */ @Override public Set<String> getSupportedAnnotationTypes() { return super.getSupportedAnnotationTypes(); } /** * 主要的逻辑处理 * * @param set * @param env * @return */ @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment env) { return false; } } </code></pre> <p>注册处理器</p> <p>在 java module 里的 main 目录中创建一个 resources 文件夹,然后下边在创建 META-INF/services ,创建一个 javax.annotation.processing.Processor 文件,在此文件中写入注解处理器的类全称</p> <pre> <code class="language-java">com.yydcdut.process.ViewInjectProcessor </code></pre> <p>添加 android-apt</p> <p>在 project 下的 build.gradle 中添加 apt 插件</p> <pre> <code class="language-java">classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' </code></pre> <p>然后在 app 中的 build.gradle 添加 apt 插件</p> <pre> <code class="language-java">apply plugin: 'com.android.application' apply plugin: 'com.neenbedankt.android-apt' </code></pre> <p>添加依赖</p> <p>在 app 中的 build.gradle 添加依赖</p> <pre> <code class="language-java">dependencies { compile project(':annotationcompiler') } </code></pre> <p>Code</p> <pre> <code class="language-java">@SupportedAnnotationTypes("com.yydcdut.annotation.InjectView") public class ViewInjectProcessor extends AbstractProcessor { @Override public Set<String> getSupportedAnnotationTypes() { Set<String> types = new LinkedHashSet<String>(); types.add(InjectView.class.getCanonicalName()); return types; } @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment env) { generateCode(set, env); return true; } private void generateCode(Set<? extends TypeElement> set, RoundEnvironment roundEnv) { StringBuilder builder = new StringBuilder() .append("package com.yydcdut.test.generated;\n\n") .append("public class GeneratedClass {\n\n") // open class .append("\tpublic String getMessage() {\n") // open method .append("\t\treturn \""); // for each javax.lang.model.element.Element annotated with the CustomAnnotation for (Element element : roundEnv.getElementsAnnotatedWith(InjectView.class)) { int id = element.getAnnotation(InjectView.class).value(); builder.append(id + "\n"); } builder.append("\";\n") // end return .append("\t}\n") // close method .append("}\n"); // close class try { // write the file JavaFileObject source = processingEnv.getFiler().createSourceFile("com.yydcdut.test.generated.GeneratedClass"); Writer writer = source.openWriter(); writer.write(builder.toString()); writer.flush(); writer.close(); } catch (IOException e) { // Note: calling e.printStackTrace() will print IO errors // that occur from the file already existing after its first run, this is normal e.printStackTrace(); } } } </code></pre> <p>被注解的部分代码:</p> <pre> <code class="language-java">package com.yydcdut.textdemo; public class MainActivity extends Activity { @InjectView(value = 123) private EditText mEditText; } </code></pre> <p>编译之后查看 app/build/generated/source/apt/debug/com/yydcdut/test/generated/GeneratedClass.java</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/06197d3569225380fbe77307679b75b3.png"></p> <h2>源码解析</h2> <h3>ButterKnifeProcessor</h3> <p>这里只分析 BindView 的过程</p> <p>ButterKnifeProcessor</p> <p>根据上述创建注解器的流程,那么我们分析源码也有了一个流程,就直接看 ButterKnife 的 AbstractProcessor :</p> <pre> <code class="language-java">@AutoService(Processor.class)//自动注册处理器 public final class ButterKnifeProcessor extends AbstractProcessor { 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 // ); private Elements elementUtils;//处理Element的工具类 private Types typeUtils;//处理TypeMirror的工具类 private Filer filer;//可以创建.java文件 @Override public synchronized void init(ProcessingEnvironment env) { super.init(env); //...... elementUtils = env.getElementUtils(); typeUtils = env.getTypeUtils(); filer = env.getFiler(); //...... } @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(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(BindInt.class); annotations.add(BindString.class); annotations.add(BindView.class); annotations.add(BindViews.class); annotations.addAll(LISTENERS); return annotations; } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) { Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);////查找所有的被注解,封装成BindingSet保存到map中 //遍历步map的生成.java文件 for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) { TypeElement typeElement = entry.getKey(); BindingSet binding = entry.getValue(); JavaFile javaFile = binding.brewJava(sdk); try { javaFile.writeTo(filer); } catch (IOException e) { error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage()); } } return false; } } </code></pre> <p>在 process 中主要做了两件事,第一是找出被注解的元素,封装成 Map<TypeElement, BindingSet> ,第一个参数可以想成类,第二个参数可以想成所有有关信息的封装;第二就是遍历这个 map ,然后针对每个类生成对应的 ViewBinder 类。</p> <p>ButterKnifeProcessor#findAndParseTargets</p> <p>那么主要的查找工作在 findAndParseTargets() 中进行:</p> <pre> <code class="language-java">public final class ButterKnifeProcessor extends AbstractProcessor { private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) { Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>(); Set<TypeElement> erasedTargetNames = new LinkedHashSet<>(); // ....... // Process each @BindView element. for (Element element : env.getElementsAnnotatedWith(BindView.class)) {//拿到被BindView注解的元素 // 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; } } </code></pre> <p>RoundEnvironment 可以理解成查询注解信息的类,而 Element 可以理解成程序中的元素,比如包、类、方法等等。</p> <pre> <code class="language-java">public class ClassA { // TypeElement private int var; // VariableElement public ClassA() {// ExecuteableElement } public void setA(int newA // TypeElement ) { } } </code></pre> <p>TypeElement 代表源代码中元素类型,但是 TypeElement 并不包含类的相关信息,可以从 TypeElement 获取类的名称,但不能获取类的信息,比如说父类。这些信息可以通过 TypeMirror 获取。你可以通过调用 element.asType() 来获取一个 Element 的 TypeMirror 。</p> <p>ButterKnifeProcessor#parseBindView</p> <pre> <code class="language-java">public final class ButterKnifeProcessor extends AbstractProcessor { private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) { TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();//类 com.yydcdut.textdemo.MainActivity // 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();//android.widget.EditText if (elementType.getKind() == TypeKind.TYPEVAR) {//false (elementType.getKind()==DECLARED) TypeVariable typeVariable = (TypeVariable) elementType; elementType = typeVariable.getUpperBound(); } Name qualifiedName = enclosingElement.getQualifiedName();//com.yydcdut.textdemo.MainActivity Name simpleName = element.getSimpleName();//mEditText if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {//必须为view类型的子类或者是接口 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. int id = element.getAnnotation(BindView.class).value();//得到id BindingSet.Builder builder = builderMap.get(enclosingElement); QualifiedId qualifiedId = elementToQualifiedId(element, id);//封装成QualifiedId 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);//为这个类创建BindingSet.Builder } String name = simpleName.toString();//mEditText TypeName type = TypeName.get(elementType);//android.widget.EditText boolean required = isFieldRequired(element); builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));//封装后放入builder中 // Add the type-erased version to the valid binding targets set. erasedTargetNames.add(enclosingElement); } /** * 判断修饰符等 * * @param annotationClass * @param targetThing * @param element * @return */ 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(); if (modifiers.contains(PRIVATE) || modifiers.contains(STATIC)) {//不能是private或者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) {//不能是class error(enclosingElement, "@%s %s may only be contained in classes. (%s.%s)", annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } // Verify containing class visibility is not private. if (enclosingElement.getModifiers().contains(PRIVATE)) {//不能是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; } /** * 判断是否弄错了包名 * 弄成android或者java的 * * @param annotationClass * @param element * @return */ private boolean isBindingInWrongPackage(Class<? extends Annotation> annotationClass, Element element) { TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); String qualifiedName = enclosingElement.getQualifiedName().toString();//com.yydcdut.textdemo.MainActivity if (qualifiedName.startsWith("android.")) { error(element, "@%s-annotated class incorrectly in Android framework package. (%s)", annotationClass.getSimpleName(), qualifiedName); return true; } if (qualifiedName.startsWith("java.")) { error(element, "@%s-annotated class incorrectly in Java framework package. (%s)", annotationClass.getSimpleName(), qualifiedName); return true; } return false; } /** * 是不是otherType的子类型 * * @param typeMirror * @param otherType * @return */ static boolean isSubtypeOfType(TypeMirror typeMirror, String otherType) { if (isTypeEqual(typeMirror, otherType)) { return true; } if (typeMirror.getKind() != TypeKind.DECLARED) { return false; } DeclaredType declaredType = (DeclaredType) typeMirror; List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments(); if (typeArguments.size() > 0) { StringBuilder typeString = new StringBuilder(declaredType.asElement().toString()); typeString.append('<'); for (int i = 0; i < typeArguments.size(); i++) { if (i > 0) { typeString.append(','); } typeString.append('?'); } typeString.append('>'); if (typeString.toString().equals(otherType)) { return true; } } Element element = declaredType.asElement(); if (!(element instanceof TypeElement)) { return false; } TypeElement typeElement = (TypeElement) element; TypeMirror superType = typeElement.getSuperclass(); if (isSubtypeOfType(superType, otherType)) { return true; } for (TypeMirror interfaceType : typeElement.getInterfaces()) { if (isSubtypeOfType(interfaceType, otherType)) { return true; } } return false; } /** * 是不是接口 * * @param typeMirror * @return */ private boolean isInterface(TypeMirror typeMirror) { return typeMirror instanceof DeclaredType && ((DeclaredType) typeMirror).asElement().getKind() == INTERFACE; } private QualifiedId elementToQualifiedId(Element element, int id) { return new QualifiedId(elementUtils.getPackageOf(element).getQualifiedName().toString(), id); } /** * 判断有没有被Nullable修饰 * * @param element * @return */ private static boolean isFieldRequired(Element element) { return !hasAnnotationWithName(element, NULLABLE_ANNOTATION_NAME); } 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; } } </code></pre> <p>这里是解析被 BindView 修饰的元素,将信息都封装成 BindingSet.Builder 。</p> <p>BindingSet#newBuilder</p> <pre> <code class="language-java">final class BindingSet { private final TypeName targetTypeName; private final ClassName bindingClassName; private final boolean isFinal; private final boolean isView; private final boolean isActivity; private final boolean isDialog; private final ImmutableList<ViewBinding> viewBindings; private final ImmutableList<FieldCollectionViewBinding> collectionBindings; private final ImmutableList<ResourceBinding> resourceBindings; private final BindingSet parentBinding; 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; } static Builder newBuilder(TypeElement enclosingElement) { TypeMirror typeMirror = enclosingElement.asType();//android.widget.EditText boolean isView = isSubtypeOfType(typeMirror, VIEW_TYPE);//是不是View boolean isActivity = isSubtypeOfType(typeMirror, ACTIVITY_TYPE);//是不是Activity boolean isDialog = isSubtypeOfType(typeMirror, DIALOG_TYPE);//是不是Dialog TypeName targetType = TypeName.get(typeMirror); if (targetType instanceof ParameterizedTypeName) { targetType = ((ParameterizedTypeName) targetType).rawType; } String packageName = getPackage(enclosingElement).getQualifiedName().toString();//com.yydcdut.textdemo String className = enclosingElement.getQualifiedName().toString().substring( packageName.length() + 1).replace('.', '$');//MainActivity ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");//com.yydcdut.textdemo.MainActivity_ViewBinding boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);//是不是final修饰的 return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog); } static final class Builder { private final TypeName targetTypeName; private final ClassName bindingClassName; private final boolean isFinal; private final boolean isView; private final boolean isActivity; private final boolean isDialog; private final Map<Id, ViewBinding.Builder> viewIdMap = new LinkedHashMap<>(); private Builder(TypeName targetTypeName, ClassName bindingClassName, boolean isFinal, boolean isView, boolean isActivity, boolean isDialog) { this.targetTypeName = targetTypeName; this.bindingClassName = bindingClassName; this.isFinal = isFinal; this.isView = isView; this.isActivity = isActivity; this.isDialog = isDialog; } void addField(Id id, FieldViewBinding binding) { getOrCreateViewBindings(id).setFieldBinding(binding); } private ViewBinding.Builder getOrCreateViewBindings(Id id) { ViewBinding.Builder viewId = viewIdMap.get(id); if (viewId == null) { viewId = new ViewBinding.Builder(id); viewIdMap.put(id, viewId); } return viewId; } BindingSet build() { ImmutableList.Builder<ViewBinding> viewBindings = ImmutableList.builder(); for (ViewBinding.Builder builder : viewIdMap.values()) { viewBindings.add(builder.build()); } return new BindingSet(targetTypeName, bindingClassName, isFinal, isView, isActivity, isDialog, viewBindings.build(), collectionBindings.build(), resourceBindings.build(), parentBinding); } } } </code></pre> <p>ButterKnifeProcessor#findAndParseTargets</p> <pre> <code class="language-java">public final class ButterKnifeProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) { Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>(); //.... //.... // 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();//com.yydcdut.textdemo.MainActivity BindingSet.Builder builder = entry.getValue(); TypeElement parentType = findParentType(type, erasedTargetNames);//父类 if (parentType == null) {//自己就是了 bindingMap.put(type, builder.build()); } else { BindingSet parentBinding = bindingMap.get(parentType);//拿到父类的BindingSet 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; } /** * Finds the parent binder type in the supplied set, if any. */ private TypeElement findParentType(TypeElement typeElement, Set<TypeElement> parents) { TypeMirror type; while (true) { type = typeElement.getSuperclass(); if (type.getKind() == TypeKind.NONE) { return null; } typeElement = (TypeElement) ((DeclaredType) type).asElement(); if (parents.contains(typeElement)) { return typeElement; } } } } </code></pre> <p>ButterKnifeProcessor#process</p> <p>findAndParseTargets 算是解析完了,再回过头来看第二步,生成文件的步骤</p> <pre> <code class="language-java">public final class ButterKnifeProcessor extends AbstractProcessor { @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);//变成JavaFile try { javaFile.writeTo(filer);//生成文件 } catch (IOException e) { error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage()); } } return false; } } </code></pre> <p>将信息转变成 JavaFile ,通过 filer 完成。</p> <p>write过程</p> <pre> <code class="language-java">final class BindingSet { JavaFile brewJava(int sdk) { return JavaFile.builder(bindingClassName.packageName(), createType(sdk))//packageName-->com.yydcdut.textdemo .addFileComment("Generated code from Butter Knife. Do not modify!") .build(); } private TypeSpec createType(int sdk) { TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName()) .addModifiers(PUBLIC);//申明类 if (isFinal) { result.addModifiers(FINAL);//添加final关键字 } if (parentBinding != null) { result.superclass(parentBinding.bindingClassName);//继承父类 } else { result.addSuperinterface(UNBINDER);//实现Unbinder接口 } if (hasTargetField()) { result.addField(targetTypeName, "target", PRIVATE);//设置private的target变量 } 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)); if (hasViewBindings() || parentBinding == null) { result.addMethod(createBindingUnbindMethod(result)); } return result.build(); } private MethodSpec createBindingConstructorForView() { MethodSpec.Builder builder = MethodSpec.constructorBuilder()//创建构造器 .addAnnotation(UI_THREAD)//添加UIThread的注解 .addModifiers(PUBLIC)//为public .addParameter(targetTypeName, "target");//构造函数中得有target参数 if (constructorNeedsView()) {//构造函数中的方法 builder.addStatement("this(target, target)"); } else { builder.addStatement("this(target, target.getContext())"); } return builder.build(); } /** * True if this binding requires a view. Otherwise only a context is needed. */ private boolean constructorNeedsView() { return hasViewBindings() // || parentBinding != null && parentBinding.constructorNeedsView(); } private MethodSpec createBindingViewDelegateConstructor() { return MethodSpec.constructorBuilder() .addJavadoc("@deprecated Use {@link #$T($T, $T)} for direct creation.\n " + "Only present for runtime invocation through {@code ButterKnife.bind()}.\n", bindingClassName, targetTypeName, CONTEXT) .addAnnotation(Deprecated.class) .addAnnotation(UI_THREAD) .addModifiers(PUBLIC) .addParameter(targetTypeName, "target") .addParameter(VIEW, "source") .addStatement(("this(target, source.getContext())")) .build(); } private MethodSpec createBindingConstructor(int sdk) { MethodSpec.Builder constructor = MethodSpec.constructorBuilder()//构造方法 .addAnnotation(UI_THREAD)//UIThread注解 .addModifiers(PUBLIC);//public if (hasMethodBindings()) { constructor.addParameter(targetTypeName, "target", FINAL);//final的target变量 } else { constructor.addParameter(targetTypeName, "target"); } if (constructorNeedsView()) { constructor.addParameter(VIEW, "source");//变量名为source的View } else { constructor.addParameter(CONTEXT, "context");//变量名为context的Context } if (hasUnqualifiedResourceBindings()) { // Aapt can change IDs out from underneath us, just suppress since all will work at runtime. constructor.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class) .addMember("value", "$S", "ResourceType") .build()); } if (hasOnTouchMethodBindings()) { constructor.addAnnotation(AnnotationSpec.builder(SUPPRESS_LINT) .addMember("value", "$S", "ClickableViewAccessibility") .build()); } if (parentBinding != null) {//如果有父类,则调用super if (parentBinding.constructorNeedsView()) { constructor.addStatement("super(target, source)"); } else if (constructorNeedsView()) { constructor.addStatement("super(target, source.getContext())"); } else { constructor.addStatement("super(target, context)"); } constructor.addCode("\n"); } if (hasTargetField()) { constructor.addStatement("this.target = target");//赋值 constructor.addCode("\n"); } if (hasViewBindings()) { if (hasViewLocal()) { // Local variable in which all views will be temporarily stored. constructor.addStatement("$T view", VIEW); } for (ViewBinding binding : viewBindings) { addViewBinding(constructor, binding);//View的初始化 } for (FieldCollectionViewBinding binding : collectionBindings) { constructor.addStatement("$L", binding.render());//资源的初始化 } if (!resourceBindings.isEmpty()) { constructor.addCode("\n"); } } if (!resourceBindings.isEmpty()) { if (constructorNeedsView()) { constructor.addStatement("$T context = source.getContext()", CONTEXT);//赋值 } if (hasResourceBindingsNeedingResource(sdk)) { constructor.addStatement("$T res = context.getResources()", RESOURCES);//赋值 } for (ResourceBinding binding : resourceBindings) { constructor.addStatement("$L", binding.render(sdk));//通过不同的SDK版本实现Resource相关(Drawable,Color等)的初始化 } } return constructor.build(); } private void addViewBinding(MethodSpec.Builder result, ViewBinding binding) { if (binding.isSingleFieldBinding()) {//只有findView的情况,没有监听器 // Optimize the common case where there's a single binding directly to a field. FieldViewBinding fieldBinding = binding.getFieldBinding(); CodeBlock.Builder builder = CodeBlock.builder() .add("target.$L = ", fieldBinding.getName());//(target-->MainActivity) ($L-->mEditText) boolean requiresCast = requiresCast(fieldBinding.getType());//是否需要强转 if (!requiresCast && !fieldBinding.isRequired()) { builder.add("source.findViewById($L)", binding.getId().code);//findViewById } else { builder.add("$T.find", UTILS); builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView"); if (requiresCast) { builder.add("AsType"); } builder.add("(source, $L", binding.getId().code); if (fieldBinding.isRequired() || requiresCast) { builder.add(", $S", asHumanDescription(singletonList(fieldBinding))); } if (requiresCast) { builder.add(", $T.class", fieldBinding.getRawType()); } builder.add(")");//target.mEditText = Utils.findRequiredViewAsType(source, R.id.edit, "field 'mEditText'", EditText.class); } result.addStatement("$L", builder.build()); return; } List<MemberViewBinding> requiredBindings = binding.getRequiredBindings(); if (requiredBindings.isEmpty()) { result.addStatement("view = source.findViewById($L)", binding.getId().code); } else if (!binding.isBoundToRoot()) { result.addStatement("view = $T.findRequiredView(source, $L, $S)", UTILS, binding.getId().code, asHumanDescription(requiredBindings));//view = Utils.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', and method 'sayGetOffMe'"); } addFieldBinding(result, binding);//强转 addMethodBindings(result, binding);//各种监听器 } private void addFieldBinding(MethodSpec.Builder result, ViewBinding binding) { FieldViewBinding fieldBinding = binding.getFieldBinding(); if (fieldBinding != null) { if (requiresCast(fieldBinding.getType())) { result.addStatement("target.$L = $T.castView(view, $L, $S, $T.class)", fieldBinding.getName(), UTILS, binding.getId().code, asHumanDescription(singletonList(fieldBinding)), fieldBinding.getRawType());// target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class); } else { result.addStatement("target.$L = view", fieldBinding.getName()); } } } } </code></pre> <p>最后文件的生成是通过 com.squareup.javapoet.JavaFile 生成的。</p> <p>官方 demo 的栗子</p> <pre> <code class="language-java">package com.example.butterknife.library; import android.app.Activity; import android.os.Bundle; import android.support.annotation.NonNull; import android.view.View; import android.view.animation.AlphaAnimation; import android.widget.Button; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import java.util.List; import butterknife.BindView; import butterknife.BindViews; import butterknife.ButterKnife; import butterknife.OnClick; import butterknife.OnItemClick; import butterknife.OnLongClick; import static android.widget.Toast.LENGTH_SHORT; public class SimpleActivity extends Activity { private static final ButterKnife.Action<View> ALPHA_FADE = new ButterKnife.Action<View>() { @Override public void apply(@NonNull View view, int index) { AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1); alphaAnimation.setFillBefore(true); alphaAnimation.setDuration(500); alphaAnimation.setStartOffset(index * 100); view.startAnimation(alphaAnimation); } }; @BindView(R2.id.title) TextView title; @BindView(R2.id.subtitle) TextView subtitle; @BindView(R2.id.hello) Button hello; @BindView(R2.id.list_of_things) ListView listOfThings; @BindView(R2.id.footer) TextView footer; @BindViews({R2.id.title, R2.id.subtitle, R2.id.hello}) List<View> headerViews; private SimpleAdapter adapter; @OnClick(R2.id.hello) void sayHello() { Toast.makeText(this, "Hello, views!", LENGTH_SHORT).show(); ButterKnife.apply(headerViews, ALPHA_FADE); } @OnLongClick(R2.id.hello) boolean sayGetOffMe() { Toast.makeText(this, "Let go of me!", LENGTH_SHORT).show(); return true; } @OnItemClick(R2.id.list_of_things) void onItemClick(int position) { Toast.makeText(this, "You clicked: " + adapter.getItem(position), LENGTH_SHORT).show(); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); ButterKnife.bind(this); // Contrived code to use the bound fields. title.setText("Butter Knife"); subtitle.setText("Field and method binding for Android views."); footer.setText("by Jake Wharton"); hello.setText("Say Hello"); adapter = new SimpleAdapter(this); listOfThings.setAdapter(adapter); } } </code></pre> <p>官方 demo 的 apt 生成类的栗子</p> <pre> <code class="language-java">// Generated code from Butter Knife. Do not modify! package com.example.butterknife.library; 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 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.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; } } </code></pre> <h3>ButterKnife</h3> <p>当要进行 bind 的时候,都需要在 Activity 或 View 等的初始化函数中进行绑定,这里就拿 Activity 为栗子进行分析:</p> <pre> <code class="language-java">public final class ButterKnife { /** * BindView annotated fields and methods in the specified {@link Activity}. The current content * view is used as the view root. * * @param target Target activity for view binding. */ @NonNull @UiThread public static Unbinder bind(@NonNull Activity target) { 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()); } 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); } } @Nullable @CheckResult @UiThread private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) { Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);//判断绑定过了没有 if (bindingCtor != null) { if (debug) { Log.d(TAG, "HIT: Cached in binding map."); } return bindingCtor; } String clsName = cls.getName(); if (clsName.startsWith("android.") || clsName.startsWith("java.")) {//排除android和java的包 if (debug) { Log.d(TAG, "MISS: Reached framework class. Abandoning search."); } return null; } try { Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");//加载后面加"_ViewBinding"的累 //noinspection unchecked bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);//初始化 if (debug) { Log.d(TAG, "HIT: Loaded binding class and constructor."); } } catch (ClassNotFoundException e) { if (debug) { Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName()); } bindingCtor = findBindingConstructorForClass(cls.getSuperclass()); } catch (NoSuchMethodException e) { throw new RuntimeException("Unable to find binding constructor for " + clsName, e); } BINDINGS.put(cls, bindingCtor); return bindingCtor; } } </code></pre> <p>最终通过 ClassLoader 的方式将类加载出来,最后 constructor.newInstance 方法来调用该类的构造函数。</p> <h2>总结</h2> <p>平时了解到更多的是运行时注解,即声明注解的生命周期为 RUNTIME ,然后在运行的时候通过反射完成注入,这种方式虽然简单,但是设计到比较多的反射,必然多多少少会有性能的损耗。而 ButterKnife 用的 APT 编译时解析技术,比较好的解决了反射这些问题。</p> <p>APT 大概就是声明的注解的生命周期为 CLASS ,然后继承 AbstractProcessor 类。继承这个类后,在编译的时候,编译器会扫描所有带有你要处理的注解的类,然后再调用 AbstractProcessor 的 process 方法,对注解进行处理,那么就可以在处理的时候,动态生成绑定事件或者控件的 java 代码,然后在运行的时候,直接调用 bind 方法完成绑定。</p> <p>其实这种方式的好处是我们不用再一遍一遍地写 findViewById 和 onClick 等代码了,这个框架在编译的时候帮我们自动生成了这些代码,然后在运行的时候调用就行了。</p> <p> </p> <p>来自:http://yydcdut.com/2017/04/19/butterknife-analyse/</p> <p> </p>
本文由用户 Ari8006 自行上传分享,仅供网友学习交流。所有权归原作者,若您的权利被侵害,请联系管理员。
转载本站原创文章,请注明出处,并保留原始链接、图片水印。
本站是一个以用户分享为主的开源技术平台,欢迎各类分享!