美文网首页
ButterKnife原理分析

ButterKnife原理分析

作者: andev009 | 来源:发表于2018-01-18 11:12 被阅读24次

    ButterKnife源码地址

    ButterKnife的分析文章很多了,这里只是简单分析原理,不想看代码,可以直接拉到后面总结。
    ButterKnife最简单的使用方法如下:

    public class SimpleActivity extends Activity {
        @BindView(R.id.title) 
        TextView title;
    
        @Override 
         protected void onCreate(Bundle savedInstanceState) {
               super.onCreate(savedInstanceState);
               setContentView(R.layout.simple_activity);
               ButterKnife.bind(this);
               title.setText("Butter Knife");
         }
    }
    

    从入口ButterKnife.bind(this)开始分析:

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

    首先获得Activity最顶层的DecorView,有了DecorView,后面就可以通过获得布局的所有View。往下走到了createBinding,参数是Activity和DecorView:

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

    createBinding首先要通过findBindingConstructorForClass去获得该Activity对应的binding类,binding类在build/generated/source/apt/debug或release的包下面,比如,上面的SimpleActivity,通过findBindingConstructorForClass方法找到的类是以上目录下的SimpleActivity_ViewBinding。这里先不用管findBindingConstructorForClass是怎么找到SimpleActivity_ViewBinding和SimpleActivity_ViewBinding是怎么生成的,看看找到后,做了什么事:return constructor.newInstance(target, source);找到后调用了构造方法。上面一大段无非是调用了SimpleActivity_ViewBinding的构造方法,来看看SimpleActivity_ViewBinding的代码:

    public class SimpleActivity_ViewBinding implements Unbinder {
         private SimpleActivity target;
         private View view2130968578;
    
         @UiThread
         public SimpleActivity_ViewBinding(final SimpleActivity target, View source) 
         {
              this.target = target;
              target.title = Utils.findRequiredViewAsType(source, R.id.title, "field 'title'", TextView.class); 
         }
         public void unbind() {
             SimpleActivity target = this.target;
             this.target = null;
             target.title = null;
          }
    }
    

    上面的target就是最初的SimpleActivity,看到target.title就知道为什么在SimpleActivity里TextView title为什么不能声明为private变量。target.title是通过
    Utils.findRequiredViewAsType获得的,看参数,findViewById需要的东西都有了,可见findRequiredViewAsType肯定是findViewById的封装方法,进去看看:

    public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who,
          Class<T> cls) {
        View view = findRequiredView(source, id, who);//获得R.id.title的View
        return castView(view, id, who, cls);//将上面的View转化成TextView类型
      }
    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.");
      }
    public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
        try {
          return cls.cast(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);
        }
      }
    

    上面代码一大把,其实就做了两件事,获得R.id.title的View,将View转化成真正的类型TextView,完成了id到View的绑定。

    以上就是ButterKnife的原理,再总结下工作流程:
    1、用@BindView注解View的ID和View变量。
    2、apt会根据注解生成该Activity对应的Activity_ViewBinding类。
    3、在Activity中调用 ButterKnife.bind(this)。
    4、ButterKnife通过bind参数可以获得当前的Activity类名和最顶层的DecorView。
    5、通过类名获得该类对应的Activity_ViewBinding类,并调用Activity_ViewBinding的构造方法初始化。
    6、在Activity_ViewBinding的构造方法中通过封装的findViewById获得该id对应的View,并通过cast方法转化成真正的View类型,比如TextView。

    额外的话题
    上面有两个问题我们先没有管:
    1、SimpleActivity_ViewBinding是怎么生成的?
    2、findBindingConstructorForClass是怎么找到SimpleActivity_ViewBinding?
    第一个问题其实是java注解的应用,比如@BindView,apt通过这些注解生成一个新的java文件,当然生成逻辑得自己写,这里比较复杂,可以参考源码。
    第二个问题看源码:

    static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap();
    
    private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
            Constructor<? extends Unbinder> bindingCtor = (Constructor)BINDINGS.get(cls);
            if(bindingCtor != null) {
                if(debug) {
                    Log.d("ButterKnife", "HIT: Cached in binding map.");
                }
    
                return bindingCtor;
            } else {
                String clsName = cls.getName();
                if(!clsName.startsWith("android.") && !clsName.startsWith("java.")) {
                    try {
                        Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
                        bindingCtor = bindingClass.getConstructor(new Class[]{cls, View.class});
                        if(debug) {
                            Log.d("ButterKnife", "HIT: Loaded binding class and constructor.");
                        }
                    } catch (ClassNotFoundException var4) {
                        if(debug) {
                            Log.d("ButterKnife", "Not found. Trying superclass " + cls.getSuperclass().getName());
                        }
    
                        bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
                    } catch (NoSuchMethodException var5) {
                        throw new RuntimeException("Unable to find binding constructor for " + clsName, var5);
                    }
    
                    BINDINGS.put(cls, bindingCtor);
                    return bindingCtor;
                } else {
                    if(debug) {
                        Log.d("ButterKnife", "MISS: Reached framework class. Abandoning search.");
                    }
    
                    return null;
                }
            }
        }
    

    首先在BINDINGS这个缓存map里去找对应的Activity_ViewBinding,找到了直接返回。没找到话往下走。这时候因为有了Activity类名,要找到Activity_ViewBinding只需要找Activity类名+"_ViewBinding"后缀的类就行了,当然系统类不用找了,所以有个"android."和"java."前缀的判断,看下怎么找的:

    Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
    

    找到了Activity_ViewBinding类bindingClass,再获得构造方法:

    bindingCtor = bindingClass.getConstructor(new Class[]{cls, View.class});
    

    最后把找到的Activity_ViewBinding缓存起来:

    BINDINGS.put(cls, bindingCtor);
    

    返回Activity_ViewBinding的构造方法结束。

    相关文章

      网友评论

          本文标题:ButterKnife原理分析

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