Android进阶系列8-编译时注解框架ButterKnife浅析

本文的诞生离不开ButterKnife源码分析Android编译时注解框架系列1-什么是编译时注解
在《Think in Java》一书中,作者提到注解解释器的实现方案,除了最常见的利用反射机制构造外,还提到了注解处理工具APT,APT操作java的源文件,而不是编译后的类,APT会在处理完源文件后编译它们。
Android开发过程中,经常要对控件进行初始化以及监听操作等。其中的代码繁琐而又机械,而这正是注解的强项,减轻程序猿的码码负担。相应的注解工具很多,比如一些敏捷开发框架xUtils3中的注解以及ButterKnife等专职注解框架,大部分注解框架采用的反射机制实现,优点是代码量少,缺点是运行时解析比较耗时。一个极简的注解解析代码大概是这个样子

//创建一个注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface ContentView {
    int value();
}
//使用注解
@ContentView(R.layout.activity_home)
public class HomeActivity extends BaseActivity {
    ......
}
public class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //注解解析
    for (Class c = this.getClass(); c != Context.class; c = c.getSuperclass()) {
        ContentView annotation = (ContentView) c.getAnnotation(ContentView.class);
        if (annotation != null) {
            try {
            this.setContentView(annotation.value());
            } catch (RuntimeException e) {
                e.printStackTrace();
            }
            return;
        }
    }
}

这个例子并没有什么实用性,很多情况没考虑,但是不妨碍我们理解使用反射去注解。
下面重点看下ButterKnife编译时注解的实现过程,我们从最初的调用一步步地深入。

@Override 
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ......
    ButterKnife.bind(this);
    ......
  }

查看ButterKnife的bind方法的实现

  public static Unbinder bind(@NonNull Activity target{
    return bind(target, target, Finder.ACTIVITY);
  }

接着往下看

static Unbinder bind(@NonNull Object target, @NonNull Object source, @NonNull Finder finder) {
    Class<?> targetClass = target.getClass();//获取Activity的类class(针对展示的执行流程是如此逻辑)
    try {
      ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass);//根据target类对象获取viewBinder
      return viewBinder.bind(finder, target, source);//调用viewBinder的bind方法并返回执行结果
    } catch (Exception e) {
      throw new RuntimeException("Unable to bind views for " + targetClass.getName(), e);
    }
  }

疑问来了viewBinder指向的类对象是啥呢?在Android Studio的工程结构的build/intermediates/classes文件夹下,存放了一些带有$$ViewBinder后缀的类文件,打开一瞅,第一行就说:

// Generated code from Butter Knife. Do not modify!
public class MainActivity$$ViewBinder<T extends MainActivity>
  implements ButterKnife.ViewBinder<T>
{
  public void bind(ButterKnife.Finder paramFinder, final T paramT, Object paramObject)
  {
    View localView = (View)paramFinder.findRequiredView(paramObject, 2131492944, "field 'textView' and method 'onClick'");
    paramT.textView = ((TextView)paramFinder.castView(localView, 2131492944, "field 'textView'"));
    localView.setOnClickListener(new DebouncingOnClickListener()
    {
      public void doClick(View paramAnonymousView)
      {
        paramT.onClick(paramAnonymousView);
      }
    });
  }

  public void unbind(T paramT)
  {
    paramT.textView = null;
  }
}

找到这样一个类,上面的疑惑也就可以解开了viewBinder指向的就是$$ViewBinder结尾的类,而调用的bind方法也定义在此类中。看到bind()中的代码,是不是分外亲切呢?和我们手撸的代码很像,也就是说通过自动生成代码的形式代替我们写findViewById这样繁琐的语句,Niubililty!
我们再看下findViewBinderForClass()方法是如何创建ViewBinder对象的。

 @NonNull
  private static ViewBinder<Object> findViewBinderForClass(Class<?> cls)
      throws IllegalAccessException, InstantiationException {
    ViewBinder<Object> viewBinder = BINDERS.get(cls);//检查缓存的列表里面有没有需要的类对象
    if (viewBinder != null) {
      return viewBinder;
    }
    String clsName = cls.getName();
    try {
      Class<?> viewBindingClass = Class.forName(clsName + "$$ViewBinder");
      viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance();//反射获取viewBindingClass实例
    } catch (ClassNotFoundException e) {
      viewBinder = findViewBinderForClass(cls.getSuperclass());
    }
    BINDERS.put(cls, viewBinder);//做一个viewBinder的缓存
    return viewBinder;
  }

代码比较简单,都已经注释了,就不再解释。
以上,我们从ButterKnife.bind(this);开始一步步找到了bind()方法,或许大家对bind()方法的参数Finder还存疑,其实它就是个枚举

public enum Finder{
 ACTIVITY {
    @Override 
    protected View findView(Object source, int id) {
      return ((Activity) source).findViewById(id);
    }
    @Override 
    public Context getContext(Object source){
      return (Activity) source;
    }
  }
  VIEW {
   ......
  }
  ......
  }

这些配置保证了我们在Activity中,在Fragment或者Adapter等中都可以使用ButterKnife成功找到控件或者其它操作。
接着考虑下bind()方法又是由谁生成的呢?一切都仰仗于ButterKnifeProcessor类,里面有两个重要的方法process ()方法和 getSupportedAnnotationTypes()方法。我们先看下getSupportedAnnotationTypes方法:

@Override public Set<String> getSupportedAnnotationTypes() {
    Set<String> types = new LinkedHashSet<>();
    types.add(Bind.class.getCanonicalName());
    for (Class<? extends Annotation> listener : LISTENERS){
      types.add(listener.getCanonicalName());
    }
    types.add(BindArray.class.getCanonicalName());
    types.add(BindBitmap.class.getCanonicalName());
    types.add(BindBool.class.getCanonicalName());
    types.add(BindColor.class.getCanonicalName());
    types.add(BindDimen.class.getCanonicalName());
    types.add(BindDrawable.class.getCanonicalName());
    types.add(BindInt.class.getCanonicalName());
    types.add(BindString.class.getCanonicalName());
    types.add(BindView.class.getCanonicalName());
    types.add(BindViews.class.getCanonicalName());
    return types;
  }

方法负责指定处理的注解类型。而这些指定的类型最终在process方法中得到处理:

 @Override 
 public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);
    for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingClass bindingClass = entry.getValue();
      try {
        bindingClass.brewJava().writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
            e.getMessage());
      }
    }
    return true;
  }

可见,process扫描、处理我们程序中的注解,然后调用bindingClass.brewJava().writeTo(filer)产生$$ViewBinder类文件,我们看下brewJava怎么生成类文件法儿:

 JavaFile brewJava() {
    TypeSpec.Builder result = TypeSpec.classBuilder(className)
        .addModifiers(PUBLIC)
        .addTypeVariable(TypeVariableName.get("T", ClassName.bestGuess(targetClass)));
    if (isFinal) {
      result.addModifiers(Modifier.FINAL);
    }
    if (hasParentBinding()) {
   result.superclass(ParameterizedTypeName.get(ClassName.bestGuess(parentBinding.classFqcn),
          TypeVariableName.get("T")));
    } else {
   result.addSuperinterface(ParameterizedTypeName.get(VIEW_BINDER, TypeVariableName.get("T")));
    }
    result.addMethod(createBindMethod());
    if (hasUnbinder() && hasViewBindings()) {
      // Create unbinding class.
      result.addType(createUnbinderClass());

      if (!isFinal) {
        // Now we need to provide child classes to access and override unbinder implementations.
        createUnbinderCreateUnbinderMethod(result);
      }
    }
    return JavaFile.builder(classPackage, result.build())
        .addFileComment("Generated code from Butter Knife. Do not modify!")
        .build();
  }

可以看到类文件是根据上面的信息用字符串一行一行的拼接起来。

总结

这样我们对ButterKnife的流程就有一个大概的认识了,ButterKnifeProcessor中指定所有用到的注解,扫描遍历程序中的注解,生成相应的Java文件,我们在自己的APP中初始相关代码后,调用生成的Java类中的bind方法,而这些bind方法里面的语句就是我们之前需要手动敲的代码。

很惭愧,做了一点微小的贡献!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值