注解学习之依赖注入框架ButterKnife

注解学习之注解基础
注解学习之依赖注入框架ButterKnife

ButterKnife使用方式

在Module:app build.gradle 中添加如下代码

dependencies {
implementation 'com.jakewharton:butterknife:10.1.0'
//作用:在编译时处理注解,生成辅助文件,提升应用性能
annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'
}

ButterKnife GitHub链接 在GitHub中可以查看完成的源码,在项目中看不到butterKnife-compiler的源码
在项目中使用

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.open_search_image)
    Button openSearchImage;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //******* 这里一般用插件工具的话会自动生成
        ButterKnife.bind(this);
        initListener();
    }

	//这里会自动给各个View注入监听事件的依赖
    @OnClick({R.id.iv_back,R.id.open_search_image})
    public void onClick(View view){
        
        switch (view.getId()){
            case R.id.open_search_image:
                break;
            
            case R.id.iv_back:
                break;
                
            default:
                break;
        }
    }

    private void initListener() {
        openSearchImage.setOnClickListener(v -> SearchImageActivity.startSearchImageActivity(MainActivity.this));
    }
}

ButterKnife支持的注解还是挺多的,自己可以点开源码看一看哦

在这里插入图片描述

流程分析

编译时生成依赖注入辅助文件

生成文件主要是在ButterKnifeProcess 的process方法

  1. TypeElement: 绑定了注解的各个类 BindSet:各个类的辅助:包含添加了注解的属性
  2. 遍历绑定了注解类的集合
  3. 生成Java文件
  4. 写入文件

源文件

public class SearchImageActivity extends AppCompatActivity implements IBaseUIView {
    @BindView(R.id.iv_back)
    ImageView ivBack;
}

生成的辅助文件

public class SearchImageActivity_ViewBinding implements Unbinder {
  private SearchImageActivity target;
  
  @UiThread
  public SearchImageActivity_ViewBinding(SearchImageActivity target) {
    this(target, target.getWindow().getDecorView());
  }

  @UiThread
  public SearchImageActivity_ViewBinding(SearchImageActivity target, View source) {
    this.target = target;

    target.ivBack = Utils.findRequiredViewAsType(source, R.id.iv_back, "field 'ivBack'", ImageView.class);
   	...
  }

  @Override
  @CallSuper
  public void unbind() {
    SearchImageActivity target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");
    this.target = null;

    target.ivBack = null;
    ...
  }
}

ButterKnifeProcess类 process() 处理流程

@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    //TODO 1. TypeElement: 绑定了注解的各个类  BindSet:各个类的辅助:包含添加了注解的属性
    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
    //TODO 2.遍历绑定了注解类的集合
    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
      //获取类元素
      TypeElement typeElement = entry.getKey();
      BindingSet binding = entry.getValue();
      //TODO 3.生成Java文件
      JavaFile javaFile = binding.brewJava(sdk, debuggable);
      try {
        //TODO 4.写入文件
        javaFile.writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }

    return false;
  }

这里分析一下BindView其他的都大同小异 ,parseBindView

1.findAndParseTargets
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
	...
	
	 // 处理每一个绑定了@BindView的元素
    for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
      try {
      	//1.对每一个元素进行处理
        parseBindView(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindView.class, e);
      }
    }
    
	...
}

看一下parseBindView: 找到个元素的上一级并把这元素添加到上一级中,在这里就是把绑定了BindView注解的属性,添加到它所在的类中去

  1. 返回这个元素的上一级元素(包裹它的元素)
  2. 返回一个类的BindingSet.Builder,已经有了就用缓存的
  3. 给一个类添加属性
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
      Set<TypeElement> erasedTargetNames) {
    //TODO 1.返回这个元素的上一级元素(包裹它的元素)
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // Start by verifying common generated code restrictions.
    boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
        || isBindingInWrongPackage(BindView.class, element);
        
	...
	
    if (hasError) {
      return;
    }

    // Assemble information on the field.
    int id = element.getAnnotation(BindView.class).value();
    //TODO 2.返回一个类的BindingSet.Builder,已经有了就用缓存的
    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);
    //TODO 3.给一个类添加属性
    builder.addField(resourceId, new FieldViewBinding(name, type, required));

    //TODO 4.添加了ViewBind注解的类的集合
    erasedTargetNames.add(enclosingElement);
  }
2.生成Java文件
JavaFile brewJava(int sdk, boolean debuggable) {
    //TODO 1.生成文件所需要的信息
    TypeSpec bindingConfiguration = createType(sdk, debuggable);
    //TODO 2.生成文件
    return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
        .addFileComment("Generated code from Butter Knife. Do not modify!")
        .build();
  }

生成文件所需要的信息

private TypeSpec createType(int sdk, boolean debuggable) {
    TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
        .addModifiers(PUBLIC)
        .addOriginatingElement(enclosingElement);
    //TODO 1.判断是不是final的
    if (isFinal) {
      result.addModifiers(FINAL);
    }
    //TODO 2.判断有没有父类
    if (parentBinding != null) {
      result.superclass(parentBinding.getBindingClassName());
    } else {
      result.addSuperinterface(UNBINDER);
    }

    if (hasTargetField()) {
      result.addField(targetTypeName, "target", PRIVATE);
    }

    //TODO 3.添加构造方法
    if (isView) {
      result.addMethod(createBindingConstructorForView());
    } else if (isActivity) {
      result.addMethod(createBindingConstructorForActivity());
    } else if (isDialog) {
      result.addMethod(createBindingConstructorForDialog());
    }
    if (!constructorNeedsView()) {
      // Add a delegating constructor with a target type + view signature for reflective use.
      result.addMethod(createBindingViewDelegateConstructor());
    }
    //TODO 4.添加这个类的属性、方法等信息
    result.addMethod(createBindingConstructor(sdk, debuggable));

    if (hasViewBindings() || parentBinding == null) {
      result.addMethod(createBindingUnbindMethod(result));
    }

    return result.build();
  }

到这里整个生成辅助文件的过程就分析完了

依赖注入流程

1.在Activity中调用绑定

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_search_image);
        //添加绑定
        ButterKnife.bind(this);
    }
2.调用ButterKnife中的Bind(@NonNull Activity target)
@NonNull @UiThread
  public static Unbinder bind(@NonNull Activity target) {
  	//获取view
    View sourceView = target.getWindow().getDecorView();
    return bind(target, sourceView);
  }

继续往下看 获取 SearchImageActivity_ViewBinding 构造函数,并执行

@NonNull @UiThread
  public static Unbinder bind(@NonNull Object target, @NonNull View source) {
    Class<?> targetClass = target.getClass();
    //获取 SearchImageActivity_ViewBinding 构造函数
    Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
    ...
    try {
      //执行构造函数
      return constructor.newInstance(target, source);
    }
  }
@Nullable @CheckResult @UiThread
  private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    if (bindingCtor != null || BINDINGS.containsKey(cls)) {
      if (debug) Log.d(TAG, "HIT: Cached in binding map.");
      return bindingCtor;
    }
    String clsName = cls.getName();
    try {
      //关键在这里,找到上面生成的辅助文件
      Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
      //noinspection unchecked
      bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
    return bindingCtor;
  }

找到编译时生成的辅助文件

Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
3.执行辅助文件(SearchImageActivity_ViewBinding)的构造函数执行
@UiThread
  public SearchImageActivity_ViewBinding(SearchImageActivity target, View source) {
    this.target = target;
	//给目标对象赋值或者注入依赖
    target.ivBack = Utils.findRequiredViewAsType(source, R.id.iv_back, "field 'ivBack'", ImageView.class);
  }
4.查询View
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);
  }
//通过View查询
public static View findRequiredView(View source, @IdRes int id, String who) {
    View view = source.findViewById(id);
    if (view != null) {
      return view;
    }

至此正整个ButterKnife的流程就分析完毕了,建议大家自己阅读一下源码,以加深理解与运用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值