Butterknife 源码剖析(一)

作者: spiritTalk | 来源:发表于2016-03-29 17:59 被阅读2417次

    源码剖析——ButterKnife的工作流程

    butterknife是一个android视图快速注入库,它通过给view字段添加java注解,可以让我们丢掉findViewById()来获取view的方法,从而简化了代码。本文是基于7.0.1版本的剖析。

    首先来看下注解方式:
    1、标准Annotation
    标准的Annotation,我们经常用的@Override、@Deprecated、@SuppressWarnings,这些是java自带的几个Annotation,分别表示重写函数、不鼓励使用、忽略某项Warning。

    2、元Annotation
    元Annotation是指用来定义Annotation的Annotation,一般我们自定义Annotation时就会用到。主要包括以下几个:

    • @Documented是否会保存到Javadoc文档中
    • @Retention保留时间,可选值SOURCE(源码时),CLASS(编译时),RUNTIME(运行时),默认为CLASS,值为SOURCE大都为MarkAnnotation,这类Annotation大都用来校验,比如Override,Deprecated,SuppressWarnings
    • @Target可以用来修饰哪些程序元素,如TYPE,METHOD,CONSTRUCTOR,FIELD,PARAMETER等,未标注则表示可修饰所有
    • @Inherited是否可以被继承,默认为false

    OK,我们来看看butterknife的@Bind注解,@Retention是编译时,@Target是字段。

    @Retention(CLASS) @Target(FIELD)
    public @interface Bind {
     /** View ID to which the field will be bound. */
     int[] value();
    }
    

    编译时 Annotation 指 @Retention 为 CLASS 的 Annotation,由apt(Annotation Processing Tool) 解析自动解析。ButterKnife便是用了Java Annotation Processing技术,就是在Java代码编译成Java字节码的时候就已经处理了@Bind、@OnClick(ButterKnife还支持很多其他的注解)这些注解了。

    你可以你定义注解,并且自己定义解析器来处理它们。Annotation processing是在编译阶段执行的,它的原理就是读入Java源代码,解析注解,然后生成新的Java代码。新生成的Java代码最后被编译成Java字节码,注解解析器(Annotation Processor)不能改变读入的Java 类,比如不能加入或删除Java方法。

    ButterKnife 工作流程
    当你编译你的Android工程时,ButterKnife工程中ButterKnifeProcessor类的process()方法会执行以下操作:

    • 开始它会扫描Java代码中所有的ButterKnife注解@Bind、@OnClick、@OnItemClicked等。
    • 当它发现一个类中含有任何一个注解时,ButterKnifeProcessor会帮你生成一个Java类,名字类似$$ViewBinder,这个新生成的类实现了ViewBinder接口。
    • 这个ViewBinder类中包含了所有对应的代码,比如@Bind注解对应findViewById(), @OnClick对应了view.setOnClickListener()等等。
    • 最后当Activity启动ButterKnife.bind(this)执行时,ButterKnife会去加载对应的ViewBinder类调用它们的bind()方法。

    来看个使用butterknife的例子:

    @Bind(R.id.button)Button mButton;
    
    @Override
    protected voidon Create(Bundlesaved InstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
    }
    
    @OnClick(R.id.button) void clickButton() {
        Toast.makeText(MainActivity.this,“HelloWorld!”,Toast.LENGTH_SHORT).show();
    }
    

    onCreate()方法中调用了ButterKnife.bind(this)我们点进去看看:

    public static void bind(Activity target) {
        bind(target, target, Finder.ACTIVITY);
    }
    

    调用了bind方法,参数分别为activity和Finder,继续点进去:

    static void bind(Object target, Object source, Finder finder) {
      Class<?> targetClass = target.getClass();
      try {
        if (debug) Log.d(TAG, "Looking up view binder for " + targetClass.getName());
        //查找ViewBinder类
        ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass);
        if (viewBinder != null) {
          viewBinder.bind(finder, target, source);
        }
      } catch (Exception e) {
        throw new RuntimeException("Unable to bind views for " + targetClass.getName(), e);
      }
     }
    

    第5行findViewBinderForClass应该是去查找ViewBinder类,点进去:

    private static ViewBinder<Object> findViewBinderForClass(Class<?> cls)
          throws IllegalAccessException, InstantiationException {
        //从内存中查找
        ViewBinder<Object> viewBinder = BINDERS.get(cls);
        if (viewBinder != null) {
          if (debug) Log.d(TAG, "HIT: Cached in view binder map.");
          return viewBinder;
        }
        String clsName = cls.getName();
        //检查是否为framework class
        if (clsName.startsWith(ANDROID_PREFIX) || clsName.startsWith(JAVA_PREFIX)) {
          if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
          return NOP_VIEW_BINDER;
        }
        try {
          //实例化“MainActivity$$ViewBinder”这样的类
          Class<?> viewBindingClass = Class.forName(clsName + ButterKnifeProcessor.SUFFIX);
          //noinspection unchecked
          viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance();
          if (debug) Log.d(TAG, "HIT: Loaded view binder class.");
        } catch (ClassNotFoundException e) {
          if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
          //异常,则去父类查找
          viewBinder = findViewBinderForClass(cls.getSuperclass());
        }
        //放入内存并返回
        BINDERS.put(cls, viewBinder);
        return viewBinder;
      }
    

    第3行是首先从内存中查找,看BINDERS的定义:

    static final Map<Class<?>, ViewBinder<Object>> BINDERS = new LinkedHashMap<Class<?>, ViewBinder<Object>>();
    

    内存中没有,第9行判断如果framework class,就放弃查找并返回ViewBinder的空实现实例。

      public static final String ANDROID_PREFIX = "android.";
      public static final String JAVA_PREFIX = "java.";
      static final ViewBinder<Object> NOP_VIEW_BINDER = new ViewBinder<Object>() {
        @Override public void bind(Finder finder, Object target, Object source) { }
        @Override public void unbind(Object target) { }
      };
    

    第14行是去实例化“MainActivity$$ViewBinder”这样的类,如果viewBinder不为空,放入缓存并返回;如果ClassNotFoundException异常则去父类查找。

      public static final String SUFFIX = "$$ViewBinder";
    

    我们回到上面的bind()方法,查找到的ViewBinder不为空,则执行viewBinder.bind(finder,target,source)方法。

    当我们编译运行后,在build文件夹下找到MainActivity$$ViewBinder.java类,具体代码为:

    public class MainActivity$$ViewBinder<T extends com.spirittalk.rxjavatraining.MainActivity> implements ViewBinder<T> {
      @Override public void bind(final Finder finder, final T target, Object source) {
        View view;
        view = finder.findRequiredView(source, 2131492970, "field 'mButton' and method 'clickButton'");
        target.mButton = finder.castView(view, 2131492970, "field 'mButton'");
        view.setOnClickListener(
          new butterknife.internal.DebouncingOnClickListener() {
            @Override public void doClick(android.view.View p0) {
              target.clickButton();
            }
          });
      }
    
      @Override public void unbind(T target) {
        target.mButton = null;
      }
    }
    

    最终,bind()方法会执行到MainActivity$$ViewBinder.java类中的bind()方法。

    那么,MainActivity$$ViewBinder.java类是如何生成的呢?这个在下篇 编译期解析注解、生成java代码流程 中揭晓。

    在上面的过程中可以看到,为什么你用@Bind、@OnClick等注解标注的属性或方法必须是public或protected的,因为ButterKnife是通过ExampleActivity.this.button来注入View的。

    为什么要这样呢?有些注入框架比如roboguice你是可以把View设置成private的,答案就是性能。如果你把View设置成private,那么框架必须通过反射来注入View,一个很大的缺点就是在Activity运行时大量使用反射会影响App的运行性能,造成卡顿以及生成很多临时Java对象更容易触发GC,不管现在手机的CPU处理器变得多快,如果有些操作会影响性能,那么是肯定要避免的,这就是ButterKnife与其他注入框架的不同。

    转载请标明出处:http://www.jianshu.com/p/95d4f0eb6027

    相关文章

      网友评论

      本文标题:Butterknife 源码剖析(一)

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