ButterKnife源码分析

View绑定的过程

(1) Activity在onCreate的中,在setContentView之后,调用ButterKnife.bind(this);

  • 必须在setContentView之后的原因是因为,bind过程会调用target.getWindow().getDecorView(); 只有setContenView之后,DecorView里面才有布局文件,才能通过DecorView找到里面存在的子view
@Override Java
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_authentication);
    ButterKnife.bind(this);
    initView();
}

(2) bind函数

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

(3)createBinding函数,完成实际的绑定

  • 首先根据该Class的名称,找到Classname__ViewBinding的构造类
  • 然后调用该构造类的构造函数,构造函数里面完成view的绑定,listener的绑
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());
// 根据当前的class,来查找对应的ViewBinding的class
  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);
    ...

dBindingConstructorForClass的代码

  • 首先从一个hashMap的缓存中查找
  • 如果缓存中没有找到,才利用Class.forName反射的机制加载BindClass
  • 因此,一个BindClass只会加载一遍
@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
  Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
 ...
  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.");
...
// 
  BINDINGS.put(cls, bindingCtor);
  return bindingCtor;
}

(4)最后看看一下Classname__ViewBinding的构造函数里面的代码

  • 从这里可以看出所有的view都是作为target的属性来直接访问的,因此Butterknife,要求注解的字段或者方法不能是Private和Final的
@UiThread
public AuthenticationActivity_ViewBinding(final T target, View source) {
    this.target = target;

    View view;
    // 通过findViewById找到对应的view
    view = Utils.findRequiredView(source, R.id.rl_choose_photo, "field 'choosePhotoRl' and method 'onClick'");
...
// 添加监听器
    view.setOnClickListener(new DebouncingOnClickListener() {
        @Override
        public void doClick(View p0) {
            target.onClick(p0);
        }
    });
    target.toolbar = Utils.findRequiredViewAsType(source, R.id.toolbar, "field 'toolbar'", Toolbar.class);
}

生成ViewBinding Java代码的过程

apt 工作原理简介

注解处理器是(Annotation Processor)是javac的一个工具,用来在编译时扫描和编译和处理注解(Annotation)。APT大概就是你声明的注解的生命周期为CLASS,然后继承AbstractProcessor类。继承这个类后,在编译的时候,编译器会扫描所有带有你要处理的注解的类,然后再调用AbstractProcessor的process方法,对注解进行处理,那么我们就可以在处理的时候,动态生成绑定事件或者控件的java代码

ButterKnife生成代码过程分析

可以首先参考http://blog.csdn.net/u013045971/article/details/53509237 理解,apt的实现过程和原理,根据这个实现过程可原理,可以轻松的理解ButterKnife的实现过程

(1)ButterKnifeProcessor init方法

 @Override public synchronized void init(ProcessingEnvironment env) {
    super.init(env);

    String sdk = env.getOptions().get(OPTION_SDK_INT);
    ...
    debuggable = !"false".equals(env.getOptions().get(OPTION_DEBUGGABLE));
   // 获取有用的工具类
    elementUtils = env.getElementUtils();
    typeUtils = env.getTypeUtils();
    filer = env.getFiler();
    try {
      trees = Trees.instance(processingEnv);
    } catch (IllegalArgumentException ignored) {
    }
  }

(2)ButterKnifeProcessor process方法(这个方法是注解器开始工作的起点)

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

    // 遍历 bindingMap 并且通过 Filer 生成 Java 代码
    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingSet binding = entry.getValue();

      JavaFile javaFile = binding.brewJava(sdk, debuggable);
      try {
        javaFile.writeTo(filer);
      }
      ...
    return false;
  }

总体来看 process 方法就干了两件事情:

  • 扫描所有的注解,然后生成以 TypeElement 为 key ,BindingSet 为 value 的 Map ;
  • 根据生成的 Map ,遍历后通过 Filter 来生成对应的辅助类源码。PS:ButterKnife 使用了 JavaPoet 来生成 Java 源码。

(3)

相关链接

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值