美文网首页
ButterKnife原理以及应用

ButterKnife原理以及应用

作者: 鲁班0号 | 来源:发表于2019-05-05 00:34 被阅读0次

0.文章导入

ButterKnife算是一款知名老牌 Android 开发框架了,通过注解绑定视图,避免了 findViewById() 的操作,广受好评!下面我们从原理开始,一步一步解析黄油刀吧!

1.注解框架

见另外一篇内容,这里就跳过了:https://www.jianshu.com/writer#/notebooks/18080563/notes/42184171

2.原理分析

绑定黄油刀,需要在inflate之后调用ButterKnife.bind(this),那么分析以下bind方法到底干了什么事情。源码如下:

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

可见,首先获取getDecorView,这view是PhoneWindow对应的布局,是一个FrameLayout,DecorView内部又分为两部分,一部分是ActionBar,另一部分是ContentParent,即activity在setContentView对应的布局。然后调用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);
    }
  }

createBinding 方法中 主要是先找到类对应的构造器constructor,然后通过constructor来new一个新的对象。接下来看看findBindingConstructorForClass是怎么找到类的构造器的:

@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;
  }

先检查BINDINGS是否存在 Class 对应的 Constructor,如果存在则直接返回,否则去构造对应的 Constructor。其中BINDINGS是一个LinkedHashMap:
Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>(),缓存了对应的 Class 和 Constructor 以提高效率!
接下来看当不存在对应 Constructor 时如何构造一个新的,首先如果clsName是系统相关的,则直接返回 null,否则先创建一个新的 Class,再获取类名,clsName + "_ViewBinding",如:MainActivity_ViewBinding.class ,在获取类中的构造方法。这个构造方法中是怎么实现的呢?源码如下:

@UiThread
  public MainActivity_ViewBinding(MainActivity target, View source) {
    this.target = target;

    target.mTextView = Utils.findRequiredViewAsType(source, R.id.hello, "field 'mTextView'", TextView.class);
  }

好,我们明白了,原来在这里有通过id找到了target.mTextView。再看看 Utils.findRequiredViewAsType这个方法是怎么实现的。

  public static View findRequiredView(View source, @IdRes int id, String who) {
    View view = source.findViewById(id);
    if (view != null) {
      return view;
    }
    String name = getResourceEntryName(source, id);
    throw new IllegalStateException("Required view '"
        + name
        + "' with ID "
        + id
        + " for "
        + who
        + " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'"
        + " (methods) annotation.");
  }

恍然大悟,其实黄油刀帮你写了findViewById,仅此而已!

3.注解处理器

还记得刚才的MainActivity_ViewBinding.class是怎么生成的吗?要生成这个类就要先得到这个类必须的基础信息,这就涉及到了annotationProcessor技术,和 APT(Annotation Processing Tool)技术类似,它是一种注解处理器,项目编译时对源代码进行扫描检测找出存活时间为RetentionPolicy.CLASS的指定注解,然后对注解进行解析处理,进而得到要生成的类的必要信息,然后根据这些信息动态生成对应的 java 类。接下来分析一下butterknife-compiler 这个黄油刀的注解处理器,主要的方法是:process;

@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);
      try {
        javaFile.writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }
    return false;
  }

第一步是findAndParseTargets(env),这个方法是注解信息的扫描收集。这个方法有点长,只看看主要的流程部分。

private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
        Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
        Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();

        scanForRClasses(env);
        ......
        ......
        // 获取所有使用BindView注解的元素
        for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
            try {
                parseBindView(element, builderMap, erasedTargetNames);
            } catch (Exception e) {
                logParsingError(element, BindView.class, e);
            }
        }
        ......
        ......
        // 将builderMap中的数据添加到队列中
        Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries =
                new ArrayDeque<>(builderMap.entrySet());
        Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();
        while (!entries.isEmpty()) {
            // 出队列
            Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst();

            TypeElement type = entry.getKey();
            BindingSet.Builder builder = entry.getValue();
            // 查找当前类元素的父类元素
            TypeElement parentType = findParentType(type, erasedTargetNames);
            // 如果没找到则保存TypeElement和对应BindingSet
            if (parentType == null) {
                bindingMap.put(type, builder.build());
            } else {
                BindingSet parentBinding = bindingMap.get(parentType);
                if (parentBinding != null) {
                    // 如果找到父类元素,则给当前类元素对应的BindingSet.Builder设置父BindingSet
                    builder.setParent(parentBinding);
                    bindingMap.put(type, builder.build());
                } else {
                    // 再次入队列
                    entries.addLast(entry);
                }
            }
        }
        return bindingMap;
    }

4.JavaPoet

以上方法遍历 bindingMap,根据BindingSet得到一个JavaFile对象,然后输入 java 类,这个过程用到了JavaPoet开源库,提供了一种友好的方式来辅助生成 java 类代码,同时将类代码生成文件,否则需要自己拼接字符串来实现,可以发现BindingSet除了保存信息目标类信息外,还封装了 JavaPoet 生成目标类代码的过程。
最终生成了一个_ViewBinding.class的文件,关于javapoet后面再学习。

相关文章

  • ButterKnife原理以及应用

    0.文章导入 ButterKnife算是一款知名老牌 Android 开发框架了,通过注解绑定视图,避免了 fin...

  • 开源框架_02ButterKnife

    参考文章 : ButterKnife使用和原理 深入理解ButterKnife源码并掌握原理(一) 深入理解But...

  • Butterknife原理

    一、目标 写一个demo来实现Butterknife的findViewById功能。 二、核心原理以及实现 核心原...

  • ButterKnife分析

    一、ButterKnife的使用 Android Butterknife使用方法总结 - 简书 二、原理浅析 Bu...

  • ButterKnife原理

    ButterKnife是用来解放开发者的,避免重复编写findViewById,setOnClickListene...

  • ButterKnife原理

    参考githubButterKnife是一个视图注入框架。解决问题:去除大量繁重的View对象查findViewB...

  • ButterKnife原理分析

    ButterKnife源码地址 ButterKnife的分析文章很多了,这里只是简单分析原理,不想看代码,可以直接...

  • volatile原理以及应用

    首先并发编程有三大特性: 可见性,有序性,原子性。volatile关键字实现了前面两个特性。那么它是如何实现这两个...

  • ButterKnife原理与源码分析

    参考深入理解ButterKnife源码并掌握原理

  • Butterknife快速入门应用

    Butterknife快速入门应用 工具和版本 背景 使用步骤 1、配置gradle 2、butterknife的...

网友评论

      本文标题:ButterKnife原理以及应用

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