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

来源:互联网 发布:淘宝服装店铺页头图片 编辑:程序博客网 时间:2024/05/22 01:58

本文的诞生离不开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
原创粉丝点击