美文网首页Android技术知识Android开发经验谈Android开发
依赖注入--Butterknife深入理解与源码解析

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

作者: 沐沐小风 | 来源:发表于2019-01-09 22:06 被阅读11次

    依赖注入(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不会因为是自动生成的代码就会让你操作本不该被你操作的东西。

    流程图

    相关文章

      网友评论

        本文标题:依赖注入--Butterknife深入理解与源码解析

        本文链接:https://www.haomeiwen.com/subject/mplwrqtx.html