欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

依赖注入和Butter Knife源码分析

程序员文章站 2022-03-14 19:05:32
...

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

依赖注入和Butter Knife源码分析
依赖注入和Butter Knife源码分析
两图的区别:

(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、代码结构
依赖注入和Butter Knife源码分析
核心代码:
* 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 文件
依赖注入和Butter Knife源码分析

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。

相关标签: 注解