美文网首页
ButterKnife源码简单整理

ButterKnife源码简单整理

作者: autisticBoy | 来源:发表于2018-10-17 11:24 被阅读0次

一开始看到这个名字,黄油刀?

添加依赖

 implementation'com.jakewharton:butterknife:8.4.0'    
 annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'  

在这里有一个坑,如果看的教程较老,会有在proect的build中添加

classpath'com.neenbedankt.gradle.plugins:android-apt:1.8'

以及在app的的build中添加

 apply plugin:'com.neenbedankt.android-apt'
 apt'com.jakewharton:butterknife-coplier:8.4.0'

之后就会报错,根据提示改即可

初识ButterKnife

最简单的莫过于bindview 以及 onclick
这里以bindview为例,之后的源码分析也都建立在这个基础上

 public class MainActivity extends AppCompatActivity {
     @NonNull
     @BindView(R.id.tv_text)
     TextView textView;
     @Override
     protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
          ButterKnife.bind(this);
    }
  }

bindView注解

进入bindView 可以看到一个简单的注解

@Retention(CLASS) @Target(FIELD)
public @interface BindView {
  /** View ID to which the field will be bound. */
  @IdRes int value();
}
  • 根据提示 可以看到里面的value变量用来存放绑定控件的id值
  • class属性说明他是在编译时运行的注解
  • field属性应用于成员变量

ButterKnifeProcessor 源码分析

首先根据对注解的处理器的理解 主要逻辑都在process方法中 于是找到process方法

@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);//1

    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {//2
      TypeElement typeElement = entry.getKey();
      BindingSet binding = entry.getValue();//3

      JavaFile javaFile = binding.brewJava(sdk, debuggable, useLegacyTypes);
      try {
        javaFile.writeTo(filer);//4
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }

    return false;
  }

查看注释1处的findAndParseTargets方法

 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)) {
      // we don't SuperficialValidation.validateElement(element)
      // so that an unresolved View type can be generated by later processing rounds
      try {
        parseBindView(element, builderMap, erasedTargetNames);//1
      } catch (Exception e) {
        logParsingError(element, BindView.class, e);
      }
    }

查看注释1处的方法

  private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
    Set<TypeElement> erasedTargetNames) {
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // Start by verifying common generated code restrictions.
    boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
        || isBindingInWrongPackage(BindView.class, element);//1

    // 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();
    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);//2
        hasError = true;
      }
    }

    if (hasError) {
      return;
    }

    // Assemble information on the field.
    int id = element.getAnnotation(BindView.class).value();
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    Id resourceId = elementToId(element, BindView.class, id);
    if (builder != null) {//3
      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);

    builder.addField(resourceId, new FieldViewBinding(name, type, required));//4

    // Add the type-erased version to the valid binding targets set.
    erasedTargetNames.add(enclosingElement);
  }

根据注释可以看到在注释1处判断了是否使用错误

  • 方法修饰符 不能为private static
  • 包含类型不能为非class
  • 包含类不能为private
  • isBindingInWrongPackage 说明类的包名
    注释2则说明修饰的必须是一个view or interface(其实这段代码中前面的一大段都是在判断使用是否正确)。接下来在注释3处判断builder是否存在不存在则创建,然后再注释4处调用了builder的 addfield方法,这样子修饰的类型信息全部转移到了builer中。
    接下来回到process中 在注释2处遍历bindingMap 取得bindingSet 调用 bingSet 的brewJava;
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();
  }

从代码中可以看出 这段将注解类生成了一个javaFile,接下来调用writeTo的方法 输出Java文件。

butterknife的bind 方法

 public static Unbinder bind(@NonNull Activity target) {
    View sourceView = target.getWindow().getDecorView();
    return createBinding(target, sourceView);
  }

可以看到主要调用的时creatBinding方法

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方法

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;
    }//1
    try {
      Class<?> bindingClass = Class.forName(clsName + "_ViewBinding");//2
      //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;
  }

在注释1处又进行了包名判断,在注射2处通过反射机制获取Class,这个Class类就是之前生成的MainActivity_ViewBinding ,且一个类只会在第一次反射形成,(简单的安卓反射机制)以后直接在BINDINGS里面去取,之后将class 转换为constructor。接着在createBinding中生成Constructor实例。

生成的辅助类分析

 public MainActivity_ViewBinding(T target, View source) {
    this.target = target;

    target.textView = Utils.findRequiredViewAsType(source, R.id.tv_text, "field 'textView'", TextView.class);
  }

  @Override
  @CallSuper
  public void unbind() {
    T target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");

    target.textView = null;

    this.target = null;
  }
}

在createBinding中已经传入了decorView(即source),调用了findRequiredViewAsType()方法,在其中调用了findRequiredView(),castView();

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;
再看castView()

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强制转换为传入class值类型,再赋值给targer(即MainActivity)其他的绑定如onclick再理解了上面的之后就很好理解了。

相关文章

网友评论

      本文标题:ButterKnife源码简单整理

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