ButterKnife系列(一) : 总体流程分析

最新版本使用方法

首先需要配置的环境,包括依赖的插件和版本库

配置环境

根目录builld.gradle

buildscript {
  repositories {
    mavenCentral()
    google()
  }
  dependencies {
    classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3'
  }
}

app.gradle中添加依赖

android {
  ...
  // Butterknife requires Java 8.
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}

dependencies {
  implementation 'com.jakewharton:butterknife:10.2.3'
  annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3'
}

modual中的配置

apply plugin: 'com.android.library'
apply plugin: 'com.jakewharton.butterknife'
 

简单使用

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.textVew)
    TextView username;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ButterKnife.bind(this);
        username.setText("hi nick to meet you");
    }
}

 

源码目录

源码包括了butterknife、butterknife-annotations、butterknife-compiler、butterknife-gradle-plugin、butterknife-lint、butterknife-reflect、butterknife-runtime这几个模块

butterknife模块提供了ButterKnife类,作为应用层的唯一封装,通过bind方法实现了View绑定功能(不仅仅是绑定功能,还有更多其他)

butterknife-annotaions提供了绑定view的注解,比如@BindView、@BindClick等

butterknife-compiler动态生成一些代码,比如MainActivity中使用了Butterknife,就会生成一个MainActivity_ViewBinding的类,ButterKnife类通过MainActivity_ViewBinding实现了View的绑定

butterknife-gradle-plugin 生成一个gradle插件,实现R文件拷贝成R2。不过最新版本里面已经不是R2了,是另一个包名的R文件。

 

 

源码分析

         前面,我们提到,通过@BindView可以关联Id和View(当然还有更多其他的用途),接下来,我们以BindView注解为例,分析如果将View绑定到对应的Id。

        假如,我们在MainActivity中使用了ButterKnife,该框架的注解处理器会帮我们自动生成一个名为MainActivity_ViewBinding的类。MainActivity_ViewBinding类最终帮我们关联Id和View,或者设定View的点击事件等等。对于MainActivity_ViewBinding这个类,我们先有个概念,知道他是干嘛的就可以了,后面再来具体分析MainActivity_ViewBinding的机制。

      接下来,我们以ButterKnife类的bind方法为入口,分析ButterKnife的总体流程。

ButterKnife.bind(this);

/**
  * ButterKnife.java
  */
@NonNull @UiThread
  public static Unbinder bind(@NonNull Activity target) {
    View sourceView = target.getWindow().getDecorView();
    return bind(target, sourceView);
}

ButterKnife.bind方法调用了另一个bind(Object target, View source)方法,方法如下:

ButterKnife.bind(Object target, View source)

bind(Object target, View source)返回了MainActivity_ViewBinding对象。首先,通过findBindingConstructorForClass方法,返回了MainActivity_ViewBinding的构造方法。然后通过newIntance构造来MainActivity_ViewBinding对象。

/**
  * ButterKnife.java
  */


// target = activity, source = decorView  
public static Unbinder bind(@NonNull Object target, @NonNull View source) {

    // MainActivity.class
    Class<?> targetClass = target.getClass();

    /**
      * 这里说多两句,假如我们在MainActivity中,使用了ButterKnife,ButterKnife的注解处理器会自        
        动生成一个MainActivity_ViewBinding.java 文件。通过MainActivity_ViewBinding这个类实现     
        TextView等View绑定到对应Id、实现View点击事件等等。
     
        findBindingConstructorForClass就是通过反射拿到MainActivity_ViewBinding这个类的构造方    
        法
      */
    Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

    if (constructor == null) {
      return Unbinder.EMPTY;
    }

    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
      // 上面的MainActivity_ViewBinding实现来UnBind接口,这里通过构造方法实现 
      // MainActivity_ViewBinding.
      return constructor.newInstance(target, source);
    } 
  
    ......

  }

findBindingConstructorForClass(Class<?> cls)

findBindingConstructorForClass方法缓存并返回了MainActivity_ViewBinding类的构造方法。

@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
  
  // 从缓存中去构造方法,如果有直接返回
  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;
  }

  // 反射构造 MainActivity_ViewBinding这种注解处理器生成的类
  try {
    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.put(cls, bindingCtor);
  return bindingCtor;
}

MainActivity_ViewBinding.java

接下来,我们继续分析,注解处理器自动生成的MainActivity_ViewBinding类是如何关联View和Id的。第二个构造函数,调用Utils.findRequiredViewAsType方法,实现View和资源Id对应视图的绑定。根据前面的入参,我们知道target和source,userName都是什么了。

public class MainActivity_ViewBinding implements Unbinder {
  private MainActivity target;

  @UiThread
  public MainActivity_ViewBinding(MainActivity target) {
    this(target, target.getWindow().getDecorView());
  }

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

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

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

    target.username = null;
  }
}

 

// target = activity // source = decorView  // userName = TextView

target.username = Utils.findRequiredViewAsType(source, R.id.textVew, "field 'username'", TextView.class);

findRequiredViewAsType(View source, @IdRes int id, String who,Class<T> cls)

findRequiredViewAsType调用了findRequiedView方法,并传入来decvor,@BindView中的资源id(上面demo是R.id.textView)

/**
  * butterknife-runtie:10.2.3.aar
  * Utils.java
  */
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);
}

findRequiredView(View source, @IdRes int id, String who)

调用source.findViewById = decorView.findViewById(R.id.textView), 对应demo中,返回TextView对象

/**
  * View = DecorView
  * id = R.id.textView (上面demo中TextView的资源Id)
  */
public static View findRequiredView(View source, @IdRes int id, String who) {
  // 有没有很熟悉啊!
  View view = source.findViewById(id);
  if (view != null) {
    return view;
  }

  ......

}

至此,我们撸完来ButterKnife实现findViewById的全过程。总结一下,通过ButterKnife.bind方法,构造了MainActivity_ViewBinding类。MainActivity_ViewBinding类的构造方法通过调用Utils.findRequredView实现来findViewById。

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值