美文网首页
ButterKnife源码走一波

ButterKnife源码走一波

作者: 的一幕 | 来源:发表于2018-12-27 19:41 被阅读13次

    用过ButterKnife源码的筒子们都知道,它的注解方式很吊,相比大家肯定想知道里面到底用了啥技术,能几行代码搞定这么多的玩意。
    一般会有这样的代码:

    public class LoginActivity extends AppCompatActivity {
        @BindView(R.id.input_call)
        public EditText inputCall;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_login);
            ButterKnife.bind(this);//框架的入口
            inputCall.setText("123");
        }
    }
    

    大家可以看到上面有BindView的注解,然后下面调用了ButterKnife.bind(this);,把当前的Activity给传进去了:

    /**
       * 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 bind(target, sourceView);
      }
    

    上面的方法把activity中顶层的DecorView传到了bind方法:

    /**
       * BindView annotated fields and methods in the specified {@code target} using the {@code source}
       * {@link View} as the view root.
       *
       * @param target Target class for view binding.
       * @param source View root on which IDs will be looked up.
       */
      @NonNull @UiThread
      public static Unbinder bind(@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方法后,最后调用了Unbinder两个参数的构造函数,并且返回来了。

    @Nullable @CheckResult @UiThread
      private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
        //首先去判断BINDINGS中有没有该实例,有的话直接返回
        Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
        if (bindingCtor != null || BINDINGS.containsKey(cls)) {
          if (debug) Log.d(TAG, "HIT: Cached in binding map.");
          return bindingCtor;
        }
        String clsName = cls.getName();
        //如果类的类名是以这样开头的直接不处理
        if (clsName.startsWith("android.") || clsName.startsWith("java.")
            || clsName.startsWith("androidx.")) {
          if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
          return null;
        }
        try {
          //类的class字节码
          Class<?> bindingClass = cls.getClassLoader().loadClass(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中,方便下次使用
        BINDINGS.put(cls, bindingCtor);
        return bindingCtor;
      }
    

    上面做的事情获到了*** _ViewBinding类class字节码,然后获取到它的Constructor实例,最后放到了BINDINGS的map中,方便下次使用。那么此处的*** _ViewBinding就是通过编译时期生成的LoginActivity_ViewBinding类,关于如何生成LoginActivity_ViewBinding类还得后面介绍APT(注解处理器)+javaPoet(自动生成代码)相关的代码:

    public class LoginActivity_ViewBinding implements Unbinder {
      private LoginActivity target;
    
      @UiThread
      public LoginActivity_ViewBinding(LoginActivity target) {
        this(target, target.getWindow().getDecorView());
      }
    
      @UiThread
      public LoginActivity_ViewBinding(LoginActivity target, View source) {
        this.target = target;
    
        target.inputCall = Utils.findRequiredViewAsType(source, R.id.input_call, "field 'inputCall'", EditText.class);
      }
    
      @Override
      @CallSuper
      public void unbind() {
        LoginActivity target = this.target;
        if (target == null) throw new IllegalStateException("Bindings already cleared.");
        this.target = null;
    
        target.inputCall = null;
      }
    }
    

    最终都会走第二个构造器的代码,看看findRequiredViewAsType方法:

    public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who,
          Class<T> cls) {
        View view = findRequiredView(source, id, who);
        return 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);
        }
      }
    

    紧接着调用了findRequiredView方法,然后强转了下:

    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.");
      }
    

    最终也是通过view.findViewById找到该view,下面来看看LoginActivity_ViewBinding如何在编译阶段能自动生成呢,编译阶段的话,需要去compile包下面的ButterKnifeProcessor类里面去看,这里可以直接到该类的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();
          //javaFile直接生成类的地方
          JavaFile javaFile = binding.brewJava(sdk, debuggable, useLegacyTypes);
          try {
            javaFile.writeTo(filer);
          } catch (IOException e) {
            error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
          }
        }
    
        return false;
      }
    

    上面方法先是通过findAndParseTargets方法找到所有的呆生成的类,放到map中,然后在通过JavaFile去生成类。

    private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
        Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
        Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
    //省略了好多代码,这里只说BindView注解
       // 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);
          }
        }
    
        return bindingMap;
      }
    

    看到了没,该方法是找所有注解的,然后依次处理注解,这里调用了parseBindView方法:

    private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
          Set<TypeElement> erasedTargetNames) {
        TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
    
       //省去了一些判断错误的情况
    
        // Assemble information on the field.
        //获取到注解的id
        int id = element.getAnnotation(BindView.class).value();
        BindingSet.Builder builder = builderMap.get(enclosingElement);
        Id resourceId = elementToId(element, BindView.class, id);
        //这里首次进来的时候为空,因此走下面的逻辑
        if (builder != null) {
          String existingBindingName = builder.findExistingBindingName(resourceId);
          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);
        //最终以resourceId为key,然后以FieldViewBinding为value放到了builder中
        builder.addField(resourceId, new FieldViewBinding(name, type, required));
    
        // Add the type-erased version to the valid binding targets set.
        erasedTargetNames.add(enclosingElement);
      }
    

    上面的方法中,走的是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;
    }
    

    上面调用了newBuilder方法,该方法是组成builder对象的重要方法:

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

    上面的代码挺简单的哈,生成类名,以及一些参数传递给builder。那么再回到parseBindView方法的最后面,会看到最后有builder.addField方法,resourceId作为key,value组合成一个FieldViewBinding对象。现在咋们还需要回到process方法。看到
    JavaFile javaFile = binding.brewJava(sdk, debuggable, useLegacyTypes);这句了没,这里就是去生成类的地方,直接找到BindingSetbrewJava方法:

    JavaFile brewJava(int sdk, boolean debuggable, boolean useLegacyTypes) {
        TypeSpec bindingConfiguration = createType(sdk, debuggable, useLegacyTypes);
        return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
            .addFileComment("Generated code from Butter Knife. Do not modify!")
            .build();
      }
    

    此处看到了调用了createType方法,这个方法很关键,生成类和方法的地方:

    private TypeSpec createType(int sdk, boolean debuggable, boolean useLegacyTypes) {
        //生成类的地方
        TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
            .addModifiers(PUBLIC);
        if (isFinal) {
          result.addModifiers(FINAL);
        }
    
        if (parentBinding != null) {
          result.superclass(parentBinding.bindingClassName);
        } else {
          result.addSuperinterface(UNBINDER);
        }
    
        if (hasTargetField()) {
          result.addField(targetTypeName, "target", PRIVATE);
        }
    
        if (isView) {
          result.addMethod(createBindingConstructorForView(useLegacyTypes));
        } else if (isActivity) {
          //看到了没,生成activity默认构造器的地方
          result.addMethod(createBindingConstructorForActivity(useLegacyTypes));
        } else if (isDialog) {
          result.addMethod(createBindingConstructorForDialog(useLegacyTypes));
        }
        if (!constructorNeedsView()) {
          // Add a delegating constructor with a target type + view signature for reflective use.
          result.addMethod(createBindingViewDelegateConstructor(useLegacyTypes));
        }
        //最终会走统一的构造器的地方
        result.addMethod(createBindingConstructor(sdk, debuggable, useLegacyTypes));
    
        //生成unbind方法的地方
        if (hasViewBindings() || parentBinding == null) {
          result.addMethod(createBindingUnbindMethod(result, useLegacyTypes));
        }
        return result.build();
      }
    

    上面注释都写得很清楚,咋们主要看下统一生成构造器的地方:

    private MethodSpec createBindingConstructor(int sdk, boolean debuggable, boolean useLegacyTypes) {
         //省略代码
        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, debuggable, useLegacyTypes);
          }
         //省略代码
    
        return constructor.build();
      }
    

    下面看下addViewBinding是如何调用的:

    private void addViewBinding(MethodSpec.Builder result, ViewBinding binding, boolean debuggable,
          boolean useLegacyTypes) {
        if (binding.isSingleFieldBinding()) {
          // Optimize the common case where there's a single binding directly to a field.
          FieldViewBinding fieldBinding = requireNonNull(binding.getFieldBinding());
          CodeBlock.Builder builder = CodeBlock.builder()
              .add("target.$L = ", fieldBinding.getName());
    
          boolean requiresCast = requiresCast(fieldBinding.getType());
          if (!debuggable || (!requiresCast && !fieldBinding.isRequired())) {
            if (requiresCast) {
              builder.add("($T) ", fieldBinding.getType());
            }
            builder.add("source.findViewById($L)", binding.getId().code);
          } else {
            //这里是关键的地方,调用utils的方法,这里不太懂,为啥是调用utils的find的方法呢
            builder.add("$T.find", UTILS);
            builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView");
            if (requiresCast) {
              builder.add("AsType");
            }
           //代替第二个参数的地方,用id来替换
            builder.add("(source, $L", binding.getId().code);
            if (fieldBinding.isRequired() || requiresCast) {
              builder.add(", $S", asHumanDescription(singletonList(fieldBinding)));
            }
            if (requiresCast) {
              //传入类型,也就是我们的view类型的class类型
              builder.add(", $T.class", fieldBinding.getRawType());
            }
            builder.add(")");
          }
          result.addStatement("$L", builder.build());
          return;
        }
    
        List<MemberViewBinding> requiredBindings = binding.getRequiredBindings();
        if (!debuggable || 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));
        }
    
        addFieldBinding(result, binding, debuggable);
        addMethodBindings(result, binding, debuggable, useLegacyTypes);
      }
    

    看到了没,这里使用$S来替换咋们需要的值,调用utils的方法这里是find方法。至此,再回到我们的 ButterKnifeProcessorprocess方法,最后调用了javaFile.writeTo(filer);方法,将LoginActivity_ViewBinding生成成功,源码也就分析到这了。

    分析不到位的地方,还请多指明,谢谢!!!

    相关文章

      网友评论

          本文标题:ButterKnife源码走一波

          本文链接:https://www.haomeiwen.com/subject/ggarlqtx.html