ButterKnife源码研究一宏观

背景资料:
源码版本: 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.”

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

I'm fish,I'm on.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值