依赖注入--Butterknife深入理解与源码解析

依赖注入(Dependency Injection)是实现控制反转(IOC -- Inversion of Control)的方式之一,另一种是依赖查找(Dependency Lookup)

IOC

对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。IOC容器的加入,对象A与对象B之间失去了直接联系,所以,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。

对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。

对象没有了耦合关系,全部对象的控制权全部上缴给“第三方”IOC容器,所以,IOC容器成了整个系统的关键核心。

依赖注入起到的作用就是将对象之间的依赖关系从原先的代码中解耦出来,通过配置文件或注解等方式加上框架的处理让我们对依赖关系灵活集中的进行管理。


正文开始

首先简单介绍一下基本流程


butterknife采用注解进行注入,在编译器生成代码

首先butterknife自动生成的代码在build/generated/source/apt下面,一般命名为‘类名’_ViewBinding

public class MainActivity_ViewBinding implements Unbinder {

      private MainActivity target;

      private View view7f07008f;        //有几个注册点击事件的,则会创建几个这样的view

      @UiThread

      public MainActivity_ViewBinding(MainActivity target) {

            this(target, target.getWindow().getDecorView());    //获取DecorView后调用自身构造方法

      }

      @UiThread

      public MainActivity_ViewBinding(final MainActivity target, View source) {   

            this.target = target;

            View view;

            view = Utils.findRequiredView(source, R.id.to_scan, "field 'toScan' and method 'onViewClicked'"); //找到需要的view

        //这里第三个参数传入的串,只在根据id找不到view时,报错的信息中使用

            target.toScan = Utils.castView(view, R.id.to_scan, "field 'toScan'", Button.class);

        //这个地方view和toScan分别赋值,不直接使用toScan去设置监听,个人理解是分离监听与View

            view7f07008f = view; //保存view,在unbind的时候使用

            view.setOnClickListener(new DebouncingOnClickListener() {    //这个是butterknife自己实现的点击监听,继承了onClickListener

            @Override

            public void doClick(View p0) {

                        target.onViewClicked();        //当有多个view注册了点击事件时,会把p0传过去以区分view

                  }    });

        target.listview = Utils.findRequiredViewAsType(source, R.id.listview, "field 'listview'", ListView.class); 

        //没注册点击事件的view得到view的方法不同于注册的,源码见下解析

        target.progress = Utils.findRequiredViewAsType(source, R.id.progress, "field 'progress'", RelativeLayout.class);

      }

      @Override

    @CallSuper

    public void unbind() {

                MainActivity target = this.target;   

                if (target == null)throw new IllegalStateException("Bindings already cleared.");

                this.target = null;    //成员变量赋值给局部变量后,置空,是为了更好的回收吗?这个地方不太懂

                target.toScan = null;

                target.listview = null;

                target.progress = null;

                view7f07008f.setOnClickListener(null);    //对应view监听置空

                view7f07008f = null;

}}

这个类继承自butterknife自己的Unbinder

public interface Unbinder {

      @UiThread void unbind();

      Unbinder EMPTY = () -> { };

}

DebouncingOnClickListener的源码

public abstract class DebouncingOnClickListener implements View.OnClickListener {

      static boolean enabled = true;

    private static final Runnable ENABLE_AGAIN = () -> 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);    //子类需要实现的点击事件的真正逻辑的抽象方法

}

Utils.findRequiredView的源码

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

      View view = source.findViewById(id);      //也是用findviewbyid来实现的

      if (view != null) {    return view;  }

      String name = getResourceEntryName(source, id);    //当找不到view时,结合who信息,抛出异常

      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.");}

Utils.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);}    //强转

Utils.castView的源码

public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {

      try {

            return cls.cast(view);    //根据接收的类型,把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);  }}

ps一点:原来findViewById后还需要强制转换成对应类型的View,现在已经不需要了,源码如下:

public final <T extends View> T findViewById(@IdRes int id) {    if (id == NO_ID) {        return null;    }    return findViewTraversal(id);}

protected <T extends View> T findViewTraversal(@IdRes int id) {    if (id == mID) {        return (T) this;    }    return null;}

//可以看出这里已经通过返回泛型的约束强转完成了

解释完自动生成代码中的实现,下面就看看这个MainActivity_ViewBinding是怎么调用的

要是用butterknife,在Activity中是要调用它的bind的方法的,一般在onCreate中,Butterknife.bind(this),这个this当然是Activity的对象,也就是target

@NonNull @UiThread

public static Unbinder bind(@NonNull Activity target) {

      View sourceView = target.getWindow().getDecorView();

      return bind(target, sourceView);}    //这里也是获取DecorView后,继续调用bind

@NonNull @UiThread

public static Unbinder bind(@NonNull Object target, @NonNull View source) {

      Class<?> targetClass = target.getClass();    //首先根据传入的target也就是Activity的实例拿到对应的字节码对象

      if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());    //debug模式下打印一些信息

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

        //这里通过字节码对象得到的constructor其实就可以理解为是传入的Activity的自动生成的ViewBinding类的constructor了

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

      //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.

      try {

            return constructor.newInstance(target, source);    //创建viewbinding对象返回

      } 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的源码

@Nullable @CheckResult @UiThread

private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {

      Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);    //通过key拿到构造对象

    //BINDINGS是(static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();)

      if (bindingCtor != null || BINDINGS.containsKey(cls)) {

            if (debug) Log.d(TAG, "HIT: Cached in binding map.");

        return bindingCtor;  }    //如果这个map中已经有了该构造对象,直接返回,如果没有,在下面进行添加

      String clsName = cls.getName();    //这里就是报名加传入的类的类名,如这个是com.example.demo.MainActivity

      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<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");    //拼接_ViewBinding,并加载字节码文件

            //noinspection unchecked

            bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class); 

        //得到参数类型分别为字节码对象类型,和View类型的构造函数(对应ViewBinding里的构造函数)

            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); 

    //将字节码对象及对应ViewBinding的构造函数存起来,注意这里的字节码对象是Activity的不是ViewBinding的

    return bindingCtor;}

constructor.newInstance()的源码

public T newInstance(Object ... initargs)    throws InstantiationException, IllegalAccessException,          IllegalArgumentException, InvocationTargetException{

        if (serializationClass == null) { 

        //在上面得到构造函数对象时,传入了View.class,又因为 Constructor有下面那个构造函数,所以得到的constructor对象的里

        //这个serializationClass 是不为空的,而实际是View.class

                return newInstance0(initargs);

        } else {

        // 所以会走这个方法去创建实例,创建出来之后会被强转为对应类型的实例对象,也就是上面所说的 viewbinding对象返回

        return (T) newInstanceFromSerialization(serializationCtor, serializationClass);    }}

Constructor的构造函数

private Constructor(Class<?> serializationCtor,    Class<?> serializationClass) {

        this.serializationCtor = serializationCtor;

        this.serializationClass = serializationClass;}

随着constructor.newInstance方法的调用完成,Activity_ViewBinding的构造方法也就走完了,那么对应的view也就赋值完毕,监听也设置完毕

至此bind结束,并且收到Unbinder的对象,用来在界面Destory时,调用unBind方法,进而调用子类实现中的unBind,将该置空的置空

最后,如果需要注入的成员是private修饰的,那么butterknife会报错,因为JVM不会因为是自动生成的代码就会让你操作本不该被你操作的东西。


流程图

下面深挖部分,偷个懒,已经有大神写好了,很详细很到位

原作者:顾修忠

链接:https://blog.csdn.net/ta893115871/article/details/52497297

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 最近项目不是很忙,因为项目用到了butterknife框架,所以进行了下系统的研究。研究下来呢发现这个框架真的是吊...
    我小时候真的可狠了阅读 315评论 0 2
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • "use strict";function _classCallCheck(e,t){if(!(e instanc...
    久些阅读 2,027评论 0 2
  • pyspark.sql模块 模块上下文 Spark SQL和DataFrames的重要类: pyspark.sql...
    mpro阅读 9,442评论 0 13
  • 出差,一群人挤牙缝的一点时间,玩。 回程却下起了小雨,在接近凌晨的夜里,伴着风,有点凉飕飕的。 赶紧扣上扣子,最漏...
    泥脚书虫阅读 214评论 0 0