butterknife-源码解析


title: butterKnife源码解析 date: 2020-06-30 09:43:23 tags: [源码笔记] typora-copy-images-to: ./imgs typora-root-url: ./imgs


使用

使用就很简单了.导包.然后来个demo

private static final String TAG = "MainActivity";
private ViewGroup.LayoutParams layoutParams;
@BindView(R.id.tip)
TextView view;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ButterKnife.bind(this);
}
@OnClick(R.id.tip)
void onclick(View view ){
    Toast.makeText(this, "uytut", Toast.LENGTH_SHORT).show();
}

原理

它主要是通过注解主动生成了一个辅助类,在辅助类中把Id和控件进行绑定。辅助类的后缀是_ViewBinding, 同时,可以看到.辅助类和我们的类是相同路径的.

image-20200630204521463

辅助类代码

所有的辅助类都是继承自unbinder. 他的unbind解绑操作必须要我们调用.否则会内存泄漏

public class MainActivity_ViewBinding implements Unbinder {
  private MainActivity target;

  private View view7f0700b7;

  @UiThread  构造函数.执行绑定过程
  public MainActivity_ViewBinding(MainActivity target) {
  //可以看到.这里拿到了 target的顶层view
    this(target, target.getWindow().getDecorView()); 
  }

  @UiThread
  public MainActivity_ViewBinding(final MainActivity target, View source) {
    this.target = target;

    View view;
    //这里是的decorview 的findviewById.找到 id对应的view. 这个R.id.tip 就是我们之前@BindView(R.id.tip)的参数.
    view = Utils.findRequiredView(source, R.id.tip, "field 'view' and method 'onclick'");
    //把这个view 转换成TextView. 然后赋值给targetActivity 的view 
    target.view = Utils.castView(view, R.id.tip, "field 'view'", TextView.class);
    view7f0700b7 = view;
    设置点击事件.绑定 view的点击和 activity里的 onclick方法.这个方法可以随便命名.
    同时这里巧妙的设置了放置瞬间多次点击的问题.
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.onclick(p0);
      }
    });
  }

  @Override
  @CallSuper 这个类里边因为绑定了activity和view. 需要主动释放.否则造成内存泄露
  public void unbind() {
    MainActivity target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");
    this.target = null;

    target.view = null;

    view7f0700b7.setOnClickListener(null);
    view7f0700b7 = null;
  }
}
总结

这个辅助类.完成了activity和里边view 的绑定方法.并实现了点击的处理. 其实就是省略的我们手写的findviewById. 而butterknife 里主要的就是如何生成这个辅助类.和对不同view的类似的处理.所以接下来我们主要看如何生成辅助类

ButterKinfe.bind过程

源码追踪,.有删减

找到docerview, decorview是每个activity的顶层view
public static Unbinder bind(@NonNull Activity target) {
  View sourceView = target.getWindow().getDecorView();
  return bind(target, sourceView);
}

public static Unbinder bind(@NonNull Object target, @NonNull View source) {
    拿到target的class,因为这时候已经生成了辅助类.而辅助类和target类是在同样的路径下.只是比target类名称多了_ViewBinding
    所以这里利用target的class来找到辅助类.然后调用构造函数初始化
    Class<?> targetClass = target.getClass();
     Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

    try {
      return constructor.newInstance(target, source);
    } catch (IllegalAccessException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    }
  }

继续看,如何找到辅助类


private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
先从LinkedHashMap缓存中查找
  Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);

  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 .然后就是得到构造函数.加入map缓存.
    Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
    bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
  } catch (ClassNotFoundException e) {
    bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
  } 
  BINDINGS.put(cls, bindingCtor);
  return bindingCtor;
}
总结

这里也很明了. 通过target的class.找到生成的辅助类.然后反射创建辅助类.执行构造函数. 在构造函数中执行绑定.

生成辅助类

这里是在编译的时候通过编译时期-注解处理器.生成的辅助类. 这个代码在butterknife的源码里.

先来个注解处理器的文章

主要是根据定义的注解. 拿到注解对应的参数.然后再把这些参数进行处理. 对于butterkinfe就是生成辅助类.

public class MyProcessor extends AbstractProcessor {

    //用来指定你使用的 java 版本。通常你应该返回  SourceVersion.latestSupported()

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    //会被处理器调用,可以在这里获取Filer,Elements,Messager等辅助类,后面会解释
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
    }

//这个方法返回stirng类型的set集合,集合里包含了你需要处理的注解
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotataions = new LinkedHashSet<String>();
        annotataions.add("com.example.MyAnnotation");
        return annotataions;
    }

   //核心方法,这个一般的流程就是先扫描查找注解,再生成 java 文件
   //这2个步骤设计的知识点细节很多。

    @Override
    public boolean process(Set<? extends TypeElement> annoations,
            RoundEnvironment env) {
        return false;
    }
}

注解的原理.找一张 网上的图

img

img

这里 我们注意.主要工作就是用注解处理器.根据注解.生成辅助类.且辅助类和target在同样的路径中.这里有手动生成java文件的操作.

主要代码在ButterKnifeProcessor 类中.在 butterkinfe-compiler 模块中.

Annotation processing是在编译阶段执行的,它的原理就是读入Java源代码,解析注解,然后生成新的Java代码。新生成的Java代码最后被编译成Java字节码,注解解析器(Annotation Processor)不能改变读入的Java 类,比如不能加入或删除Java方法。注解处理器运行在它自己的 JVM 中

下面看源码

支持的注解

这是他支持的所有注解类型.其实一目了然.

private Set<Class<? extends Annotation>> getSupportedAnnotations() {
  Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();

  annotations.add(BindAnim.class);
  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(BindFont.class);
  annotations.add(BindInt.class);
  annotations.add(BindString.class);
  annotations.add(BindView.class);
  annotations.add(BindViews.class);
  annotations.addAll(LISTENERS);

  return annotations;
}
处理过程
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
得到 BindingSet 集合.这里是对各种注解的处理. 
bindingSet 可以理解为是一个java 文件的抽象.他里边的各种属性最后可以生成一个完整的java文件.这是butterknife自己定义的.
  Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);

  for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
    TypeElement typeElement = entry.getKey();
    BindingSet binding = entry.getValue();
    //这里已经生成了java文件,这个java文件就是上文的辅助类.
    JavaFile javaFile = binding.brewJava(sdk, debuggable);
    try {
    把java文件由内存中写到磁盘上
      javaFile.writeTo(filer);
    } catch (IOException e) {
     }
  }
  return false;
}

接下来看如何把有注解得到的数据封装成辅助类.他这里处理了各种注解.我们先只看BindView

private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
  Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
  Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
  这里是拿到所有 bindView 的注解.然后遍历处理.
   for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
      try {
        parseBindView(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindView.class, e);
      }
    }
    对lister的处理.
     for (Class<? extends Annotation> listener : LISTENERS) {
      findAndParseListener(env, listener, builderMap, erasedTargetNames);
    }


    最后是处理父类的绑定
     // 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();
      BindingSet.Builder builder = entry.getValue();

      TypeElement parentType = findParentType(type, erasedTargetNames, classpathBindings.keySet());
      if (parentType == null) {
        bindingMap.put(type, builder.build());
      } else {
        BindingInformationProvider parentBinding = bindingMap.get(parentType);
        if (parentBinding == null) {
          parentBinding = classpathBindings.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;
  }

这里总共看三步. 处理bindview的注解.生成对应bindingset. 处理listener. 处理父类绑定.

解析bindView注解

他遍历的Element 代表了源文件中的每一部分特定类型.介绍如下

public class ClassA { // TypeElement
    private int var_0; // VariableElement
    public ClassA() {} // ExecuteableElement

    public void setA( // ExecuteableElement
            int newA // TypeElement
    ) {
    }
}

入口处在 ButterKnifeProcessor.parseBindView

private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
      Set<TypeElement> erasedTargetNames) {
      这里就是注解类型
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();


    TypeMirror elementType = element.asType();
    注解的名称和全限定名
    Name qualifiedName = enclosingElement.getQualifiedName();
    Name simpleName = element.getSimpleName();



    // 拿到注解对应的id值. 就是我们写的BindView(R.id.tip) 中的R.id.tip
    int id = element.getAnnotation(BindView.class).value();
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    封装id并缓存
    Id resourceId = elementToId(element, BindView.class, id);
    这里创建了新的 BindingSet .为了封装 bindview 的相关参数.
    if (builder != null) {
      String existingBindingName = builder.findExistingBindingName(resourceId);
    } else {
      builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    }
        到这.就给辅助类生成了这个 bindview注解先关的属性.名称.类型.然后添加到builder中.最后统一创建.
    String name = simpleName.toString();
    TypeName type = TypeName.get(elementType);
    boolean required = isFieldRequired(element);
    我猜其实就是生成 private View view7f0700b7; 这种类型的格式.当然还绑定了对应的id.
    builder.addField(resourceId, new FieldViewBinding(name, type, required));

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

所以这方法大概就是,通过annotation的类型.然后取出注解的值. 最后经过封装.创建这个属性的builder.与id进行绑定.这里主要是根据注解创建类结构.看不太懂也没事.

listener的处理

继续回到ButterKnifeProcessor 的findAndParseTargets中对点击事件的处理

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

接着看 findAndParseListener 又跳到parseListenerAnnotation.

private void parseListenerAnnotation(Class<? extends Annotation> annotationClass, Element element,
    Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) {

    先拿到注解的类型.
  ExecutableElement executableElement = (ExecutableElement) element;
  TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
  在拿到注解中的值.也就是绑定点击的id数组.  @OnClick({R.id.hello,R.id.hello2}) 这个
  int[] ids = (int[]) annotationValue.invoke(annotation);
    String name = executableElement.getSimpleName().toString();
    boolean required = isListenerRequired(executableElement);

    这里我也看不太懂. 应该是监听的注解. 这里我们要知道.butterknife会默认创建一个listener.并在click中执行view的回调
    ListenerClass listener = annotationClass.getAnnotation(ListenerClass.class);
    ListenerMethod method;
    ListenerMethod[] methods = listener.method();

    callback 的注解.  没有listener的就会走callback 的回调
      Method annotationCallback = annotationClass.getDeclaredMethod("callback");
      Enum<?> callback = (Enum<?>) annotationCallback.invoke(annotation);
      Field callbackField = callback.getDeclaringClass().getField(callback.name());
      method = callbackField.getAnnotation(ListenerMethod.class);
      拿到所有参数
      List<? extends VariableElement> methodParameters = executableElement.getParameters();
      返回值类型
      TypeMirror returnType = executableElement.getReturnType();
      最后,把上边的参数进行封装.等着以后生成listener的辅助类.
       MethodViewBinding binding =
        new MethodViewBinding(name, Arrays.asList(parameters), required, hasReturnValue);
    BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    Map<Integer, Id> resourceIds = elementToIds(element, annotationClass, ids);

看一下生成listener相关的注解,应该就是构建这个类似的结构.

@ListenerClass(
    targetType = "android.widget.AdapterView<?>",
    setter = "setOnItemClickListener",
    type = "android.widget.AdapterView.OnItemClickListener",
    method = @ListenerMethod(
        name = "onItemClick",
        parameters = {
            "android.widget.AdapterView<?>",
            "android.view.View",
            "int",
            "long"
        }
    )
)

最后就是那这些封装数据.生成一个java类. 那个不看了.不好理解.

本文由博客群发一文多发等运营工具平台 OpenWrite 发布

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值