源码剖析——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与其他注入框架的不同。