butterknife源码分析系列:
谈一谈Java的注解
http://blog.csdn.net/u012933743/article/details/54909590
如何处理注解—反射与注解处理器
http://blog.csdn.net/u012933743/article/details/54972050
代码分析
http://blog.csdn.net/u012933743/article/details/64437988
前面两篇讲解了注解的定义,以及如何用反射与注解处理器的方法来处理注解。在第二篇文章中,我们用反射的方式模拟了butterknife的findViewById来简化代码,同时,我们也说过butterknife其实是用注解处理器来实现的。
虽然butterknife的源码解析文章已有许多,这里借其肩膀,总结总结。
流程介绍
上文我们详细介绍了注解处理器,这里再结合butterknife再次强调下。在编译源文件时,会分析扫描注解,当扫描到butterknife定义的@BindView、@OnClick等注解时,会使用JavaPoet来生成代码。生成后的文件会再次分析,直到没有分析到需要处理的注解位置。
JavaPoet简介
Poet译为诗人,JavaPoet可以帮助便捷地生成代码,而不是手动繁琐的拼接语句。简要介绍下比较关键的几个类:
- MethodSpec 代表一个构造函数或方法声明。
- TypeSpec 代表一个类,接口,或者枚举声明。
- FieldSpec 代表一个成员变量,一个字段声明。
详细的使用方法可以看github上的介绍:
从结果入手
直接从源码分析容易一头雾水。既然ButterKnife会在编译时生成代码,那我们从结果入手,看看生成的代码长什么样。
源文件:
public class MainActivity extends Activity {
@BindView(R.id.tv_title)
public TextView tvTitle;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
@OnClick(R.id.tv_title)
void titleClick() {
}
}
编译生成的文件可以在build/source/apt下可以看到:
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
private View view2131034112;
@UiThread
public MainActivity_ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public MainActivity_ViewBinding(final MainActivity target, View source) {
this.target = target;
View view;
view = Utils.findRequiredView(source, R.id.tv_title, "field 'tvTitle' and method 'titleClick'");
target.tvTitle = Utils.castView(view, R.id.tv_title, "field 'tvTitle'", TextView.class);
view2131034112 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.titleClick();
}
});
}
@Override
@CallSuper
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.tvTitle = null;
view2131034112.setOnClickListener(null);
view2131034112 = null;
}
}
注意到源文件名是MainActivity,而生成的文件是MainActivity_ViewBinding。
在构造函数内,使用
target.tvTitle = Utils.findRequiredViewAsType(source, R.id.tv_title, "field 'tvTitle'", TextView.class);
利用Utils.findRequiredViewAsType得到的结果赋值到我们定义的TextView(tvTitle)上,这也解释了为什么tvTitle需要用public来修饰。
进一步看看findRequiredViewAsType。
public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who,
Class<T> cls) {
View view = findRequiredView(source, id, who);
return castView(view, id, who, cls);
}
castView方法是将得到的View转化成具体的子View,这里是TextView。而findRequiredView里进行了findViewById的操作。
这里我们可能会有疑问,在生成的MainActivity_ViewBinding的构造方法使用到MainActivity,而我们在使用ButterKnife时会使用ButterKnife.bind(this)将Activity传递到ButterKnife里,这之间是怎么一个过程?
进去ButterKnife.bind(this)看看。
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
根据activity得到DecorView,再传递到createBinding。
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 &