• 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生成这些文件
    在...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.”
     
  • 相关阅读:
    Collection<E>接口
    Iterable<T>接口
    Iterator<E>接口
    js图片压缩
    js计算最大公约数和最小公倍数
    canvas原生js写的贪吃蛇
    左右两栏div布局,高度自适应
    vue的图片路径,和背景图片路径打包后错误解决
    职责链模式
    js多个异步请求,按顺序执行next
  • 原文地址:https://www.cnblogs.com/zeroones/p/8600193.html
Copyright © 2020-2023  润新知