美文网首页程序员
ButterKnife源码解读

ButterKnife源码解读

作者: tomcat_pikaqiu | 来源:发表于2018-10-19 18:32 被阅读0次

    Butterknife是一款利用注解方式实现的框架,为Android项目提供了较好的解耦。下面针对这款大神作品,做下简单分析。

    核心成员介绍

    1.Butterknife-annotations

    注解库,里面包含了所有用到的注解类
    例如BindView.java

    @Retention(RUNTIME) @Target(FIELD)
    public @interface BindView {
      /** View ID to which the field will be bound. */
      @IdRes int value();
    }
    
    2.Butterknife-compiler

    负责在编译过程中生成view_Binding文件
    核心成员类 ButterKnifeProcessor.java
    我们来看一下这个类的源代码

    public final class ButterKnifeProcessor extends AbstractProcessor
    

    这个类继承了AbstractProcessor,正是利用了apt的原理,如对apt不了解,建议先百度学习一下。

    接下来再看下ButterKnifeProcessor的核心方法

      @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
        Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
    
        for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
          TypeElement typeElement = entry.getKey();
          BindingSet binding = entry.getValue();
    
          JavaFile javaFile = binding.brewJava(sdk, debuggable, useLegacyTypes);
          try {
            javaFile.writeTo(filer);
          } catch (IOException e) {
            error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
          }
        }
    
        return false;
      }
    

    通过代码可以发现process过程包含了以下操作

    1. 通过编译期查找已经在代码中添加注解标识的类,得到binding元素的Map
      binding注解过的元素例如
    @BindView(R.id.textview)
    TextView textview
    
    @OnClick(R.id.click)
    void onClick(View view)
    

    等等。

    1. 从得到的Map中挨个取出每个BindingSet
    2. 根据binding对象生成JavaFile对象,然后写入文件(xxx_viewBinding.java)

    ps:简单看下两个过程

    1. findAndParseTargets查找过程
      private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
        Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
        Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
        ……
        省略一部分代码
        ……
            // Process each @BindView element.
        for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
          // we don't SuperficialValidation.validateElement(element)
          // so that an unresolved View type can be generated by later processing rounds
          try {
            parseBindView(element, builderMap, erasedTargetNames);
          } catch (Exception e) {
            logParsingError(element, BindView.class, e);
          }
        }
    
        // Process each @BindViews element.
        for (Element element : env.getElementsAnnotatedWith(BindViews.class)) {
          // we don't SuperficialValidation.validateElement(element)
          // so that an unresolved View type can be generated by later processing rounds
          try {
            parseBindViews(element, builderMap, erasedTargetNames);
          } catch (Exception e) {
            logParsingError(element, BindViews.class, e);
          }
        }
        ……
        省略一部分代码
        ……
    }
    

    这里面实际上是分别对@BindView, @BindViews, @BindString等类型做了查找

    1. BindingSet的生成JavaFile过程(brewJava)
      JavaFile brewJava(int sdk, boolean debuggable, boolean useLegacyTypes) {
        TypeSpec bindingConfiguration = createType(sdk, debuggable, useLegacyTypes);
        return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
            .addFileComment("Generated code from Butter Knife. Do not modify!")
            .build();
      }
    

    这里通过createType()方法得到了TypeSpec对象,然后再通过建造者模式创建了JavaFile对象,并返回。
    那么,再看下createType()具体做了什么

      private TypeSpec createType(int sdk, boolean debuggable, boolean useLegacyTypes) {
        TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
            .addModifiers(PUBLIC);
        if (isFinal) {
          result.addModifiers(FINAL);
        }
    
        if (parentBinding != null) {
          result.superclass(parentBinding.bindingClassName);
        } else {
          result.addSuperinterface(UNBINDER);
        }
    
        if (hasTargetField()) {
          result.addField(targetTypeName, "target", PRIVATE);
        }
    
        if (isView) {
          result.addMethod(createBindingConstructorForView(useLegacyTypes));
        } else if (isActivity) {
          result.addMethod(createBindingConstructorForActivity(useLegacyTypes));
        } else if (isDialog) {
          result.addMethod(createBindingConstructorForDialog(useLegacyTypes));
        }
        if (!constructorNeedsView()) {
          // Add a delegating constructor with a target type + view signature for reflective use.
          result.addMethod(createBindingViewDelegateConstructor(useLegacyTypes));
        }
        result.addMethod(createBindingConstructor(sdk, debuggable, useLegacyTypes));
    
        if (hasViewBindings() || parentBinding == null) {
          result.addMethod(createBindingUnbindMethod(result, useLegacyTypes));
        }
    
        return result.build();
      }
    

    这里主要是定义了即将创建的viewBinding类的属性,并且,所有的类都实现了Unbinder接口。这点很重要,因为后面绑定时,得到的正是这个Unbinder对象。

    3.Butterknife

    接下来看下绑定时用到的操作类。
    当我们要绑定控件时,可以在组件的生命周期开始的时候调用Butterknife.bind()方法
    例如

    public class TestActivity extends Activity {
    
        @Override
        onCreate(Bundle savedInstanceState) {
           super.onCreate(savedInstanceState);
           unbinder = ButterKnife.bind(this);
        }
    }
    

    那看下ButterKnife的bind()方法具体做了什么

      @NonNull @UiThread
      public static Unbinder bind(@NonNull Activity target) {
        View sourceView = target.getWindow().getDecorView();
        return bind(target, sourceView);
      }
    

    实际上获取了Activity的根View,也就是decorView。
    再往下看

      @NonNull @UiThread
      public static Unbinder bind(@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);
        }
      }
    

    在此有两步操作

    1. 通过findBindingConstructorForClass()方法得到一个构造器对象
    2. 调用构造器Constructor的newInstance()方法得到Unbinder对象并返回

    接下来就看findBindingConstructorForClass()
    很显然,这一步操作跟之前Butterknife-compiler里面生成的viewBinding文件有关,那么,我们来看代码

      @VisibleForTesting
      static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
    
      @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.")
            || clsName.startsWith("androidx.")) {
          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;
      }
    

    这里有3步操作

    1. 从BINDINGS缓存中读取Unbinder的构造器对象,BINDINGS是个LinkedHashMap,很好的满足缓存功能,若缓存中已有,则表示已经加载过。
    2. 缓存中没有,则通过传进来的Class对象(例如TestActivity.class)调用类加载器ClassLoader的loadClass()方法得到TestActivity_ViewBinding.class对象。这个TestActivity_ViewBinding.class也就是在编译时生成的binding文件生成的。
    3. 将加载出来的ViewBinding的class实例放入到缓存中

    由此一来,就完成了绑定操作

    总结

    总结来说,分为两大部分,编译时,和运行时发生的操作。

    1. 编译时通过ButterKnifeProcessor生成ViewBinding文件
    2. 运行时,通过ButterKnife.bind()加载ViewBinding文件并绑定得到Unbinder对象

    转载请注明出处,谢谢合作。作者,皮卡丘
    附上一只大猫图

    WechatIMG5.jpeg

    相关文章

      网友评论

        本文标题:ButterKnife源码解读

        本文链接:https://www.haomeiwen.com/subject/gasuzftx.html