依赖注入和Butter Knife源码分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/www851903307/article/details/80720558

一、控制反转和依赖注入:

这里写图片描述
这里写图片描述
两图的区别:

(1)图一有高度的耦合性,多个对象相互依赖,如果ObjectA想让ObjectB跟随自己转动应该怎么做?
在ObjectA中创建一个ObjectB的对象,然后调用ObjectB的某个方法进行转动。ObjectA主动控制ObjectB的创建和行为。
(2)图二在ABCD中创建了一个第三方容器,第三方容器控制ObjectA、B、C、D的创建和转动。如果ObjectA想让ObjectB跟随自己转动应该怎么做?
A告诉第三方容器,让第三方容器去创建ObjectB,并且让ObjectB进行转动。
(3)对比:图一控制权在自身,图二控制权在IOC容器,降低了多个对象之间的耦合性。这就是控制反转的思想。

1、控制反转(IOC):
Ioc—Inversion of Control,即“控制反转”。控制反转是种设计思想,将你设计好的对象交给容器控制,而不是在你对象内部直接控制。
* 控制什么?控制了外部资源获取,比如对象创建、文件读取等。
* 什么反转了?依赖对象的获取反转了。ObjectB依赖于ObjectA。之前是ObjectA想获取ObjectB由自身控制,现在ObjectA获取ObjectB由IOC容器控制。

2、依赖注入(DI):
DI—Dependency Injection,即“依赖注入”。组件之间依赖关系由容器在运行期决定,即由容器动态的将某个依赖关系注入到组件之中。
谁依赖谁?某个对象依赖于IOC容器
谁注入谁?IOC容器提供资源,将资源注入到某个需要的对象中。

3、控制反转和依赖注入的关系:
同一思想的不同说法。资源获取的主动索取变成被注入。

4、依赖注入的实现:
* 基于接口:定义接口,提供给IOC容器来传入自身所需要的对象
* 基于set方法:通过方法提供给IOC容器来传入自身所需要的对象
* 基于构造函数:通过构造提供给IOC容器来传入自身所需要的对象
* 基于注解:通过注解,生成方法,给IOC容器来传入自身所需要的对象

三、ButterKnife使用:

1、build.gradle:

implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'

2、调用:

 @BindView(R.id.tv_title)
 TextView mTextView;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.activity_main);
     ButterKnife.bind(this);

 }

四、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。

猜你喜欢

转载自blog.csdn.net/www851903307/article/details/80720558
今日推荐