Butterknife

我研究Butterknife源码的目的是为了解决以下几个我在使用过程中所思考的问题:

前言

Jake Wharton大神的Butterknife可谓是造福广大Android开发者,
再也不用重复写findViewById和setOnClickListener了.但是用了这么久的Butterknife,
一直以为用的是反射, 直到看了源码…(路漫漫其修远兮,吾将上下而求索)

1.什么是注解
2.注解的分类
3.编译时注解的原理
4.APT
5.创建项目及依赖
6.编码实现
7.总结

前言

Butterknife我相信,对大部分做Android开发的人都不陌生,这个是供职于Square公司的JakeWharton大神开发的,目前github的star为
12449 。使用这个库,在AS中搭配Android ButterKnife
Zelezny插件,简直是开发神器,从此摆脱繁琐的findViewById(int id),也不用自己手动@bind(int id)

直接用插件生成即可。这种采用注解DI组件的方式,在Spring中很常见,起初也是在Spring中兴起的
。今天我们就一探究竟,自己实现一个butterknife
(有不会用的,请自行Google)

项目地址:
JakeWharton/butterknife

图片 1

butterknife

Butterknife

  1. 在很多文章中都提到Butterknife使用编译时注解技术,什么是编译时注解?
  2. 是完全不调用findViewById()等方法了吗?
  3. 为什么绑定各种view时不能使用private修饰?
  4. 绑定监听事件的时候方法命名有限制吗?

Butterknife的使用

Git地址:
https://github.com/JakeWharton/butterknife,
里面有具体的引用及使用说明, 这里不再介绍.

图片 2

实现原理 (假定你对注解有一定的了解)

注解

对ButterKnife有过了解人 ,
注入字段的方式是使用注解@Bind(R.id.tv_account_name),但首先我们需要在Activity声明注入ButterKnife.bind(Activity activity)
。我们知道,注解分为好几类,
有在源码生效的注解,有在类文件生成时生效的注解,有在运行时生效的注解。分别为RetentionPolicy.SOURCERetentionPolicy.CLASSRetentionPolicy.RUNTIME
,其中以RetentionPolicy.RUNTIME最为消耗性能。而ButterKnife使用的则是编译器时期注入,在使用的时候,需要配置classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

这个配置说明,在编译的时候,进行注解处理。要对注解进行处理,则需要继承AbstractProcessor

boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)中进行注解处理。

实现方式

知晓了注解可以在编译的时候进行处理,那么,我们就可以得到注解的字段属性与所在类
, 进而生成注入文件,生成一个注入类的内部类,再进行字段处理 ,
编译之后就会合并到注入类中,达到植入新代码段的目的。例如:我们注入@VInjector(R.id.tv_show) TextView tvShow;我们就可以得到tvShow这个变量与R.id.tv_show这个id的值,然后进行模式化处理injectObject.tvShow = injectObject.findViewById(R.id.tv_show);
,再将代码以内部类的心事加入到组件所在的类中 , 完成一次DI(注入) 。

实现流程图

图片 3

view_injector_流程图

① 首先创建一个视图注解
② 创建一个注解处理器,用来得到注解的属性与所属类
③ 解析注解,分离组合Class与属性
④ 组合Class与属性,生成新的Java File

APT生成的Java File , 以及模式代码

图片 4

generator java file

使用Javac , 编译时期生成注入类的子类

findViewById

@InjectView(R.id.button1) Button button1;
@InjectViews({ R.id.first_name, R.id.middle_name, R.id.last_name }) List<EditText> nameViews;
  • Activity

    setContentView(R.layout.main_activity);
    ButterKnife.inject(this);
    
  • FragmentAdapter

    ButterKnife.inject(this, rootView);
    
    void onDestroyView() {
        super.onDestroyView();
        ButterKnife.reset(this);
    }
    

新版改为 @BindView 和 ButterKnife.bind(this);

基于Butterknife
8.8.1版本。为了更好地分析代码,我写了一个demo:MainActivity.java:

Butterknife的原理

谈到Butterknife的原理, 不得不先提一下运行时注解和编译时注解的区别:

  • 编译时注解:
    (代码生成)在编译时扫描所有注解,对注解进行处理,在项目中生成一份代码,然后在项目运行时直接调用;
  • 运行时注解:
    (代码注入)在代码中通过注解进行标记,在运行时通过反射在原代码的基础上完成注入;

Butterknife就是用的编译时注解(Annotation Processing Tool),简称APT技术.
关于APT,
可以戳这里,里面有demo,介绍了编译时自动生成代码的整个过程.
看完这篇文章再看Butterknife就有章可循了.

我们首先了解一下什么是注解以及注解的核心原理,在掌握原理的前提下自己动手实现一个注解框架。通过代码的编写能够对Butterknife底层实现有更加清楚的认识。

项目UML图

图片 5

ViewInject UML

简要说明:

主要类:
VInjectProcessor —-> 注解处理器 , 需要配置注解处理器

resources
        - META-INF
              - services
                    - javax.annotation.processing.Processor

Processor内容:

com.zeno.viewinject.apt.VInjectProcessor   # 指定处理器全类名

图示:

图片 6

processor config

VInjectHandler —-> 注解处理类 ,
主要进行注入类与注解字段进行解析与封装,将同类的字段使用map集合进行映射。exp:
Map<Class,List<Attr>> 。

ViewGenerateAdapter —–> Java File
生成器,将注入的类与属性,重新生成一个Java File,是其注入类的内部类 。

setOnClickListener

  • 无参数

    @OnClick(R.id.submit)
    public void submit() { //... }
    
  • 附带参数

    @OnClick(R.id.submit)
    public void submit(View view) { //... }
    
  • 多个控件

    @OnClick({ R.id.door1, R.id.door2, R.id.door3 })
    public void pickDoor(DoorView door) {}
    
public class MainActivity extends Activity { @BindView(R.id.text) TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind; } @OnClick(R.id.text) public void textClick() { Toast.makeText(MainActivity.this, "textview clicked", Toast.LENGTH_LONG); }}

Butterknife源码解析

了解APT之后就知道了最重要的有两步,一编译时自动生成代码,二运行时进行代码绑定.下面以Butterknife最新版本8.8.1的源码进行说明;

注解

注解在Java文档中定义如下:

An annotation is a form of metadata, that can be added to Java source
code. Classes, methods, variables, parameters and packages may be
annotated. Annotations have no direct effect on the operation of the
code they annotate.

翻译一下,大概的意思是:

注解是一种元数据, 可以添加到java代码中.
类、方法、变量、参数、包都可以被注解,注解对注解的代码没有直接影响。

具体实现

一 , 创建注解 , 对视图进行注解,R.id.xxx , 所以注解类型是int类型

/**
 * Created by Zeno on 2016/10/21.
 *
 * View inject
 * 字段注入注解,可以新建多个注解,再通过AnnotationProcessor进行注解处理
 * RetentionPolicy.CLASS ,在编译的时候进行注解 。我们需要在生成.class文件的时候需要进行处理
 */

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface VInjector {
    int value();
}

二, 注解处理器 关于注解处理器配置,上面已经做了说明

/**
 * Created by Zeno on 2016/10/21.
 *
 * Inject in View annotation processor
 *
 * 需要在配置文件中指定处理类 resources/META-INF/services/javax.annotation.processing.Processor
 * com.zeno.viewinject.apt.VInjectProcessor
 */

@SupportedAnnotationTypes("com.zeno.viewinject.annotation.VInjector")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class VInjectProcessor extends AbstractProcessor {

    List<IAnnotationHandler> mAnnotationHandler = new ArrayList<>();
    Map<String,List<VariableElement>> mHandleAnnotationMap = new HashMap<>();
    private IGenerateAdapter mGenerateAdapter;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        // init annotation handler , add handler
        registerHandler(new VInjectHandler());

        // init generate adapter
        mGenerateAdapter = new ViewGenerateAdapter(processingEnv);

    }

    /*可以有多个处理*/
    protected void registerHandler(IAnnotationHandler handler) {
        mAnnotationHandler.add(handler);
    }

    // annotation into process run
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

        for (IAnnotationHandler handler : mAnnotationHandler) {
            // attach environment , 关联环境
            handler.attachProcessingEnvironment(processingEnv);
            // handle annotation 处理注解 ,得到注解类的属性列表
            mHandleAnnotationMap.putAll(handler.handleAnnotation(roundEnv));
        }
        // 生成辅助类
        mGenerateAdapter.generate(mHandleAnnotationMap);
        // 表示处理
        return true;
    }
}

对得到的注解进行处理 ,
主要是进行注解类型与属性进行分离合并处理,因为一个类有多个属性,所以采用map集合,进行存储,数据结构为:Map<String:className
, List<VariableElement:element>>

/**
 * Created by Zeno on 2016/10/21.
 *
 * 注解处理实现 , 解析VInjector注解属性
 */
public class VInjectHandler implements IAnnotationHandler {


    private ProcessingEnvironment mProcessingEnvironment;

    @Override
    public void attachProcessingEnvironment(ProcessingEnvironment environment) {
            this.mProcessingEnvironment = environment;
    }

    @Override
    public Map<String, List<VariableElement>> handleAnnotation(RoundEnvironment roundEnvironment) {
        Map<String,List<VariableElement>> map = new HashMap<>();
        /*获取一个类中带有VInjector注解的属性列表*/
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(VInjector.class);
        for (Element element : elements) {
            VariableElement variableElement = (VariableElement) element;
            /*获取类名 ,将类目与属性配对,一个类,对于他的属性列表*/
            String className = getFullClassName(variableElement);
            List<VariableElement> cacheElements = map.get(className);
            if (cacheElements == null) {
                cacheElements = new ArrayList<>();
                map.put(className,cacheElements);
            }
            cacheElements.add(variableElement);
        }

        return map;
    }

    /**
     * 获取注解属性的完整类名
     * @param variableElement
     */
    private String getFullClassName(VariableElement variableElement) {
        TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
        String packageName = AnnotationUtils.getPackageName(mProcessingEnvironment,typeElement);
        return packageName+"."+typeElement.getSimpleName().toString();
    }
}

生成Java File , 根据获取的属性与类,创建一个注入类的内部类

/**
 * Created by Zeno on 2016/10/21.
 *
 * 生成View注解辅助类
 */
public class ViewGenerateAdapter extends AbstractGenerateAdapter {

    public ViewGenerateAdapter(ProcessingEnvironment processingEnvironment) {
        super(processingEnvironment);
    }

    @Override
    protected void generateImport(Writer writer, InjectInfo injectInfo) throws IOException {
        writer.write("package "+injectInfo.packageName+";");
        writer.write("\n\n");
        writer.write("import  com.zeno.viewinject.adapter.IVInjectorAdapter;");
        writer.write("\n\n");
        writer.write("import  com.zeno.viewinject.utils.ViewFinder;");
        writer.write("\n\n\n");
        writer.write("/* This class file is generated by ViewInject , do not modify */");
        writer.write("\n");
        writer.write("public class "+injectInfo.newClassName+" implements IVInjectorAdapter<"+injectInfo.className+"> {");
        writer.write("\n\n");
        writer.write("public void injects("+injectInfo.className+" target) {");
        writer.write("\n");
    }

    @Override
    protected void generateField(Writer writer, VariableElement variableElement, InjectInfo injectInfo) throws IOException {
        VInjector vInjector = variableElement.getAnnotation(VInjector.class);
        int resId = vInjector.value();
        String fieldName = variableElement.getSimpleName().toString();
        writer.write("\t\ttarget."+fieldName+" = ViewFinder.findViewById(target,"+resId+");");
        writer.write("\n");
    }

    @Override
    protected void generateFooter(Writer writer) throws IOException {
        writer.write(" \t}");
        writer.write("\n\n");
        writer.write("}");
    }
}

我们从Butterknife.bind()方法,即方法入口开始分析:ButterKnife#bind():

编译时自动生成代码

首先来看ButterKnifeProcessor类,继承AbstractProcessor,需要复写的方法:
init(): 主要初始化一些工具类

@Override 
public synchronized void init(ProcessingEnvironment env) {
    super.init(env);

    ... //获取sdk和是否debug
    elementUtils = env.getElementUtils();//获取元素相关信息的工具类
    typeUtils = env.getTypeUtils();//处理TypeMirror的工具类
    filer = env.getFiler();//生成java文件的工具类
    try {
      trees = Trees.instance(processingEnv);
    } catch (IllegalArgumentException ignored) {
    }
  }

getSupportedAnnotationTypes():
返回支持的注解类型,包括BindAnim,BindArray等注解,以及OnClick,OnCheckedChanged等监听.

private Set<Class<? extends Annotation>> getSupportedAnnotations() {
    Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();

    annotations.add(BindAnim.class);
    annotations.add(BindArray.class);
    annotations.add(BindBitmap.class);
    annotations.add(BindBool.class);
    annotations.add(BindColor.class);
    annotations.add(BindDimen.class);
    annotations.add(BindDrawable.class);
    annotations.add(BindFloat.class);
    annotations.add(BindFont.class);
    annotations.add(BindInt.class);
    annotations.add(BindString.class);
    annotations.add(BindView.class);
    annotations.add(BindViews.class);
    annotations.addAll(LISTENERS);

    return annotations;
  }

process():生成代码的核心部分,分两步,扫描处理代码中所有的注解,然后通过javapoet生成java文件.
返回值表示这组注解是否被这个 Processor
接受,如果接受(返回true)后续子的 processor 不会再对这个注解进行处理.

@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    //扫描代码中所有的注解,存到map中
    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);

    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingSet binding = entry.getValue();
     //生成java文件
      JavaFile javaFile = binding.brewJava(sdk, debuggable);
      try {
        javaFile.writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }
    return false;
  }

RoundEnvironment表示当前或之前的运行环境,可以通过该对象查找注解.
TypeElement代表源代码中的元素类型, 如包,类,属性,方法,
源代码中每一部分都是一种类型.包为PackageElement,类为TypeElement,属性为VariableElement,方法为ExecuteableElement
,都是Element的子类.
然后看下findAndParseTargets方法是如何扫描所有注解的.

private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
    Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
    //建立view与id的关系
    scanForRClasses(env);

   // Process each @BindView element.
   //env.getElementsAnnotatedWith(BindView.class))获取所有注解是BindView的元素
    for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
      // we don't SuperficialValidation.validateElement(element)
      // so that an unresolved View type can be generated by later processing rounds
      try {
        parseBindView(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindView.class, e);
      }
    }    return bindingMap;
  }

首先看scanForRClasses()如何建立view与id的关系

private void scanForRClasses(RoundEnvironment env) {
    if (trees == null) return;
    //R文件扫描器,扫描代码中所有的R文件
    RClassScanner scanner = new RClassScanner();

    for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
      for (Element element : env.getElementsAnnotatedWith(annotation)) {
        JCTree tree = (JCTree) trees.getTree(element, getMirror(element, annotation));
        if (tree != null) { // tree can be null if the references are compiled types and not source
          //获取R文件的包名
          String respectivePackageName =
              elementUtils.getPackageOf(element).getQualifiedName().toString();
          scanner.setCurrentPackageName(respectivePackageName);
          tree.accept(scanner);
        }
      }
    }

    for (Map.Entry<String, Set<String>> packageNameToRClassSet : scanner.getRClasses().entrySet()) {
      String respectivePackageName = packageNameToRClassSet.getKey();
      for (String rClass : packageNameToRClassSet.getValue()) {
        //解析R文件
        parseRClass(respectivePackageName, rClass);
      }
    }
  }

parseRClass()利用IdScanner寻找R文件内部类,如array,attr,string等

private void parseRClass(String respectivePackageName, String rClass) {
    Element element;

    try {
      element = elementUtils.getTypeElement(rClass);
    } catch (MirroredTypeException mte) {
      element = typeUtils.asElement(mte.getTypeMirror());
    }

    JCTree tree = (JCTree) trees.getTree(element);
    if (tree != null) { // tree can be null if the references are compiled types and not source
      //利用IdScanner寻找R文件内部类,如array,attr,string等
      IdScanner idScanner = new IdScanner(symbols, elementUtils.getPackageOf(element)
          .getQualifiedName().toString(), respectivePackageName);
      tree.accept(idScanner);
    } else {
      parseCompiledR(respectivePackageName, (TypeElement) element);
    }
  }

在IdScanner类内部利用VarScanner扫描R文件内部类(id,string等)的属性(键值对:资源名和id)

private static class IdScanner extends TreeScanner {
    private final Map<QualifiedId, Id> ids;
    private final String rPackageName;
    private final String respectivePackageName;

    IdScanner(Map<QualifiedId, Id> ids, String rPackageName, String respectivePackageName) {
      this.ids = ids;
      this.rPackageName = rPackageName;
      this.respectivePackageName = respectivePackageName;
    }

    @Override public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
      for (JCTree tree : jcClassDecl.defs) {
        if (tree instanceof ClassTree) {
          ClassTree classTree = (ClassTree) tree;
          String className = classTree.getSimpleName().toString();
          if (SUPPORTED_TYPES.contains(className)) {
            ClassName rClassName = ClassName.get(rPackageName, "R", className);
            VarScanner scanner = new VarScanner(ids, rClassName, respectivePackageName);
            ((JCTree) classTree).accept(scanner);
          }
        }
      }
    }
  }

在VarScanner类内部记录资源名称及id

private static class VarScanner extends TreeScanner {
    private final Map<QualifiedId, Id> ids;
    private final ClassName className;
    private final String respectivePackageName;

    private VarScanner(Map<QualifiedId, Id> ids, ClassName className,
        String respectivePackageName) {
      this.ids = ids;
      this.className = className;
      this.respectivePackageName = respectivePackageName;
    }

    @Override public void visitVarDef(JCTree.JCVariableDecl jcVariableDecl) {
      if ("int".equals(jcVariableDecl.getType().toString())) {
        int id = Integer.valueOf(jcVariableDecl.getInitializer().toString());
        String resourceName = jcVariableDecl.getName().toString();
        QualifiedId qualifiedId = new QualifiedId(respectivePackageName, id);
        ids.put(qualifiedId, new Id(id, className, resourceName));
      }
    }
  }

到此就建立了所有view与id的关系,然后看如何处理注解,
以BindView为例(其他类似).
将每一个BindView注解的元素存放到被绑定类的成员变量中.

private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
      Set<TypeElement> erasedTargetNames) {
    //获取BindView注解元素的父元素, 如MainActivity
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // Start by verifying common generated code restrictions.
    //isInaccessibleViaGeneratedCode检查父元素是否是类且非private
    //isBindingInWrongPackage检查父元素是否是系统相关的类
    boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
        || isBindingInWrongPackage(BindView.class, element);

    // Verify that the target type extends from View.
    TypeMirror elementType = element.asType();
    if (elementType.getKind() == TypeKind.TYPEVAR) {
      TypeVariable typeVariable = (TypeVariable) elementType;
      elementType = typeVariable.getUpperBound();
    }
    Name qualifiedName = enclosingElement.getQualifiedName();
    Name simpleName = element.getSimpleName();
    //判断BindView注解的元素是view的子类或接口,否则有错return
    if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
      if (elementType.getKind() == TypeKind.ERROR) {
        note(element, "@%s field with unresolved type (%s) "
                + "must elsewhere be generated as a View or interface. (%s.%s)",
            BindView.class.getSimpleName(), elementType, qualifiedName, simpleName);
      } else {
        error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
            BindView.class.getSimpleName(), qualifiedName, simpleName);
        hasError = true;
      }
    }

    if (hasError) {
      return;
    }

    // Assemble information on the field.
    //获取BindView注解的value,及id
    int id = element.getAnnotation(BindView.class).value();
    //builderMap缓存所有被绑定类的信息, 键是父元素(如MainActivity),值是键对应的被绑定类的信息(如MainActivity_ViewBinding)
    //获取BindingSet.Builder, 有则使用缓存,无则创建
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    QualifiedId qualifiedId = elementToQualifiedId(element, id);
    if (builder != null) {
      //从被绑定类的成员变量中查找这个id,不为空,说明有多个view绑定了同一个id, 抛异常
      String existingBindingName = builder.findExistingBindingName(getId(qualifiedId));
      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);
    //加入到成员变量集合中
    builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));

    // Add the type-erased version to the valid binding targets set.
    erasedTargetNames.add(enclosingElement);
  }

再看如何创建被绑定类BindingSet

private BindingSet.Builder getOrCreateBindingBuilder(
      Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) {
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    if (builder == null) {
      builder = BindingSet.newBuilder(enclosingElement);
      builderMap.put(enclosingElement, builder);
    }
    return builder;
  }
static Builder newBuilder(TypeElement enclosingElement) {
    //获取注解父元素的类型,View/Activity/Dialog
    TypeMirror typeMirror = enclosingElement.asType();

    boolean isView = isSubtypeOfType(typeMirror, VIEW_TYPE);
    boolean isActivity = isSubtypeOfType(typeMirror, ACTIVITY_TYPE);
    boolean isDialog = isSubtypeOfType(typeMirror, DIALOG_TYPE);

    TypeName targetType = TypeName.get(typeMirror);
    if (targetType instanceof ParameterizedTypeName) {
      targetType = ((ParameterizedTypeName) targetType).rawType;
    }
    //获取父元素的包名和类名
    String packageName = getPackage(enclosingElement).getQualifiedName().toString();
    String className = enclosingElement.getQualifiedName().toString().substring(
        packageName.length() + 1).replace('.', '$');
    //获取绑定的类名,加后缀_ViewBinding(后面介绍生成加后缀_ViewBinding的java文件)
    ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");

    boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
    return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog);
  }

到此完成了扫描所有注解的工作,再看如何生成java文件.就是process()中的binding.brewJava(sdk,
debuggable);

JavaFile brewJava(int sdk, boolean debuggable) {
    //bindingClassName就是加了后缀_ViewBinding的类
    return JavaFile.builder(bindingClassName.packageName(), createType(sdk, debuggable))
        .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);
    if (isFinal) {
      result.addModifiers(FINAL);
    }

    if (parentBinding != null) {
      result.superclass(parentBinding.bindingClassName);
    } else {
      result.addSuperinterface(UNBINDER);
    }

    if (hasTargetField()) {
      result.addField(targetTypeName, "target", PRIVATE);
    }
    //添加构造方法
    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());
    }
    result.addMethod(createBindingConstructor(sdk, debuggable));

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

    return result.build();
  }

这里用到了javapoet,感兴趣的可以戳这里,
通过TypeSpec生成类,并一步步地添加构造方法等.
编译之后就会在build目录下会产生原Activity.class以及原Activity_ViewBinding.class两份代码,.来看下_ViewBinding代码的结构,以官方SimpleActivity_ViewBinding为例.在构造函数中生成控件以及事件监听,在unbind对所有控件,事件以及原Activity的引用置空,等待GC回收.

public class SimpleActivity_ViewBinding<T extends SimpleActivity> implements Unbinder {
  protected T target;

  private View view2130968578;

  private View view2130968579;

  @UiThread
  public SimpleActivity_ViewBinding(final T target, View source) {
    this.target = target;

    View view;
    //target就是SimpleActivity,所以SimpleActivity中的title,subtitle等被BindView注解的元素都不能是private的.
    target.title = Utils.findRequiredViewAsType(source, R.id.title, "field 'title'", TextView.class);
    target.subtitle = Utils.findRequiredViewAsType(source, R.id.subtitle, "field 'subtitle'", TextView.class);
    view = Utils.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', and method 'sayGetOffMe'");
    target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class);
    view2130968578 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.sayHello();
      }
    });
    view.setOnLongClickListener(new View.OnLongClickListener() {
      @Override
      public boolean onLongClick(View p0) {
        return target.sayGetOffMe();
      }
    });
    view = Utils.findRequiredView(source, R.id.list_of_things, "field 'listOfThings' and method 'onItemClick'");
    target.listOfThings = Utils.castView(view, R.id.list_of_things, "field 'listOfThings'", ListView.class);
    view2130968579 = view;
    ((AdapterView<?>) view).setOnItemClickListener(new AdapterView.OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView<?> p0, View p1, int p2, long p3) {
        target.onItemClick(p2);
      }
    });
    target.footer = Utils.findRequiredViewAsType(source, R.id.footer, "field 'footer'", TextView.class);
    target.headerViews = Utils.listOf(
        Utils.findRequiredView(source, R.id.title, "field 'headerViews'"), 
        Utils.findRequiredView(source, R.id.subtitle, "field 'headerViews'"), 
        Utils.findRequiredView(source, R.id.hello, "field 'headerViews'"));
  }

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

    target.title = null;
    target.subtitle = null;
    target.hello = null;
    target.listOfThings = null;
    target.footer = null;
    target.headerViews = null;

    view2130968578.setOnClickListener(null);
    view2130968578.setOnLongClickListener(null);
    view2130968578 = null;
    ((AdapterView<?>) view2130968579).setOnItemClickListener(null);
    view2130968579 = null;

    this.target = null;
  }
}

编译时自动生成代码讲完了, 然后来看代码的绑定.

注解的分类

  • <b>运行时注解:</b>指的是运行阶段利用反射,动态获取被标记的方法、变量等,如EvenBus。
  • <b>编译时注解:</b>指的是程序在编译阶段会根据注解进行一些额外的处理,如ButterKnife。

运行时注解和编译时注解,都可以理解为通过注解标识,然后进行相应处理。两者的区别是:前者是运行时执行的,反射的使用会降低性能;后者是编译阶段执行的,通过生成辅助类实现效果。

运行时注解由于性能问题被一些人所诟病,所以本文主要讲解编译时注解的原理,并实现自己的Butterknife框架。

结语

ButterKnife类型的注解框架,其主要核心就是编译时期注入,
如果是采用运行时注解的话,那性能肯定影响很大,国内有些DI框架就是采用的运行时注解,所以性能上会有所损伤
。原以为很高深的东西,其实剖析过原理之后,也就渐渐明白了,不再视其为高深莫测,我们自己也可以实现同等的功能。

程序员最好的学习方式就是,学习别人的代码,特别是像jakeWharton这样的大神的代码,值得研究与学习
, 然后模仿之。

@NonNull @UiThreadpublic static Unbinder bind(@NonNull Activity target) { View sourceView = target.getWindow().getDecorView(); return createBinding(target, sourceView);}private static Unbinder createBinding(@NonNull Object target, @NonNull View source) { Class<?> targetClass = target.getClass(); if  Log.d(TAG, "Looking up binding for " + targetClass.getName; // !!! Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass); if (constructor == null) { return Unbinder.EMPTY; } //noinspection TryWithIdenticalCatches Resolves to API 19+ only type. try { return constructor.newInstance(target, source); } catch (IllegalAccessException e) { throw new RuntimeException("Unable to invoke " + constructor, e); } catch (InstantiationException e) { throw new RuntimeException("Unable to invoke " + constructor, e); } catch (InvocationTargetException e) { Throwable cause = e.getCause(); if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } if (cause instanceof Error) { throw  cause; } throw new RuntimeException("Unable to create binding instance.", cause); }}@Nullable @CheckResult @UiThreadprivate static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) { Constructor<? extends Unbinder> bindingCtor = BINDINGS.get; if (bindingCtor != null) { if  Log.d(TAG, "HIT: Cached in binding map."); return bindingCtor; } String clsName = cls.getName(); if (clsName.startsWith("android.") || clsName.startsWith { if  Log.d(TAG, "MISS: Reached framework class. Abandoning search."); return null; } try { // !!! Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding"); //noinspection unchecked bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class); if  Log.d(TAG, "HIT: Loaded binding class and constructor."); } catch (ClassNotFoundException e) { if  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); } BINDINGS.put(cls, bindingCtor); return bindingCtor;}

代码绑定

用过Butterknife的朋友都知道是通过ButterKnife.bind(this)进行绑定(以Activity为例).

@NonNull @UiThread
  public static Unbinder bind(@NonNull Activity target) {
    //获取跟布局view
    View sourceView = target.getWindow().getDecorView();
    return createBinding(target, sourceView);
  }
private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
    Class<?> targetClass = target.getClass();
    if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
    //找到被绑定类(如SimpleActivity_ViewBinding)的构造函数
    Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

    if (constructor == null) {
      return Unbinder.EMPTY;
    }

    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
    //返回被绑定类
      return constructor.newInstance(target, source);
    } catch (IllegalAccessException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InstantiationException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InvocationTargetException e) {
      Throwable cause = e.getCause();
      if (cause instanceof RuntimeException) {
        throw (RuntimeException) cause;
      }
      if (cause instanceof Error) {
        throw (Error) cause;
      }
      throw new RuntimeException("Unable to create binding instance.", cause);
    }
  }

到此就完成了原Activity与Activity_ViewBinding的绑定,即在ButterKnife.bind(this)时就完成原Activity注解控件及事件的初始化,原Activity就可以直接调用了.

如理解有误,欢迎指正.最后附上相关技术的文章:
APT—-《android-apt》
Javapoet—-《javapoet——让你从重复无聊的代码中解放出来》

编译时注解的原理

编译时注解的核心原理依赖APT(Annotation Processing Tools)实现:

编译时Annotation解析的基本原理是,在某些代码元素上(如类型、函数、字段等)添加注解,在编译时javac编译器会检查AbstractProcessor的子类,并且调用该类型的process函数,然后将添加了注解的所有元素都传递到process函数中,使得开发人员可以在编译器进行相应的处理,例如,根据注解生成新的Java类,这也就是ButterKnife
Dragger等开源库的基本原理

源码

ViewInjectDemo
UML图与流程图都会放在github上

代码还是比较清晰的,bind()方法的流程:

那么APT又是什么呢?

APT(Annotation Processing
Tool)是一种处理注解的工具,它对源代码文件进行检测找出其中的Annotation,使用Annotation进行额外的处理。
Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定),APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件。

下面以Butterknife为例:

public class MainActivity extends AppCompatActivity {

  @BindView(R.id.tv_main)
  TextView tvMain;

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

这是Butterknife最简单的用法,我们只需要加上一个注解
@BindView并指定对应的Id就可以了,从而避免了findViewById(),那么它底层是怎么实现的呢?本篇文章重点不是介绍Butterknife的实现原理,所以这里只是简单的说一下它底层的实现。这里我们只写了一个MainActivity.java文件,编译后我们查看一下class文件,我们会发现在MainActivity中多了一个内部类ViewBinder,其实Butterknife就是在这个内部类中关联对应控件的,下面以伪代码的形式简单说明一个它底层实现的原理。

图片 7

字节码文件

public class MainActivity extends AppCompatActivity {

  @BindView(R.id.tv_main)
  TextView tvMain;

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

  /*
  * 当bind()方法被调用之后,tvMain就有对应的值了
  * */
  private static class ViewBinder{
       public static void bind(MainActivity activity){
           activity.tvMain = (TextView) activity.findViewById(R.id.tv_main);
       }
   }
}

大概的原理是这样的:在编译的时候如果某个类中使用了注解,Butterknife就会在其中“添加”一个内部类,在内部类中实现控件的关联。我们知道编译java源文件的工具是javac,其实在javac中有一个注解处理工具(依赖APT)用来编译时扫描和处理的注解的工具。我们可以为特定的注解,注册你自己的注解处理器,来实现自己的处理逻辑。

上面我们了解了基本原理,接下来我们实战演练

图片 8

项目结构

我们的项目结构如上图所示:每个库都有自己的实现功能,最中通过我们的项目依赖相应的库来使用。

  1. 首先获取当前activity的sourceView,其实就是获取Activity的DecorView,DecorView是整个ViewTree的最顶层View,包含标题view和内容view这两个子元素。我们一直调用的setContentView()方法其实就是往内容view中添加view元素。
  2. 然后调用createBinding() –>
    findBindingConstructorForClass(),重点是

创建App

我们新建一个工程,因为我们要处理注解需要用到APT,所以在app中需要使用apt的插件
<b>github:</b>https://github.com/Aexyn/android-apt

关联APT插件:
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);BINDINGS.put(cls, bindingCtor);
Step1: 在我们工程目录下的build.gradle文件中添加如下代码:

图片 9

按照所写的代码,这里会加载一个MainActivity_ViewBinding类,然后获取这个类里面的双参数(Activity,
View)构造方法,最后放在BINDINGS里面,它是一个map,主要作用是缓存。在下次使用的时候,就可以从缓存中获取到:

Step2: 在我们项目目录下的build.gradle文件中添加如下代码:

图片 10

Constructor<? extends Unbinder> bindingCtor = BINDINGS.get;if (bindingCtor != null) { if  Log.d(TAG, "HIT: Cached in binding map."); return bindingCtor;}

创建Java库(定义注解)

图片 11

inject-annotion

在上面分析过程中,我们知道最后我们会去加载一个MainActivity_ViewBinding类,而这个类并不是我们自己编写的,而是通过编译时注解(APT

创建Android库

图片 12

inject

<b>关联Java库(inject-annotion)</b>

图片 13

关联java库

  • Annotation Processing Tool)的技术生成的。这一节将会介绍一下这个技术。

创建Java库(处理注解库)

我们需要在编译的时候根据注解创建新的类并添加到源文件中,所有需要引用几个依赖。并且要关联上一个Java库

  • com.google.auto.service:auto-service:谷歌提供的Java 生成源代码库
  • com.squareup:javapoet:提供了各种 API 让你用各种姿势去生成 Java
    代码文件
  • com.google.auto:auto-common:生成代码的库

图片 14

依赖

<b>全部创建完毕后,我们的工程目录如下:</b>

图片 15

项目目录

1、什么是注解

注解其实很常见,比如说Activity自动生成的onCreate()方法上面就有一个@Override注解

图片 16image.png

  • 注解的概念:能够添加到 Java
    源代码的语法元数据。类、方法、变量、参数、包都可以被注解,可用来将信息元数据与程序元素进行关联。
  • 注解的分类:
    • 标准注解,如Override, Deprecated,SuppressWarnings等
    • 元注解,如@Retention, @Target, @Inherited,
      @Documented。当我们要自定义注解时,需要使用它们
    • 自定义注解,表示自己根据需要定义的 Annotation
  • 注解的作用:
    • 标记,用于告诉编译器一些信息
    • 编译时动态处理,如动态生成java代码
    • 运行时动态处理,如得到注解信息

关联库

让我们的项目(app)去关联注解库

图片 17

关联库

2、运行时注解 vs 编译时注解

一般有些人提到注解,普遍就会觉得性能低下。但是真正使用注解的开源框架却很多例如ButterKnife,Retrofit等等。所以注解是好是坏呢?首先,并不是注解就等于性能差。更确切的说是运行时注解这种方式,由于它的原理是java反射机制,所以的确会造成较为严重的性能问题。但是像Butterknife这个框架,它使用的技术是编译时注解,它不会影响app实际运行的性能(影响的应该是编译时的效率)。一句话总结:

  • 运行时注解就是在应用运行的过程中,动态地获取相关类,方法,参数等信息,由于使用java反射机制,性能会有问题;
  • 编译时注解由于是在代码编译过程中对注解进行处理,通过注解获取相关类,方法,参数等信息,然后在项目中生成代码,运行时调用,其实和直接运行手写代码没有任何区别,也就没有性能问题了。这样我们就解决了第一个问题。

编写代码

3、如何使用编译时注解技术

这里要借助到一个类:AbstractProcessor

public class TestProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { // TODO Auto-generated method stub return false; } } 

重点是process()方法,它相当于每个处理器的主函数main(),可以在这里写相关的扫描和处理注解的代码,他会帮助生成相关的Java文件。后面我们可以具体看一下Butterknife中的使用。

我们了解了编译时注解的基本概念之后,我们先看一下MainActivity_ViewBinding类具体实现了什么。在编写完demo之后,需要先build一下项目,之后可以在build/generated/source/apt/debug/包名/下面找到这个类,如图所示:

图片 18

接上面的分析,到最后会通过反射的方式去调用MainActivity_ViewBinding的构造方法。我们直接看这个类的构造方法:

@UiThreadpublic MainActivity_ViewBinding(final MainActivity target, View source) { this.target = target; View view; // 1 view = Utils.findRequiredView(source, R.id.text, "field 'textView' and method 'textClick'"); // 2 target.textView = Utils.castView(view, R.id.text, "field 'textView'", TextView.class); // 3 view2131165290 = view; view.setOnClickListener(new DebouncingOnClickListener() { @Override public void doClick { target.textClick;}

1.定义注解:inject-annotion

/**
 * @Retention(RetentionPolicy.CLASS):编译时被保留,在class文件中存在,但JVM将会忽略
 * @Target(ElementType.FIELD) :出现的位置(字段、枚举的常量)
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
    int value();
}
1、findRequiredView()
public static View findRequiredView(View source, @IdRes int id, String who) { View view = source.findViewById; if (view != null) { return view; } String name = getResourceEntryName(source, id); throw new IllegalStateException("Required view '" + name + "' with ID " + id + " for " + who + " was not found. If this view is optional add '@Nullable'  or '@Optional'" + "  annotation."); }

看到这里我们已经解决了第二个问题:到最后还是会调用findViewById()方法,并没有完全舍弃这个方法,这里的source代表着在上面代码中传入的MainActivity的DecorView。大家可以尝试一下将Activity转化为Fragment的情况~

2.定义方法:inject

  • <b>InjectView.java</b>

    public class InjectView {
      public  static  void bind(Activity activity)
      {
          String clsName=activity.getClass().getName();
          try {
              //获取内部类
              Class<?> viewBidClass=Class.forName(clsName+"$$ViewBinder");
              //创建内部类的实例
              ViewBinder viewBinder= (ViewBinder) viewBidClass.newInstance();
              viewBinder.bind(activity);//绑定页面
          } catch (ClassNotFoundException e) {
              e.printStackTrace();
          } catch (InstantiationException e) {
              e.printStackTrace();
          } catch (IllegalAccessException e) {
              e.printStackTrace();
          }
      }
    }
    
  • <b>ViewBinder.java</b>

    public interface ViewBinder <T>{
      void  bind(T tartget);
    }
    
2、Util.castView

在这里,我们解决了第三个问题,绑定各种view时不能使用private修饰,而是需要用public或default去修饰,因为如果采用private修饰的话,将无法通过对象.成员变量方式获取到我们需要绑定的View。Util#castView():

public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) { try { return cls.cast; } catch (ClassCastException e) { String name = getResourceEntryName; throw new IllegalStateException("View '" + name + "' with ID " + id + " for " + who + " was of the wrong type. See cause for more info.", e); }}

这里直接调用Class.cast强制转换类型,将View转化为我们需要的view。

3.处理注解:inject-compiler

  • <b>FieldViewBinding.java</b>

    /**
     * 注解信息封装类
     */
    public class FieldViewBinding {
    
        private String name;// 字段的名字 textview
        private TypeMirror type ;// 字段的类型 --->TextView
        private int resId;// 对应的id R.id.textiew
    
        public FieldViewBinding(String name, TypeMirror type, int resId) {
            this.name = name;
            this.type = type;
            this.resId = resId;
        }
    
        public String getName() {
            return name;
        }
    
        public TypeMirror getType() {
            return type;
        }
    
        public int getResId() {
            return resId;
        }
    }
    
  • <b>BindViewProcessor.java</b>

![](https://upload-images.jianshu.io/upload_images/1797490-2bc6cda9ef915a33.png)

    /**
     * 注解处理类
     */
    @AutoService(Processor.class)
    public class BindViewProcessor extends AbstractProcessor {

        private Elements elementUtils;
        private Types typeUtils;
        private Filer filer;

        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
            elementUtils = processingEnvironment.getElementUtils();
            typeUtils = processingEnvironment.getTypeUtils();
            filer = processingEnvironment.getFiler();
        }

        /* 设置处理那些注解 */
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            Set<String> types = new LinkedHashSet<>();
            types.add(BindView.class.getCanonicalName());
            return types;
        }

        /* 设置支持的JDk版本 */
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }

        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            Map<TypeElement, List<FieldViewBinding>> targetMap = new HashMap<>();
            for (Element element : roundEnvironment.getElementsAnnotatedWith(BindView.class)) {

                TypeElement enClosingElement = (TypeElement) element.getEnclosingElement();
                List<FieldViewBinding> list = targetMap.get(enClosingElement);
                if (list == null) {
                    list = new ArrayList<>();
                    targetMap.put(enClosingElement, list);
                }
                int id = element.getAnnotation(BindView.class).value();
                String fieldName = element.getSimpleName().toString();
                TypeMirror typeMirror = element.asType();
                FieldViewBinding fieldViewBinding = new FieldViewBinding(fieldName, typeMirror, id);
                list.add(fieldViewBinding);
            }
            for (Map.Entry<TypeElement, List<FieldViewBinding>> item : targetMap.entrySet()) {
                List<FieldViewBinding> list = item.getValue();

                if (list == null || list.size() == 0) {
                    continue;
                }
                TypeElement enClosingElement = item.getKey();
                String packageName = getPackageName(enClosingElement);
                String complite = getClassName(enClosingElement, packageName);
                ClassName className = ClassName.bestGuess(complite);
                ClassName viewBinder = ClassName.get("com.example.inject", "ViewBinder");
                TypeSpec.Builder result = TypeSpec.classBuilder(complite + "$$ViewBinder")
                        .addModifiers(Modifier.PUBLIC)
                        .addTypeVariable(TypeVariableName.get("T", className))
                        .addSuperinterface(ParameterizedTypeName.get(viewBinder, className));

                MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
                        .addModifiers(Modifier.PUBLIC)
                        .returns(TypeName.VOID)
                        .addAnnotation(Override.class)
                        .addParameter(className, "target", Modifier.FINAL);
                for (int i = 0; i < list.size(); i++) {
                    FieldViewBinding fieldViewBinding = list.get(i);
                    String pacckageNameString = fieldViewBinding.getType().toString();
                    ClassName viewClass = ClassName.bestGuess(pacckageNameString);
                    methodBuilder.addStatement
                            ("target.$L=($T)target.findViewById($L)", fieldViewBinding.getName()
                                    , viewClass, fieldViewBinding.getResId());
                }
                result.addMethod(methodBuilder.build());

                try {
                    JavaFile.builder(packageName, result.build())
                            .addFileComment("auto create make")
                            .build().writeTo(filer);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return false;
        }

        /* 获取类名 */
        private String getClassName(TypeElement enClosingElement, String packageName) {
            int packageLength = packageName.length() + 1;
            return enClosingElement.getQualifiedName().toString().substring(packageLength).replace(".", "$");
        }

        /* 获取包名 */
        private String getPackageName(TypeElement enClosingElement) {
            return elementUtils.getPackageOf(enClosingElement).getQualifiedName().toString();
        }
    }
3、
view2131165290 = view;view.setOnClickListener(new DebouncingOnClickListener() { @Override public void doClick { target.textClick;

这里会生成一个成员变量来保存我们需要绑定的View,重点是下面它会调用setOnClickListener()方法,传入的是DebouncingOnClickListener:

/** * A {@linkplain View.OnClickListener click listener} that debounces multiple clicks posted in the * same frame. A click on one button disables all buttons for that frame. */public abstract class DebouncingOnClickListener implements View.OnClickListener { static boolean enabled = true; private static final Runnable ENABLE_AGAIN = new Runnable() { @Override public void run() { enabled = true; } }; @Override public final void onClick { if  { enabled = false; v.post(ENABLE_AGAIN); doClick; } } public abstract void doClick;}

这个DebouncingOnClickListener是View.OnClickListener的一个子类,作用是防止一定时间内对view的多次点击,即防止快速点击控件所带来的一些不可预料的错误。个人认为这个类写的非常巧妙,既完美解决了问题,又写的十分优雅,一点都不臃肿。这里抽象了doClick()方法,实现代码中是直接调用了target.textClick(),这里解决了第四个问题:绑定监听事件的时候方法命名是没有限制的,不一定需要严格命名为onClick,也不一定需要传入View参数。

上文提到,MainActivity_ViewBinding类是通过编译时注解技术生成的,我们找到Butterknife相关的继承于AbstractProcessor的类,ButterKnifeProcessor,我们直接看process()方法:

public final class ButterKnifeProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) { // 1 Map<TypeElement, BindingSet> bindingMap = findAndParseTargets; for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet { TypeElement typeElement = entry.getKey(); BindingSet binding = entry.getValue(); JavaFile javaFile = binding.brewJava(sdk, debuggable); try { javaFile.writeTo; } catch (IOException e) { error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage; } } return false; }}

1、findAndParseTargets()这个方法的作用是处理所有的@BindXX注解,我们直接看处理@BindView的部分:

private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) { // 省略代码 // Process each @BindView element. for (Element element : env.getElementsAnnotatedWith(BindView.class)) { // we don't SuperficialValidation.validateElement // so that an unresolved View type can be generated by later processing rounds try { parseBindView(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindView.class, e); } } // 省略代码}private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) { TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // Start by verifying common generated code restrictions. boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element) || isBindingInWrongPackage(BindView.class, element); // Verify that the target type extends from View. TypeMirror elementType = element.asType(); if (elementType.getKind() == TypeKind.TYPEVAR) { TypeVariable typeVariable = (TypeVariable) elementType; elementType = typeVariable.getUpperBound(); } Name qualifiedName = enclosingElement.getQualifiedName(); Name simpleName = element.getSimpleName(); if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) { if (elementType.getKind() == TypeKind.ERROR) { note(element, "@%s field with unresolved type  " + "must elsewhere be generated as a View or interface. ", BindView.class.getSimpleName(), elementType, qualifiedName, simpleName); } else { error(element, "@%s fields must extend from View or be an interface. ", BindView.class.getSimpleName(), qualifiedName, simpleName); hasError = true; } } if  { return; } // Assemble information on the field. int id = element.getAnnotation(BindView.class).value(); BindingSet.Builder builder = builderMap.get(enclosingElement); QualifiedId qualifiedId = elementToQualifiedId(element, id); if (builder != null) { String existingBindingName = builder.findExistingBindingName(getId(qualifiedId)); if (existingBindingName != null) { error(element, "Attempt to use @%s for an already bound ID %d on '%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; builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required)); // Add the type-erased version to the valid binding targets set. erasedTargetNames.add(enclosingElement);}

代码逻辑是处理获取相关注解的信息,比如绑定的资源id等等,然后通过获取BindingSet.Builder类的实例来创建一一对应的关系,这里有一个判断,如果builderMap存在相应实例则直接取出builder,否则通过getOrCreateBindingBuilder()方法生成一个新的builder,最后调用builder.addField()方法。

后续的话返回到findAndParseTargets()方法的最后一部分:

private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) { // bindView() // Associate superclass binders with their subclass binders. This is a queue-based tree walk // which starts at the roots (superclasses) and walks to the leafs (subclasses). Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries = new ArrayDeque<>(builderMap.entrySet; Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>(); while (!entries.isEmpty { Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst(); TypeElement type = entry.getKey(); BindingSet.Builder builder = entry.getValue(); TypeElement parentType = findParentType(type, erasedTargetNames); if (parentType == null) { bindingMap.put(type, builder.build; } else { BindingSet parentBinding = bindingMap.get(parentType); if (parentBinding != null) { builder.setParent(parentBinding); bindingMap.put(type, builder.build; } else { // Has a superclass binding but we haven't built it yet. Re-enqueue for later. entries.addLast; } } } return bindingMap;}

这里会生成一个bindingMap,key为TypeElement,代表注解元素类型,value为BindSet类,通过上述的builder.build()生成,BindingSet类中存储了很多信息,例如绑定view的类型,生成类的className等等,方便我们后续生成java文件。最后回到process方法:

@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) { Map<TypeElement, BindingSet> bindingMap = findAndParseTargets; for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet { TypeElement typeElement = entry.getKey(); BindingSet binding = entry.getValue(); JavaFile javaFile = binding.brewJava(sdk, debuggable); try { javaFile.writeTo; } catch (IOException e) { error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage; } } return false;}

最后通过brewJava()方法生成java代码。这里使用到的是javapoet。javapoet是一个开源库,通过处理相应注解来生成最后的java文件,这里是项目地址传送门,具体技术不再分析。

这篇文章会同步到我的个人日志,如有问题,请大家踊跃提出,谢谢大家!

测试

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.text)
    TextView textview;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        InjectView.bind(this);
        if(textview == null){
            Toast.makeText(this,"注解处理失败",Toast.LENGTH_SHORT).show();
        }else{
            textview.setText("世界你好!");
        }
    }
}

用法跟Butterknife一样,页面上有一个TextView,使用注解关联,如果关联失败,弹出提示信息。否则设置显示为“世界你好!”。

图片 19

演示

总结

通过上述代码的编写,我们能更加对Butterknife的底层实现有更清楚的认识,虽然只是实现了绑定View。在编译时javac编译器会检查AbstractProcessor的子类,并且调用该类型的process函数,然后将添加了注解的所有元素都传递到process函数中,我们需要继承该类重写此方法我们就能获取我们想要处理的注解。在里面做具体的绑定逻辑。
AutoService注解处理器是Google开发的,用来生成META-INF/services/javax.annotation.processing.Processor文件的。我们可以在注解处理器中使用注解。非常方便。

关注微信公众号获取更多相关资源

图片 20

Android小先生

发表评论

电子邮件地址不会被公开。 必填项已用*标注