ButterKnife源码分析(一)

来源:互联网 发布:java中long转换成date 编辑:程序博客网 时间:2024/05/16 07:09

ButterKnife源码分析(一)

简介

上篇文章介绍了ButterKnife的使用方法(ButterKnife使用详解跳转链接),这篇文章分析一下ButterKnife的源码。算是ButterKnife的代码阅读笔记。

  • 一句话介绍:他可以让你省去让人厌烦的findViewById,借助插件可以实现自动绑定Java和XML
  • 源码网址:https://github.com/JakeWharton/butterknife
  • 最新版本:8.5.1(最新版本的代码与老代码还是有很大变化的,不过好在基本思路没变)
  • 本文内容:本文将分析ButterKnife的源码,让我们看看ButterKnife是怎么通过注解实现findViewById的

目录

  • ButterKnife源码分析一
    • 简介
    • 目录
    • 举个栗子
    • 问题一 BindView是如何生效的
    • 问题二 onClick是如何转换的
    • 问题三 注解Optional的作用
    • 问题四 ButterKnife在Fragment中有什么不同
    • 问题四 为什么注解的View不能是private
    • 问题六 unBind干了些什么
    • 问题五 ButterKnife的类结构是什么样子的呢
    • 问题七 MainActivity_ViewBinding是如何生成的呢
    • 总结

举个栗子

要看ButterKnife的源码我们先要找一个切入点,给自己提几个问题。带着问题去看源码,效率会高很多哦。
下面我们来举一个最简单的butterKnife的使用例子。

假设有一个:MainActivity.java

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

对应的xml文件为:activity_main.xml。

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/activity_main"    android:layout_width="match_parent"    android:layout_height="match_parent">    <TextView        android:id="@+id/tv_content"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="Hello World!" /></RelativeLayout>

看完这个例子后,我们的第一个问题来了。

问题一: BindView是如何生效的

换句话说,我们想知道ButterKnife是如何将代码

@BindView(R.id.tv_content)TextView tvContent;

变换成代码

TextView tvContent = findViewById(R.id.tv_content)

首先我们从代码

ButterKnife.bind(this);

入手

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

代码首先会通过target.getWindow().getDecorView();语句获取activity的跟视图decorView。读过view源码的同学知道,activity的根view为名叫mDecor的Framlayout,也就是这里获取到的sourceView。

获取到sourceView后,将target(就是MainActivity对象)与sourceView(MainActivity的跟视图)作为参数createBinding。

这里我们通过返回值可以猜测一下。通过createBinding获取到的是继承了Unbinder接口的一个实例。

再看createBinding(target, sourceView)方法

private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {  //拿到MainActivity.class  Class<?> targetClass = target.getClass();  if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());  //通过targetClass获取一个构造器  Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);  if (constructor == null) {    return Unbinder.EMPTY;  }  //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.  try {    //使用构造器构造一个实例,参数是target(MainActivity)和source(MainActivity对应的根视图)    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);  }}

方法挺长不过,我们真正关心的主要是两句:

Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

通过MainActivity.class获取对应的构造器,看到这里我们可以猜到是通过反射创建Unbinder实例。
果然紧接着就是

return constructor.newInstance(target, source);

但究竟是如何将MainActivity与Unbinder对应的呢?继续看findBindingConstructorForClass(targetClass)

@Nullable @CheckResult @UiThreadprivate static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {  //首先从BINDINGS获取Constructor,BINDINGS可以理解为缓存,已经用过的Constructor都会记录在BINDINGS中  Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);  //如果从BINDINGS找到了Constructor直接返回  if (bindingCtor != null) {    if (debug) Log.d(TAG, "HIT: Cached in binding map.");    return bindingCtor;  }  String clsName = cls.getName();  //通过类名如果发现是android类文件或者java类文件,则返回空。这里可能奇怪为什么会遇到android或者java文件?我们带着疑问继续看  if (clsName.startsWith("android.") || clsName.startsWith("java.")) {    if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");    return null;  }  try {    //通过反射获取到名叫MainActivity_ViewBinding的类的构造器    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.");  } catch (ClassNotFoundException e) {    //如果MainActivity_ViewBinding这个类没有找到就获取MainActivity.class的父类,继续执行     findBindingConstructorForClass,这里是个递归,这个递归什么时候停止呢?知道遇到android或者java类文件。    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);  }  //最后将这个Constructor放到缓存中,这样再次用到MainActivity_ViewBinding时就可以不用反射,可以提高运行效率  BINDINGS.put(cls, bindingCtor);  return bindingCtor;}

findBindingConstructorForClass方法是一个比较核心的方法,我们来总结一下。这个方法的作用就是利用MainActivity.class的类名,找到名叫MainActivity_ViewBinding的类,并创建一个实例。

所以我们可以把原来MainActivity的方法改一下,这里ButterKnife经过不懈努力,将语句ButterKnife.bind(this)替换成了new MainActivity_ViewBinding(this, getWindow().getDecorView());两句话的效果是一样的,不信可以试试,哈哈。

public class MainActivity extends AppCompatActivity {    @BindView(R.id.tv_content)    TextView tvContent;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        //ButterKnife.bind(this);        new MainActivity_ViewBinding(this, getWindow().getDecorView());    }}

继续提问:MainActivity_ViewBinding在哪里?又是什么样子的呢?我们可以搜索一下这个类

这里写图片描述

看一下这个类里都有什么

// Generated code from Butter Knife. Do not modify!package com.example.admin.butterknifedemo;import android.support.annotation.CallSuper;import android.support.annotation.UiThread;import android.view.View;import android.widget.TextView;import butterknife.Unbinder;import butterknife.internal.Utils;import java.lang.IllegalStateException;import java.lang.Override;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.tvContent = Utils.findRequiredViewAsType(source, R.id.tv_content, "field 'tvContent'", TextView.class);  }  @Override  @CallSuper  public void unbind() {    MainActivity target = this.target;    if (target == null) throw new IllegalStateException("Bindings already cleared.");    this.target = null;    target.tvContent = null;  }}

这个类有两个构造方法,和一个unbind函数。我们先看构造方法。不知道大家还记得么,构造方法的两个参数:target(MainActivity) , source (MainActivity的根视图)。 构造方法只有一个方法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);  }

调用了两个方法,先看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.");  }

终于找到了findViewById。。。。哈哈,可以看到,函数的返回值是View,我们需要的是TextView,所以第二个函数castView出场了,这个函数的作用就是类型强制转换。

所以经过了这一系列的转换。我们的MainActivity又可以改一下了。

public class MainActivity extends AppCompatActivity {    TextView tvContent;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        tvContent = (TextView) getWindow().getDecorView().findViewById(R.id.tv_content);    }}

经过ButterKnife再一次的不懈努力,我们成功的将ButterKnife.bind()转换成了:

tvContent = (TextView) getWindow().getDecorView().findViewById(R.id.tv_content);

这里可能有人会问,我们一般在Activity中不会写getWindow().getDecorView().findViewById(R.id.tv_content) 这么麻烦,只会写findViewById(R.id.tv_content),这里大家可以看下findViewById的源码(如下所示)。我们发现其实两个语句根本是一样的。

@Nullablepublic View findViewById(@IdRes int id) {    return getDecorView().findViewById(id);}

问题二: @onClick是如何转换的

我们改一下MainActivity:

public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        ButterKnife.bind(this);    }    @OnClick(R.id.tv_content)    public void onContentClick() {    }}

编译一下,看看生成的 MainActivity_ViewBinding:

@UiThread  public MainActivity_ViewBinding(final MainActivity target, View source) {    this.target = target;    View view;    view = Utils.findRequiredView(source, R.id.tv_content, "method 'onContentClick'");    view2131427414 = view;    view.setOnClickListener(new DebouncingOnClickListener() {      @Override      public void doClick(View p0) {        target.onContentClick();      }    });  }

这里不难理解,只是为什么是DebouncingOnClickListener不是View.OnClickListener。看看代码:

/** * A {@linkplain View.OnClickListener click listener} that debounces multiple clicks posted in the * same frame. A click on one button disables all buttons for that frame. */public abstract class DebouncingOnClickListener implements View.OnClickListener {  static boolean enabled = true;  private static final Runnable ENABLE_AGAIN = new Runnable() {    @Override public void run() {      enabled = true;    }  };  @Override public final void onClick(View v) {    if (enabled) {      enabled = false;      v.post(ENABLE_AGAIN);      doClick(v);    }  }  public abstract void doClick(View v);}

通过注释不难理解,这个类的作用是当一个Button被按下是,其他的Button都disable。

问题三: 注解@Optional的作用

还是上面的例子

public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        ButterKnife.bind(this);    }    //注意这里增加了@Optional注解    @Optional    @OnClick(R.id.tv_content)    public void onContentClick() {    }}

看下生成的代码

  @UiThread  public MainActivity_ViewBinding(final MainActivity target, View source) {    this.target = target;    View view;    view = source.findViewById(R.id.tv_content);    if (view != null) {      view2131427414 = view;      view.setOnClickListener(new DebouncingOnClickListener() {        @Override        public void doClick(View p0) {          target.onContentClick();        }      });    }  }

比之前多了个view空判断。

问题四: ButterKnife在Fragment中有什么不同。

这里我们要看下ButterKnife.bind()方法,之前我们只取了一个例子。这里我们看一下全家福

  //适用于activity,view注入  public static Unbinder bind(@NonNull Activity target){    View sourceView = target.getWindow().getDecorView();    return createBinding(target, sourceView);  }  //在view中绑定子view使用  public static Unbinder bind(@NonNull View target){    return createBinding(target, target);  }  //适用于Dialog,view注入  public static Unbinder bind(@NonNull Dialog target) {    View sourceView = target.getWindow().getDecorView();    return createBinding(target, sourceView);  }  //适用于在Activity外部使用Activity的view  public static Unbinder bind(@NonNull Object target, @NonNull Activity source) {    View sourceView = source.getWindow().getDecorView();    return createBinding(target, sourceView);  }  //适用于Fragment中,view注入  public static Unbinder bind(@NonNull Object target, @NonNull View source) {    return createBinding(target, source);  }  //在dialog外部使用Dialog的view  public static Unbinder bind(@NonNull Object target, @NonNull Dialog source) {    View sourceView = source.getWindow().getDecorView();    return createBinding(target, sourceView);  }

如果大家觉得我写的不够清楚,可以直接看源码注释。

问题四: 为什么注解的View不能是private

我们发现用注解修饰的变量,和方法都不能试private,这是为什么呢,我们可以看一下,MainActivity_ViewBinding的包名,与MainActivity是相同的。而 MainActivity_ViewBinding使用了MainActivity中的tvContent变量和onContentClick方法,如果设置成private会导致编译出错。所以不能试private的。在生成MainActivity_ViewBinding时会逐一检查注解修饰的所有变量和方法是否是private。这部分将在下一篇文章中讲述。

@BindView(R.id.tv_content)TextView tvContent;@OnClick(R.id.tv_content)public void onContentClick() {}

问题六: unBind干了些什么

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

主要是将一些view置空,跟上一篇同样的结论,感觉这样做法稍稍有点多余,也许一些特殊的需求会用到吧。

问题五: ButterKnife的类结构是什么样子的呢

这里写图片描述

呵呵,本文所讲述的源码,除去注解之外一共只有四个类和一个接口,大家看以仔细看一下,代码还是很简单,很清晰的。8.5.1的代码明显比7.0.1版本的代码清晰了很多。

问题七: MainActivity_ViewBinding是如何生成的呢。

这个将放在下一篇博客介绍,原谅我,头都大了,今天就到这吧。

总结

总结一下,ButterKnife的作用就是将ButterKnife.bind(this)转换成了,我们数据的findViewById,所以下次见到ButterKnife.bind(this)时,大脑可以自动将他认为是很多的view自动finderViewById。

另外在整个过程中一次ButterKnife.bind(this)会使用1次反射(同一个类,第二次调用ButterKnife.bind(this)时,就不再用反射了),会影响一点速度。

2 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 在部队干活的钱怎么办 新兵5公里超过标准时间怎么办 17个月婴儿裹手怎么办 7个月婴儿裹手怎么办 俩月孩子裹手怎么办 婴儿 3个月 裹手怎么办 一岁宝宝裹手怎么办 5个月宝宝裹手怎么办 不想让孩子裹手怎么办 四个月宝宝裹手怎么办 开车撞狗了跑了怎么办 母螃蟹抱卵了怎么办 我能怎么办我也想睡觉 一个人的微信群找不到了怎么办 一个人的微信被删了找不到了怎么办 不想回对方微信怎么办 喜欢我的人太多怎么办 假如战争今夜打响我们该怎么办 约客户客户说忙怎么办 第一天来姨妈痛怎么办 痛经痛的很厉害怎么办 工作第一天被辞怎么办 第一天练车紧张怎么办 到新公司第一天怎么办 我妈上班别人欺负怎么办 欺负别人他妈来找了怎么办 家人都欺负我妈怎么办 人老了日不动了怎么办? 日照职业技术学院公租房怎么办留宿 眼镜上沾了胶水怎么办 近视镜片刮花了怎么办 墨镜镜片磨花了怎么办 邻居养狗味道大怎么办 邻居不让养狗了怎么办 养狗地板有味道怎么办 狗狗嘴巴被大狗咬肿了怎么办 花了钱心里难受怎么办 狗在屋里有味道怎么办 养狗家里有异味怎么办 养狗房子有味道怎么办 狗身上有腥臭味怎么办