美文网首页
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