美文网首页
Butterknife源码解析

Butterknife源码解析

作者: 海之韵Baby | 来源:发表于2017-10-11 10:43 被阅读0次

    前言

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

    Butterknife的使用

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

    Butterknife的原理

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

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

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

    Butterknife源码解析

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

    编译时自动生成代码

    首先来看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;
      }
    }
    

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

    代码绑定

    用过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——让你从重复无聊的代码中解放出来》

    相关文章

      网友评论

          本文标题:Butterknife源码解析

          本文链接:https://www.haomeiwen.com/subject/vqpvyxtx.html