Butterknife 源码剖析(一)

源码剖析——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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容