ButterKnife源码研究一宏观

来源:互联网 发布:sql delete select语句 编辑:程序博客网 时间:2024/06/05 08:23
背景资料:
源码版本: 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@UiThreadpublic 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@UiThreadprivate 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.”

至此原理大致懂了,但是很多细节并不懂,不过能站在宏观的角度看问题感觉不错。

官网:https://github.com/JakeWharton/butterknife
参考1:http://www.gongmingqm10.net/blog/2016/05/11/butterknife-principle/
参考2:http://bxbxbai.github.io/2016/03/12/how-butterknife-works/
参考3:http://beginnersbook.com/2014/09/java-annotations/
参考4: https://github.com/google/auto/tree/master/service
参考5: https://www.race604.com/annotation-processing/

I'm fish,I'm on.
1 0
原创粉丝点击