官方传送门
本文基于compile 'com.jakewharton:butterknife:8.6.0'分析
1.原理
相信我们做Android都用过或了解过ButterKnife,毕竟也是github上星星过万的库。说ButterKnife原理之前,一般这样的注入框架(比如xUtils)是运行时注解,声明注解的Retention为Runtime,即注解的保留时期为运行时,然后通过反射来完成view的绑定,这样比较耗费资源,影响性能。而ButterKnife它是基于编译时的注解,即注解的Retention为Class,利用Java Annotation Processor技术自定义注解,注册注解处理器,然后生成相应的辅助类,在运行时,通过bind方法完成绑定。
2.引入
1.在项目中引入
在module下的build.gradle的dependencies中引入
dependencies {
compile 'com.jakewharton:butterknife:8.6.0'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.6.0'
}
2.在library中引入
将插件加入到buildscript
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.jakewharton:butterknife-gradle-plugin:8.6.0'
}
}
在module下的build.gradle中apply plugin
apply plugin: 'com.jakewharton.butterknife'
3.基本用法
直接看代码
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv_text)
TextView mTvText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
@OnClick(R.id.tv_text)
protected void onClick() {
mTvText.setText("ButterKnife");
}
}
这里就完成了我们经常写的findViewById以及setOnclickListener的工作。
PS:Butter Knife如果是在library中使用,需要将R转为R2,原因是在library里面由于R类中变量不再是final类型而导致各种问题。
4.源码分析
1.先看我们在入口时的bind
方法,返回了一个Unbinder对象
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
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 {
// 创建了一个Unbinder对象,并在构造方法中完成了初始化
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);
}
}
2.再看findBindingConstructorForClass
方法,它返回了我们绑定目标类的一个构造器
@Nullable @CheckResult @UiThread
private static Constructor extends Unbinder> findBindingConstructorForClass(Class> cls) {
// 缓存中取
Constructor extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null) {
if (debug) Log.d(TAG, "HIT: Cached in binding map.");
return bindingCtor;
}
String clsName = cls.getName();
if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return null;
}
try {
// 这里获得了一个clsName + "_ViewBinding"的class
Class> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
//noinspection unchecked
// 获得构造器
bindingCtor = (Constructor extends Unbinder>) bindingClass.getConstructor(cls, View.class);
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);
return bindingCtor;
}
这里如果我们是在Activity中bind的话,那么loadClass里的name就是"Activity_Viewbinding",这个类是怎么来的呢?就是我们提到的Java Annotation Processor,在编译时生产了这样的一个类。
3.我们看下在build下的classes里确实有一个MainActivity_ViewBinding.class
再看下这个类里面到底干了什么,是我们非常熟悉的findViewById
、setOnClickListener
方法
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
private View view2131427422;
@UiThread
public MainActivity_ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public MainActivity_ViewBinding(final MainActivity target, View source) {
this.target = target;
// 这里就是调用了source.findViewById,完成了View的初始化
View view = Utils.findRequiredView(source, 2131427422, "field \'mTvText\' and method \'onClick\'");
// 给我们在MainActivity中@BindView的mTvText 赋值,这里mTvText就成功被初始化了
target.mTvText = (TextView)Utils.castView(view, 2131427422, "field \'mTvText\'", TextView.class);
this.view2131427422 = view;
// 这里
view.setOnClickListener(new DebouncingOnClickListener() {
public void doClick(View p0) {
target.onClick();
}
});
}
@CallSuper
public void unbind() {
MainActivity target = this.target;
if(target == null) {
throw new IllegalStateException("Bindings already cleared.");
} else {
this.target = null;
target.mTvText = null;
this.view2131427422.setOnClickListener((OnClickListener)null);
this.view2131427422 = null;
}
}
}
MainActivity_ViewBinding
它实现了Unbinder
接口,在构造方法中进行了我们的findViewById
以及setOnClickListener
等操作。
4.MainActivity_ViewBinding
这个类是怎么生成的呢?我们来看一下ButterKnifeProcessor
这个类,它继承了AbstractProcessor
它的init方法
@Override public synchronized void init(ProcessingEnvironment env) {
super.init(env);
String sdk = env.getOptions().get(OPTION_SDK_INT);
if (sdk != null) {
try {
this.sdk = Integer.parseInt(sdk);
} catch (NumberFormatException e) {
env.getMessager()
.printMessage(Kind.WARNING, "Unable to parse supplied minSdk option '"
+ sdk
+ "'. Falling back to API 1 support.");
}
}
elementUtils = env.getElementUtils();
typeUtils = env.getTypeUtils();
filer = env.getFiler();
try {
trees = Trees.instance(processingEnv);
} catch (IllegalArgumentException ignored) {
}
}
ProcessingEnviroment参数提供很多有用的工具类Elements, Types和Filer。Types是用来处理TypeMirror的工具类,Filer用来创建生成辅助文件。至于ElementUtils嘛,其实ButterKnifeProcessor在运行的时候,会扫描所有的Java源文件,然后每一个Java源文件的每一个部分都是一个Element,比如一个包、类或者方法。
再看这个类中最重要的process方法
@Override public boolean process(Set extends TypeElement> elements, RoundEnvironment env) {
Map bindingMap = findAndParseTargets(env);
for (Map.Entry entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
JavaFile javaFile = binding.brewJava(sdk);
try {
// 生成文件到apt目录下,也就是我们上面提到的MainActivity_ViewBinding类
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return false;
}
这个方法的作用主要是扫描、处理我们程序中的注解,BindingSet通过JavaPoet的Api构建Java类,最后通过Filer写入到文件中,完成了上面提到的辅助类的生成。
5.总结
ButterKnife的依赖注入其实就是通过ButterKnifeProcessor这个注解处理器生成辅助类,然后在使用的地方调用bind方法时,就会通过反射创建出辅助类对象,在辅助类的构造方法中完成View的初始化等操作。