编译时注解Butterknife源码解析之深入篇(雷惊风)

    xiaoxiao2021-03-25  128

    1.概述。

    上篇文章我对Butterknife实现做了一些基础的说明,本篇文章我将向大家详细分析@BindView、@OnClick解析流程、生成BindingSet对应Java文件流程及我们调用ButterKnife.bind(this)后ButterKnife与生成Java文件的建立连接过程。

    2.@BindView解析流程。

    这篇文章将接着上篇文章的findAndParseTargets(RoundEnvironment env)方法,如果你看了上篇文章,那么你就知道,所有Butterknife中注解的解析都是在这个方法中的。好的那么我们开始吧。我们这个方法的流程上篇文章已经说过了,这里就不再浪费口水了,我们把解析@BindView的代码拷出来再看一下:

    // Process each @BindView element. /** * 处理BindView(R.id.btn)注解; * 1.做了一系列验证;如:private static修饰符判断、@BindView必须作用在View上或者interface上等等; * 2.id的处理;生成QualifiedId、缓存等; * 3.builder创建;一个id是否注解了多次 * 4.@Nullable处理,创建FieldViewBinding存入builder,将enclosingElement存入erasedTargetNames; */ 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); } }

     很简单,获取了项目中所有的被@BindView注解了的Element,通过for()循环处理每一个,一个try{}catch{}代码块,try中出问题,打印信息,关于在注解处理器中处理错误log信息,在这里就不讲了,可以在init()方法中获取Messager辅助类。看来重点在parseBindView(element, builderMap, erasedTargetNames);方法中了,跟进去:

    parseBindView(element, builderMap, erasedTargetNames);方法中了,跟进去: private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) { //获取父级Element; 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(); //@BindView必须作用在View上或者interface上; 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. int id = element.getAnnotation(BindView.class).value(); //通过enclosingElement获取builder,每一个builder对应一个类,如activity; BindingSet.Builder builder = builderMap.get(enclosingElement); //将element所在包与id封装到QualifiedId中; QualifiedId qualifiedId = elementToQualifiedId(element, id); if (builder != null) { //判断当前@BindView所修饰控件是否已经绑定过; //getId():将id存入Id对象,并存入symbols; 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; builder = getOrCreateBindingBuilder(builderMap, enclosingElement); } String name = simpleName.toString(); TypeName type = TypeName.get(elementType); //判断是否添加了@Nullable boolean required = isFieldRequired(element); //通过Id创建ViewBinding.Builder并setFieldBinding(fieldViewBinding) builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required)); // Add the type-erased version to the valid binding targets set. erasedTargetNames.add(enclosingElement); }

    方法也还好,不到100行,来看看在这个方法中做了一些什么操作吧。咱们先拿出一部分来看:

    //获取父级Element; 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(); //@BindView必须作用在View上或者interface上; 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; }

    这部分是对我们@BindView注解应用的一个正确性的一个检查,首先获取了我们的element对应的外部类的TypeElement,比如,activity中用@BindView注解了一个Button btn;获取了activity对应的TypeElement,如果你看了上篇文章你就会明白。然后调用了一个isInaccessibleViaGeneratedCode(BindView.class,"fields", element)方法与一个isBindingInWrongPackage(BindView.class, element)方法进行判断操作。一个一个来看一下,第一个:

    /** * 检查annotation作用域是否正确; * * @param annotationClass * @param targetThing * @param element * @return */ private boolean isInaccessibleViaGeneratedCode(Class<? extends Annotation> annotationClass, String targetThing, Element element) { boolean hasError = false; //获取当前element所在类的TypeElement; TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // Verify method modifiers.获取当前element的修饰符; Set<Modifier> modifiers = element.getModifiers(); //修饰符不能是private或者static的,否则报告异常(error方法); // Messager提供给注解处理器一个报告错误、警告以及提示信息的途径。 // 它不是注解处理器开发者的日志工具,而是用来写一些信息给使用此注解器的第三方开发者的。 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.element只能直接从属于类(不能修饰局部变量);否则报错; 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; } // Verify containing class visibility is not private.element外层类不能是私有的,否则报错; 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; }

    在这个类中判断了三种非正常情况:

    1.@BindView注解的Element为private或者static修饰报错,如下例子:

    @BindView(R.id.btn) private Button btn;

    这种情况下就会报错。

    2.当前Element不是直接在一个类里边,报错。

       如:在一个方法的局部变量上添加了注解。

    3.外部Elementprivate的报错。如:

    private class Activity extends ...{ @BindView(R.id.btn) Button btn; }

    再看一下isBindingInWrongPackage(BindView.class, element)方法:

    /** * 检查anitation注解是否作用在了系统类上; * * @param annotationClass * @param element * @return */ 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; }

     这个方法主要是检查我们是否用到了系统类上,这里注意我们自己定义的包名。接着回到上一个方法向下看,后续又检查了是否用在了View子类上或者Interface上。我都加了注释,不在详细解释。如果上边检查有一步出问题,则return终止。到这里,检查我们应用@BindView合法性就完了。再往下边走:

    // Assemble information on the field. int id = element.getAnnotation(BindView.class).value(); //通过enclosingElement获取builder,每一个builder对应一个类,如activity; BindingSet.Builder builder = builderMap.get(enclosingElement); //将element所在包与id封装到QualifiedId中; QualifiedId qualifiedId = elementToQualifiedId(element, id); if (builder != null) { //判断当前@BindView所修饰控件是否已经绑定过; //getId():将id存入Id对象,并存入symbols; 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; builder = getOrCreateBindingBuilder(builderMap, enclosingElement); }

    首先获取到我们注解中指定的id,如下代码中的R.id.btn:

    @BindView(R.id.btn) Button btn;

    查看builderMap中是否已经缓存了外部Element对应的BindingSet.Builder,通过id创建QualifiedId看一下这个过程:

    private QualifiedId elementToQualifiedId(Element element, int id) { return new QualifiedId(elementUtils.getPackageOf(element).getQualifiedName().toString(), id); }

    这里用到了辅助类elementUtils获取element的包名,通过包名与id创建了QualifiedId

    后边是builder判断,如果不为空,通过QualifiedId生成Id判断是否注解过相同id,注解过,则输出错误信息。Builder为空,调用getOrCreateBindingBuilder(builderMap, enclosingElement)创建或获取builder,看一下:

    private BindingSet.Builder getOrCreateBindingBuilder( Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) { BindingSet.Builder builder = builderMap.get(enclosingElement); if (builder == null) { //生成一个builder, // builder中保存了泛型信息、将要生成的类名称、是否Final修饰、是否View内部,是否Activity内部、是否Dialog内部; builder = BindingSet.newBuilder(enclosingElement); builderMap.put(enclosingElement, builder); } return builder; }

    如果我们的builderMap中已经存在,直接返回,不存在调用BindingSet.newBuilder()创建并保存到builderMap中。创建保存这步是关键,看一下:

    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); //参数:泛型、将要生成的类名称、是否Final修饰、是否View内部,是否Activity内部、是否Dialog内部; return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog); }

    首先判断了我们外部类Element是不是View是不是Activity是不是Dialog,进行了泛型判断处理,通过包名与类名得到我们将要创建的Java类的名称,有没有final修饰,然后通过以上判断的结果生成Builder对象返回。这样我们就创建了一个BindingSet.Builder对象,其中包含是不是ViewactivityDialog,将来要生成的Java类名,有没有final修饰,泛型信息。再次回到parseBindView()方法中看剩余部分:

    String name = simpleName.toString(); TypeName type = TypeName.get(elementType); //判断是否添加了@Nullable boolean required = isFieldRequired(element); //通过Id创建ViewBinding.Builder并setFieldBinding(fieldViewBinding) builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required)); // Add the type-erased version to the valid binding targets set. erasedTargetNames.add(enclosingElement);

    获取注解的element的name(上边例子中的btn)与type(上边例子中的Button),判断了是否添加了@Nullable注解,通过这三个内容生成FieldViewBinding(与MethodViewBinding都继承了MemberViewBinding,封装一个最基础的@BindView注解的element对象,)对象,调用builder的addField()方法将Id对象与FieldViewBinding对象传入,进入BindingSet的Buidler看一下:

    void addField(Id id, FieldViewBinding binding) { getOrCreateViewBindings(id).setFieldBinding(binding); } 又调用了getOrCreateViewBindings()方法,跟: private ViewBinding.Builder getOrCreateViewBindings(Id id) { ViewBinding.Builder viewId = viewIdMap.get(id); //创建针对当前id的ViewBinding.Builder; if (viewId == null) { viewId = new ViewBinding.Builder(id); viewIdMap.put(id, viewId); } return viewId; }

       通过查看缓存中是否已经保存了当前id对应的ViewBinding.Builder有返回,没有创建并缓存。在这里通过id创建了ViewBinding.Builder对象。回到addField()中,调用创建好的ViewBinding.Builder对象的setFieldBingding()方法将FieldViewBinding对象保存。这里的层次你得搞清楚了。FieldViewBinding保存在ViewBinding.Builder对象中,ViewBinding.Builder对象是在BindingSet.Builder对象调用addField()方法时创建的。回到parseBindView()方法,还剩一句代码,就是将父类的elementType保存到一开始的erasedTargetNames中。好,我们的parseBindView()方法就分析完了。同样,我们解析@BindView注解逻辑也就分析完了,你弄明白了吗,不明白也没关系,我做一下总结:

    1.首先就是一系列判断我们应用@BindView注解的正确性;

    2.然后获取id,获取外部类ElementType对应的BindingSet.Builder对象是否存在,存在判断当前id是否已经注解过。不存在,创建。创建的过程中获取了类是否为View、activity、Dialog,是否final,泛型,将生成的类名等。保存到builderMap中。

    3.调用BindingSet.Builder对象的addField()方法,过程中创建了与之对应的FieldViewBinding对象、ViewBinding.Buidler对象,并将部分信息进行缓存。

    4.将外部类ElementType保存到erasedTargetNames中。

    这就是解析@ViewBind注解的整个过程。

       下边说一下个人的理解,如果一个类(如Activity)里边有@ViewBind注解的控件,那么就会对应这个类生成一个BindingSet.Builder,每一个@ViewBind注解的id会对应生成一个ViewBinding.Builder对象,一个类里边的所有生成的ViewBinding.Builder对象会根据id保存到BindingSet中的viewIdMap中。ViewBinding.Builder中保存了自己的FieldViewBinding对象。最终就是把生成的ViewBinding.Builder对象与外部类的ElementType对应着保存到了builderMap中,将外部类的ElementType保存到erasedTargetNames中。好了,关于@ViewBind的解析就分析到这里。

    3.@OnClick注解解析过程分析

    // Process each annotation that corresponds to a listener. for (Class<? extends Annotation> listener : LISTENERS) { findAndParseListener(env, listener, builderMap, erasedTargetNames); }

    下边看一下ButterKnife的事件处理流程,通过上边的代码可以看出来,所有的事件注解处理流程都是一样的,看一下for循环中方法,这里传入的是辅助对象env,当前要处理的事件注解类listener,还有就是builderMap,erasedTargetNames,我们进入findAndParseListener()方法,看一下:

    /** * 传入一种事件注解进行处理; * * @param env * @param annotationClass * @param builderMap * @param erasedTargetNames */ private void findAndParseListener(RoundEnvironment env, Class<? extends Annotation> annotationClass, Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) { //获取被某个事件注解的每一个element; for (Element element : env.getElementsAnnotatedWith(annotationClass)) { //google封装的方法验证element合法性; if (!SuperficialValidation.validateElement(element)) continue; try { //anotationClass是@@OnClickListener;element是被其注解的方法类型的element; 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(annotationClass, element, builderMap, erasedTargetNames);这个方法比较长,这里就不贴了,首先还是判断合法性,这部分代码不太重要。

    //反射方式获取id数组; int[] ids = (int[]) annotationValue.invoke(annotation); ...//省略部分合法行应用代码; //生成类里边重写父类的方法;如@OnClick中的doClick(); 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) { //@OnClick等都是一个; 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处理;@OnItemSelected、@OnPageChange、@OnTextChanged Method annotationCallback = annotationClass.getDeclaredMethod("callback"); Enum<?> callback = (Enum<?>) annotationCallback.invoke(annotation); Field callbackField = callback.getDeclaringClass().getField(callback.name()); method = callbackField.getAnnotation(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())); } }

    上边代码主要是获取了在定义事件注解时定义的方法信息,这里看一下我们的@OnClick注解是怎么定义的:

    @Target(METHOD) @Retention(CLASS) @ListenerClass( targetType = "android.view.View", setter = "setOnClickListener", type = "butterknife.internal.DebouncingOnClickListener", method = @ListenerMethod( name = "doClick", parameters = "android.view.View" ) ) public @interface OnClick { /** View IDs to which the method will be bound. */ @IdRes int[] value() default { View.NO_ID }; }

    以上代码主要是获取的这个method就是@OnClick注解类中的@ListenerClass 中的@ListenerMethod中的doClick。接着去代码里找关键代码:

    /** * 被注解事件注解的方法参数与jack在注解事件中定义的方法参数做匹配处理;如simple中的onItemClick()参数,如下代码; * @OnItemClick(R2.id.list_of_things) * void onItemClick(int position) { * Toast.makeText(this, "You clicked: " + adapter.getItem(position), LENGTH_SHORT).show(); * } */ Parameter[] parameters = Parameter.NONE; //我们的方法中参数不为空,开始处理; if (!methodParameters.isEmpty()) { parameters = new Parameter[methodParameters.size()]; //BitSet常见的应用是那些需要对海量数据进行一些统计工作的时候,比如日志分析等 BitSet methodParameterUsed = new BitSet(methodParameters.size()); //parameterTypes为jack注解中定义的参数;methodParameters为我们的参数 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; } } //参数匹配错误处理; ...//这里省略掉错误处理 } }

    以上代码主要是获取我们在用@OnClick等注解时方法的参数与定义@OnClick注解时规定参数的一个对应处理,最终保存到parameters中,已经添加注释,不过多解释,接着往下看:

    //public void onClick(){};name相当于onClick; MethodViewBinding binding = new MethodViewBinding(name, Arrays.asList(parameters), required); //获取注解外部类对应的Builder对象;与@BindView中一样; BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement); for (int id : ids) { QualifiedId qualifiedId = elementToQualifiedId(element, id); /** * 是否添加到了对应的ViewBinding.Builder中,没有创建,添加; * * 参数:Id:通过QualifiedId生成的Id对象; * listener:注解中的@ListenerClass注解信息 * method:生成类里边重新父类的方法;如@OnClick中的doClick(); * binding:生成的包含name/参数/判断有无@Optional 注解的 *MethodViewBinding对象; */ 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);

    创建了MethodViewBinding对象,保存name、参数及是否有@Optional注解。调用了在@BindView中也用到的getOrCreateBindingBuilder(builderMap, enclosingElement); 方法获取BindingSet.Builder对象;循环每一个id创建对应的QualifiedId对象,调用builder.addMethod()方法,这里参数已经在代码中注释,看一下这个方法:

    boolean addMethod( Id id, ListenerClass listener, ListenerMethod method, MethodViewBinding binding) { ViewBinding.Builder viewBinding = getOrCreateViewBindings(id); //ViewBinding.Builder 中的methodBindings中是否已经有了对应的方法; if (viewBinding.hasMethodBinding(listener, method) && !"void".equals(method.returnType())) { return false; } viewBinding.addMethodBinding(listener, method, binding); return true; }

    同样也调用了getOrCreateViewBindings(id)方法获取ViewBinding.Builder;最后将listener等信息传入viewBinding.addMethodBinding()方法。进入ViewBinding看一下: public void addMethodBinding(ListenerClass listener, ListenerMethod method, MethodViewBinding binding) { //methods为一种事件方法;如:OnPageChangeListener、OnTextChangeListener中的一种 Map<ListenerMethod, Set<MethodViewBinding>> methods = methodBindings.get(listener); //set为每一种事件下边的多个小事件;如acitivity中可能会注解 // @OnTextChanged(value = R.id.mEditV,callback =BEFORE_TEXT_CHANGED); //@OnTextChanged(value = R.id.mEditV,callback = TEXT_CHANGED); //@OnTextChanged(value = R.id.mEditV,callback = AFTER_TEXT_CHANGED); // 每一次是一个; Set<MethodViewBinding> set = null; if (methods == null) { methods = new LinkedHashMap<>(); methodBindings.put(listener, methods); } else { set = methods.get(method); } if (set == null) { set = new LinkedHashSet<>(); methods.put(method, set); } set.add(binding); }

    里边也有缓存,有注释,就不说了,有了前边对@BindView的了解,这个就省不少事了,说白了,还是在getOrCreateBindingBuilder(builderMap, enclosingElement)这个方法中保存builderMap,最后保存erasedTargetNames.add(enclosingElement);这里有不理解的可以下载加好注释的源码多看几遍,最后会有下载地址。

    到现在为止,我们关于注解解析成BindingSet.Builder对象就完了。回到我们解析事件注解的开始ButterKnifeProcessor的findAndParseTargets(RoundEnvironment env)方法,

    看剩余部分,也就是将BindingSet.Builder对象生成BindingSet的过程。看代码吧:

    // 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). //builderMap===(EnclosingElement,BindingSet.Builder) //父类的处理,生成BindingSet存入bindingMap返回; 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(); //(EnclosingElement,BindingSet.Builder) TypeElement type = entry.getKey(); BindingSet.Builder builder = entry.getValue(); //erasedTargetNames里是否保存了type的父类,有直接返回,没有返回null; TypeElement parentType = findParentType(type, erasedTargetNames); // erasedTargetNames里没有保存type父类; if (parentType == null) { bindingMap.put(type, builder.build()); } else { //erasedTargetNames保存了type父类; 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.put(type, builder.build())这句,builder.Build()方法中创建了BindingSet对象,进去看看:

    BindingSet build() { ImmutableList.Builder<ViewBinding> viewBindings = ImmutableList.builder(); //viewIdMap在addMethod、addField是添加数据;每个id对应的builder; for (ViewBinding.Builder builder : viewIdMap.values()) { //每一个id生成ViewBinding(id, methodBindings, fieldBinding)保存到ViewBindings中; viewBindings.add(builder.build()); } return new BindingSet(targetTypeName, bindingClassName, isFinal, isView, isActivity, isDialog, viewBindings.build(), collectionBindings.build(), resourceBindings.build(), parentBinding); } }

    这里重点是通过我们之前生成的所有信息new出我们的BindingSet对象返回。最终存到我们ButterKnifeProcessor中findAndParseTargets(RoundEnvironment env)方法的bindingMap对象中,再将bindingMap返回到最初的process(Set<?extends TypeElement> elements, RoundEnvironment env)方法中,进行后续处理,那就让我们看看吧:

    @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) { //注解的一系列处理,最终返回bindingMap; 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); try { javaFile.writeTo(filer); } catch (IOException e) { error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage()); } } return false; }

    可以看到后边就是循环取出bindingMap中的每一个,调用binding.brewJava(sdk)方法,那先让咱们看看这个方法:

    /** * 创建JavaFile; * @param sdk * @return */ JavaFile brewJava(int sdk) { //包名,及要生成的文件内容; return JavaFile.builder(bindingClassName.packageName(), createType(sdk)) //类上边的注释; .addFileComment("Generated code from Butter Knife. Do not modify!") .build(); }

    最终调用了JavaFilebuilder()方法,JavaFileJavapoet包里边的类,就不说了。看一下它的builder方法的参数,第一个看方法也知道包名,第二个是啥?看看:

    /** * 创建绑定类; * @param sdk * @return */ private TypeSpec createType(int sdk) { TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName()) .addModifiers(PUBLIC); if (isFinal) { result.addModifiers(FINAL); } //如果有parentBinding则不继承Unbinder; if (parentBinding != null) { result.superclass(parentBinding.bindingClassName); } else { result.addSuperinterface(UNBINDER); } //注解了成员变量或者方法添加target成员变量;对应例子中---》private SimpleActivity target;; if (hasTargetField()) { result.addField(targetTypeName, "target", PRIVATE); } //根据不同情况添加单参数构造函数; 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)); //生成unbind()方法; if (hasViewBindings() || parentBinding == null) { result.addMethod(createBindingUnbindMethod(result)); } return result.build(); }

    噢,my god,都是Javapoet中的东西,没错,这里就是文件内容操作了,这里我说几个里边用到的类方法吧。详细每一行代码的意思就不说了:

    TypeSpec.classBuilder-------创建类相关;

    .addModifiers(PUBLIC)-------添加修饰符;

    Result.superclass-----------设置父类;

    result.addSuperinterface----添加接口;

    result.addField-------------添加成员变量;

    result.addMethod------------类添加方法或构造函数;

    MethodSpec.constructorBuilder()---构造函数相关;

    constructor.addParameter----构造函数添加参数;

    constructor.addAnnotation---构造函数添加注解;

    .addStatement---------------添加代码;

    .......等等吧。

    关键的生成代码是这句:

    //生成最终绑定的构造函数; result.addMethod(createBindingConstructor(sdk));

    所有的绑定代码,事件处理代码生成都是在这句里边,代码就不贴了啊,篇幅有点大了,想看的可以下载我标好注释的代码,我只把sample里边activity生成的对应文件贴一下吧,大家看一下:

    // Generated code from Butter Knife. Do not modify! package com.example.butterknife.library; import android.content.Context; import android.content.res.Resources; 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 com.example.butterknife.R; import java.lang.IllegalStateException; import java.lang.Override; public class SimpleActivity_ViewBinding implements Unbinder { private SimpleActivity target; private View view2130968578; private View view2130968579; @UiThread public SimpleActivity_ViewBinding(SimpleActivity target) { this(target, target.getWindow().getDecorView()); } @UiThread public SimpleActivity_ViewBinding(final SimpleActivity 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'")); Context context = source.getContext(); Resources res = context.getResources(); target.butterKnife = res.getString(R.string.app_name); target.fieldMethod = res.getString(R.string.field_method); target.byJakeWharton = res.getString(R.string.by_jake_wharton); target.sayHello = res.getString(R.string.say_hello); } @Override @CallSuper public void unbind() { SimpleActivity 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; view2130968578.setOnClickListener(null); view2130968578.setOnLongClickListener(null); view2130968578 = null; ((AdapterView<?>) view2130968579).setOnItemClickListener(null); view2130968579 = null; } }

    大家可以对着生成的Java文件去看生成Java文件过程的代码,这样便于理解,可以看到它的全部操作都是在构造函数里边做的,在构造函数里边有一个,这里咱们看一下Utils. findRequiredViewAsType()方法:

    public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who, Class<T> cls) { //绑定View;findViewById(R.id.btn); View view = findRequiredView(source, id, who); //强转View;如:(Button)findViewById(R.id.btn); return castView(view, id, who, cls); } public static View findRequiredView(View source, @IdRes int id, String who) { View view = source.findViewById(id); if (view != null) { return view; } String name = getResourceEntryName(source, id); throw new IllegalStateException("Required view '" + name + "' with ID " + id + " for " + who + " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'" + " (methods) annotation."); }

    终于看到了我们的findViewById()方法,返回了获取的view,在调用castView(view, id, who, cls)方法:

    public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) { try { return cls.cast(view); } catch (ClassCastException e) { String name = getResourceEntryName(view, id); throw new IllegalStateException("View '" + name + "' with ID " + id + " for " + who + " was of the wrong type. See cause for more info.", e); } }

    进行类型转换,如将View转换成Button,就是进行(Button)findViewById(id)操作。好了,回到我们process()方法:

    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()); }

      最终拿到了javaFile,通过辅助类filer生成文件。到这里,我们的文件生成工作就完了。也就是说,我们编译期的所有工作就完成了,之前,所有的操作都是为了生成我们的这些Java文件,生成的Java文件会与我们所写的Activity等Java文件一同编译成.class文件,所以我们在Activity中调用ButterKnife.bind(this)方法才有作用。本篇文章写到现在也已经进行了四分之三了,还剩下的就是我们调用ButterKnife.bind(this)方法后的处理了,这部分就简单了,用buns eyes想想也知道是怎么搞的,为了让大家更好的了解,咱们还是看一看吧。

    4.我们Activity中调用ButterKnife.bind(this)后... ...

    @NonNull @UiThread public static Unbinder bind(@NonNull Activity target) { View sourceView = target.getWindow().getDecorView(); return createBinding(target, sourceView); } 获取了decorView根布局,调用createBinding()方法: 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); } }

    上边代码中调用了一个findBindingConstructorForClass方法返回了一个constructor对象,后边调用这个对象的newInstance()方法,new了一个它的实例,最终返回,那么,我们猜测这里肯定是获取了当前Acitivity对应的在编译器生成的那个添加了绑定事件等信息的类的构造函数,看一下关键代码findBindingConstructorForClass(targetClass)

    @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.")) { if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search."); return null; } try { Class<?> bindingClass = Class.forName(clsName + "_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; }

    看见没,看见没,

    Class<?> bindingClass = Class.forName(clsName + "_ViewBinding"); //noinspection unchecked bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);

    对应到我们生成代码里就是下面这句:

    @UiThread public SimpleActivity_ViewBinding(final SimpleActivity target, View source){... ...}

       就这里,好了到这里,你应该明白整个ButterKnife工作原理了吧,我在最后总结一下:首先ButterKnife在编译器通过ButterKnifeProcessor类获取每一个ButterKnife支持的注解类型,通过他获取每一个添加该注解的element,经过一系列做操作,每一个添加了注解的类最终对应生成BindingSet.Builder对象,再生成BindingSet,最后根据BindingSet生成对应的Java类。我们在Activity里边调用bind()方法就是通过当前Activity找到对应的Java生成类并获取构造方法生成实例对象,在构造方法了进行了绑定操作。啊...终于说完了,思路很清晰,实现很麻烦。在此膜拜并感谢Jack大神为我们造出了这么牛逼的利器。好了,这篇文章就写到这里吧。不明白,就照着我添加好注释的源码看,应该很快就明白了,下边就是源码下载地址。谢谢!

     

    源码下载地址:http://download.csdn.net/detail/liuyonglei1314/9773886

    转载请注明原文地址: https://ju.6miu.com/read-2194.html

    最新回复(0)