首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

依赖注入和Butter Knife源码分析(3)

依赖注入和Butter Knife源码分析(3)

四、ButterKnife 源码分析:

1、代码结构

核心代码:

    butterknife 提供一些api
    butterknife-annotations 提供一些自定义注解
    butterknife-compiler 注解解析器

2、BindView 注解

    @Retention(CLASS)
    @Target(FIELD)
    public @interface BindView {
      /** View ID to which the field will be bound. */
      @IdRes int value();
    }
    复制代码

@Retention(CLASS)说明是编译时注解。@Target(FIELD)说明注解作用于成员变量。 如何将注解解析生成Java文件呢?

3、注解解析器 ButterKnifeProcessor:

    @AutoService(Processor.class)
    public final class ButterKnifeProcessor extends AbstractProcessor {
      @Override
      public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
        //(1)查找所有的注解并进行解析
        Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
        //(2)进行遍历,获取注解的值
        for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
          TypeElement typeElement = entry.getKey();
          BindingSet binding = entry.getValue();
          //(3)将注解的类生成一个JavaFile,并输出为一个Java文件
          JavaFile javaFile = binding.brewJava(sdk, debuggable, useAndroidX);
          try {
            javaFile.writeTo(filer);
          } catch (IOException e) {
            error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
          }
        }
     
        return false;
      }
    }
    复制代码

(1)findAndParseTargets:

     private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
         ......
        for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
     processing rounds
          try {
            parseBindView(element, builderMap, erasedTargetNames);
          } catch (Exception e) {
            logParsingError(element, BindView.class, e);
          }
        }
     }
    复制代码

内部调用了parseBindView方法

      private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
          Set<TypeElement> erasedTargetNames) {
        ......
        //获取注解标注的值
        int id = element.getAnnotation(BindView.class).value();
        BindingSet.Builder builder = builderMap.get(enclosingElement);
        Id resourceId = elementToId(element, BindView.class, id);
        if (builder != null) {
          String existingBindingName = builder.findExistingBindingName(resourceId);
          if (existingBindingName != null) {
            error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
                BindView.class.getSimpleName(), id, existingBindingName,
                enclosingElement.getQualifiedName(), element.getSimpleName());
            return;
          }
        } else {
          builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
        }
     
        String name = simpleName.toString();
        TypeName type = TypeName.get(elementType);
        boolean required = isFieldRequired(element);
        //将值存在到field上
        builder.addField(resourceId, new FieldViewBinding(name, type, required));
      }
    复制代码

解析注解的过程:将注解进行解析,获取到注解的值,然后将注解的类生成一个Java文件。Java文件在哪?

4、编译后生成文件: 通过注解解析器将注解解析生成java 文件

    public class MainActivity_ViewBinding implements Unbinder {
      private MainActivity target;
     
      @UiThread
      public MainActivity_ViewBinding(MainActivity target) {
        this(target, target.getWindow().getDecorView());
      }
     
      @UiThread
      public MainActivity_ViewBinding(MainActivity target, View source) {
        this.target = target;
     
        target.mTextView = Utils.findRequiredViewAsType(source, R.id.tv_title, "field 'mTextView'", TextView.class);
      }
     
      @Override
      @CallSuper
      public void unbind() {
        MainActivity target = this.target;
        if (target == null) throw new IllegalStateException("Bindings already cleared.");
        this.target = null;
     
        target.mTextView = null;
      }
    }
    复制代码

(1)查找id为tv_title的TextView:

    public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who,
          Class<T> cls) {
        View view = findRequiredView(source, id, who);
        return castView(view, id, who, cls);
    }
    复制代码

    public static View findRequiredView(View source, @IdRes int id, String who) {
        View view = source.findViewById(id);
        if (view != null) {
          return view;
        }
        ...
    }
    复制代码

最终还是通过source.findViewById 去查找view。传入的source的View当前的Activity的DecorView。

5、ButterKnife.bind(this):
ButterKnife的bind方法:

      @NonNull @UiThread
      public static Unbinder bind(@NonNull Dialog target) {
        View sourceView = target.getWindow().getDecorView();
        return createBinding(target, sourceView);
      }
    复制代码

获取到了当前Activit的DecorView,然后调用了createBinding方法。

      private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
        ......
        //去获取constructor 对象
        Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
        
        if (constructor == null) {
          return Unbinder.EMPTY;
        }
        //通过反射区实例化constructor对象
        try {
          return constructor.newInstance(target, source);
        } catch (IllegalAccessException e) {
        ......
      }
    复制代码

findBindingConstructorForClass方法:

    @Nullable @CheckResult @UiThread
      private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
        //(1)BINDINGS是一个LinkedHashMap的集合,先在集合取cls对应的Constructor实例
        Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
        //(2)取到了就直接返回
        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 {
          //(3)获取不到就通过反射就创建,就是刚刚的MainActivity_ViewBinding的辅助类
          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);
        }
        //将该Constructor的实例作为value,存入集合中
        BINDINGS.put(cls, bindingCtor);
        return bindingCtor;
      }
    复制代码

总结:在MainActivity中通过BindView对TextView进行注解
(1)在编译时期通过注解解析器ButterKnifeProcessor对注解进行解析,获取到注解标注的值,然后生成一个MainActivity_ViewBinding的类。
(2)通过调用ButterKnife.bind(this)来绑定上下文。会从一个LinkedHashMap的集合中取对应MainActivity的Constructor对象,如果取不到就通过反射创建,然后存到集合中。
(3)同时通过反射创建了MainActivity_ViewBinding的对象,此时传入了MainActivity上下文对象,在MainActivity_ViewBinding中可以通过DecorView 去获取被注解的TextView。
返回列表