背景资料:
源码版本: 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生成这些文件
在...uildintermediatesclassesdebugcomexampleutterknifelibrary下发现class文件
在...uildgeneratedsourceaptdebugcomexampleutterknifelibrary发现生成的_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在编译的时候生成的 uildgeneratedsourceaptdebug…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文件放在 uildgeneratedsourceaptdebug下,然后通过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.”至此原理大致懂了,但是很多细节并不懂,不过能站在宏观的角度看问题感觉不错。