背景资料:
源码版本: ButterKnife 8.5.1
编译工具: Android Studio 2.2.1
java版本: 1.8.0_101_b13
在这篇文章的前面可能有些混乱,那是因为一直在找思路,不会去特意整理,这样才能体现我的思考过程。
Java Annotation processing 是javac中用于编译时扫描和解析Java注解的工具
自定义注解,并且自己定义解析器来处理它们。Annotation processing是在编译阶段执行的,它的原理就是读入Java源代码,解析注解,然后生成新的Java代码。新生成的Java代码最后被编译成Java字节码,注解解析器(Annotation Processor)不能改变读入的Java 类,比如不能加入或删除Java方法
ButterKnife 主要是实例化 View 或者给某个 View 添加各种事件 Listenters,那为什么在编译的时候会跑ButterKnifeProcessor这个类呢? 在哪里定义?
Activity启动时,ButterKnife.bind(this) 是如何加载对应的ViewBinder类中的方法的?
对编译原理 加载原理 运行原理 不大懂
手动打包 生成R文件 编译 打包 签名 Zipalign Upload Run,这些手动做了一下,发现AS中可以Build生成这些文件
在...\build\intermediates\classes\debug\com\example\butterknife\library下发现class文件
在...\build\generated\source\apt\debug\com\example\butterknife\library发现生成的_ViewBinding.java文件。
注解就是一个继承自`java.lang.annotation.Annotation`的接口。
简单来说就是java通过动态代理的方式为你生成了一个实现了"接口"`TestAnnotation`的实例(对于当前的实体来说,例如类、方法、属性域等,这个代理对象是单例的),然后对该代理实例的属性赋值,这样就可以在程序运行时(如果将注解设置为运行时可见的话)通过反射获取到注解的配置信息。
ButterKnife 工作流程
当你编译你的Android工程时,ButterKnife工程中 ButterKnifeProcessor 类的 process() 方法会执行以下操作:
- 开始它会扫描Java代码中所有的ButterKnife注解 @Bind 、 @OnClick 、 @OnItemClicked 等
- 当它发现一个类中含有任何一个注解时, ButterKnifeProcessor 会帮你生成一个Java类,名字类似 <className>$$ViewBinder ,这个新生成的类实现了 ViewBinder<T> 接口
- 这个 ViewBinder 类中包含了所有对应的代码,比如 @Bind 注解对应 findViewById() , @OnClick 对应了 view.setOnClickListener() 等等
- 最后当Activity启动 ButterKnife.bind(this) 执行时,ButterKnife会去加载对应的 ViewBinder 类调用它们的 bind() 方法
资料看的差不多了,开始分析, 在Activity中使用代码
@BindView(R.id.title)
TextView title;
@BindView(R.id.subtitle)
TextView subtitle;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
ButterKnife.bind(this);
点击bind()方法进入ButterKnife类
@NonNull
@UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
接着点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 == null) {
return Unbinder.EMPTY;
}
//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
try {
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);
}
}
这里的BINDINGS是一个Map<Class<?>, Constructor<? extends Unbinder>> 集合
@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 {
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;
}
这个集合会将APT在编译的时候生成的 \build\generated\source\apt\debug\…SimpleActivity_ViewBinding.class, 源代码如下
public class SimpleActivity_ViewBinding implements Unbinder {
private SimpleActivity target;
private View view2130968578;
private View view2130968579;
@UiThread
public SimpleActivity_ViewBinding(SimpleActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public SimpleActivity_ViewBinding(final SimpleActivity target, View source) {
this.target = target;
View view;
target.title = Utils.findRequiredViewAsType(source, R.id.title, "field 'title'", TextView.class);
target.subtitle = Utils.findRequiredViewAsType(source, R.id.subtitle, "field 'subtitle'", TextView.class);
view = Utils.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', and method 'sayGetOffMe'");
target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class);
view2130968578 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.sayHello();
}
});
view.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View p0) {
return target.sayGetOffMe();
}
});
view = Utils.findRequiredView(source, R.id.list_of_things, "field 'listOfThings' and method 'onItemClick'");
target.listOfThings = Utils.castView(view, R.id.list_of_things, "field 'listOfThings'", ListView.class);
view2130968579 = view;
((AdapterView<?>) view).setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> p0, View p1, int p2, long p3) {
target.onItemClick(p2);
}
});
target.footer = Utils.findRequiredViewAsType(source, R.id.footer, "field 'footer'", TextView.class);
target.headerViews = Utils.listOf(
Utils.findRequiredView(source, R.id.title, "field 'headerViews'"),
Utils.findRequiredView(source, R.id.subtitle, "field 'headerViews'"),
Utils.findRequiredView(source, R.id.hello, "field 'headerViews'"));
Context context = source.getContext();
Resources res = context.getResources();
target.butterKnife = res.getString(R.string.app_name);
target.fieldMethod = res.getString(R.string.field_method);
target.byJakeWharton = res.getString(R.string.by_jake_wharton);
target.sayHello = res.getString(R.string.say_hello);
}
@Override
@CallSuper
public void unbind() {
SimpleActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.title = null;
target.subtitle = null;
target.hello = null;
target.listOfThings = null;
target.footer = null;
target.headerViews = null;
view2130968578.setOnClickListener(null);
view2130968578.setOnLongClickListener(null);
view2130968578 = null;
((AdapterView<?>) view2130968579).setOnItemClickListener(null);
view2130968579 = null;
}
}
这里让我郁闷的是这东西是怎么生成的,注解只是一个接口,@BindView虽然这样写但是如果开发人员不使用APT工具来提取和处理Annotation信息,《疯狂java讲义》上说可以使用反射获取该类的AnnotatedElement接口来接受注解的信息,因为它是Class,Method,Constructor的父接口,但是这只是获取,属于APT(Annotation Processing Tool)的一部分,APT是一种注解处理工具,在前面说过,要使用工具来提取和处理Annotation信息,否则注解毫无意义,APT对源码进行检测,找出Annotation信息,生成额外的源文件和其他文件(由APT编写者决定),APT会编译生成的源文件和原来的源文件一起合成一个新的class文件,简单理解是APT可以在编译期间做一些其他维护工作,那问题是APT如何编写呢?
Java提供的javac.exe工具有一个-processor选项,可指定一个Annotation处理器,该处理器需要实现javax.annotation.processing包下的Processor接口,为了方便一般继承AbstractProcessor来实现处理器。可以使用命令java -processorpath ‘XXXAnnotationProcessor’ XXX.java
现在大致知道了一些东西了,但是有些混乱,重新来看一下,@BindView会通过APT生成额外的class文件放在 \build\generated\source\apt\debug\下,然后通过ButterKnife.bind(this)来将代码合并在一起。 那么现在问题是如何以及何时调用APT呢
如何: 在源码中找到了一个ButterKnifeProcessor类继承自AbstractProcessor
@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
何时: 在注解处理器类中有个注解@AutoService(Processor.class) 这个是google开发的用来解决APT更加方便使用问题的
在github原话是这样的
“AutoService will generate the file
META-INF/services/javax.annotation.processing.Processor
in the output classes folder. In the case of javax.annotation.processing.Processor, if this metadata file is included in a jar, and that jar is on javac's classpath, then
javac
willautomatically load it, and include it in its normal annotation processing environment.”
至此原理大致懂了,但是很多细节并不懂,不过能站在宏观的角度看问题感觉不错。