08 ButterKnife源码解析

作者: 凤邪摩羯 | 来源:发表于2021-08-31 09:12 被阅读0次

    本文基于ButterKnife版本:

    'com.jakewharton:butterknife:10.2.1'

    'com.jakewharton:butterknife-compiler:10.2.1'

    1-自定义注解处理器

    java代码编译期,javac会调用java注解器来处理注解相关(属于编译期注解)。先看下butterknife-compiler库中的核心ButterKnifeProcessor

    通过继承AbstractProcessor实现自定义处理器,重写以下方法

    public class ButterKnifeProcessor extends AbstractProcessor {
    
        //指定java版本
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }
    
        //(1)初始化辅助类
        @Override
        public synchronized void init(ProcessingEnvironment env) {
            super.init(env);
            String sdk = env.getOptions().get(OPTION_SDK_INT);
            if (sdk != null) {
            try {
                this.sdk = Integer.parseInt(sdk);
            } catch (NumberFormatException e) {
                env.getMessager()
                    .printMessage(Kind.WARNING, "Unable to parse supplied minSdk option '"
                        + sdk
                        + "'. Falling back to API 1 support.");
            }
            debuggable = !"false".equals(env.getOptions().get(OPTION_DEBUGGABLE));
            typeUtils = env.getTypeUtils();
            filer = env.getFiler();
            try {
                trees = Trees.instance(processingEnv);
            } catch (IllegalArgumentException ignored) {
                try {
                    // Get original ProcessingEnvironment from Gradle-wrapped one or KAPT-wrapped one.
                    for (Field field : processingEnv.getClass().getDeclaredFields()) {
                      if (field.getName().equals("delegate") || field.getName().equals("processingEnv")) {
                        field.setAccessible(true);
                        ProcessingEnvironment javacEnv = (ProcessingEnvironment) field.get(processingEnv);
                        trees = Trees.instance(javacEnv);
                        break;
                      }
                    }
                } catch (Throwable ignored2) {
                }
            }
        }
    
        //返回目标注解集合
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            Set<String> types = new LinkedHashSet<>();
            for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
                types.add(annotation.getCanonicalName());
            }
            return types;
        }
    
        //(2)核心方法。先扫描查找注解,再生成java文件
        @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
        //@1.查找注解信息,生成BindingSet并保存到bingdingMap中
        Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
        //@4.遍历bindingMap,生成对应的 className_ViewBinding.java文件
        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(filer);
          } catch (IOException e) {
            error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
          }
        }
        return false;
      }
    }
    
    

    (1)初始相关辅助类init()

    init方法中涉及的几个辅助类\变量

    (2)核心操作process()方法
    @1.扫描查找注解

    private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
        Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
        Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
    
        // @BindView 注解
        for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
          try {
            //@2.处理BindView的注解
            parseBindView(element, builderMap, erasedTargetNames);
          } catch (Exception e) {
            logParsingError(element, BindView.class, e);
          }
        }
        //省略@BindViews @BindString等注解处理
        。。。
    
        //@3.监听处理,OnClick,OnItemClick等
        for (Class<? extends Annotation> listener : LISTENERS) {
          findAndParseListener(env, listener, targetClassMap, erasedTargetNames);
        }
    
        for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
          TypeElement parentType = findParentType(entry.getKey(), erasedTargetNames);
          if (parentType != null) {
            BindingClass bindingClass = entry.getValue();
            BindingClass parentBindingClass = targetClassMap.get(parentType);
            bindingClass.setParent(parentBindingClass);
          }
        }
    }
    
    

    @2.处理@BindView。这里涉及到BindingSet辅助类,缓存相关注解信息。ViewBiding

    private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
          Set<TypeElement> erasedTargetNames) {
        TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
    
        // 检测注解语法是否合法。注解方法不能为private、static,注解类不能是android/java包
        // 若非法,抛出异常
        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 (%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;
        }
    
        // 获取注解中的对应的viewId
        int id = element.getAnnotation(BindView.class).value();
        BindingSet.Builder builder = builderMap.get(enclosingElement);
        Id resourceId = elementToId(element, BindView.class, id);
        if (builder != null) {
            //判断ID是否已经绑定过,如果已绑定则抛出异常
          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 {
            //未绑定过,则创建BindingSet.Builder
          builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
        }
    
        String name = simpleName.toString();
        TypeName type = TypeName.get(elementType);
        boolean required = isFieldRequired(element);
        //将当前注解生成FieldViewBinding并添加到builder中
        builder.addField(resourceId, new FieldViewBinding(name, type, required));
    
        // Add the type-erased version to the valid binding targets set.
        erasedTargetNames.add(enclosingElement);
      }
    
    

    @3.监听处理,OnClick,OnItemClick等

    private void findAndParseListener(RoundEnvironment env,
          Class<? extends Annotation> annotationClass,
          Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) {
        for (Element element : env.getElementsAnnotatedWith(annotationClass)) {
          if (!SuperficialValidation.validateElement(element)) continue;
          try {
            //核心方法,解析注解
            //并将listener注解对应的信息封装到MethodViewBinding中
            //将MethodViewBinding添加到builder中
            parseListenerAnnotation(annotationClass, element, builderMap, erasedTargetNames);
          } catch (Exception e) {
            StringWriter stackTrace = new StringWriter();
            e.printStackTrace(new PrintWriter(stackTrace));
    
            error(element, "Unable to generate view binder for @%s.\n\n%s",
                annotationClass.getSimpleName(), stackTrace.toString());
          }
        }
      }
    
    

    (3)注册自定义注解处理器。通过google提供的auto-service库来通过注解注册

    compile 'com.google.auto.service:auto-service:1.0-rc2

    @AutoService(Processor.class)
    public class ButterKnifeProcessor extends AbstractProcessor {
        ...
    }
    
    

    2-生成对应的className_ViewBinding.java

    @4.遍历bindingMap,生成对应的 className_ViewBinding.java文件。例如业务使用

    //ActivityB.java
    public class ActivityB extends BaseActivity{
        @BindView(R.id.edit_txt)
        EditText editText;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_b);
            //@1.绑定
            ButterKnife.bind(this);
        }
    
        @OnClick(R.id.btn_one)
        protected void postMsg() {
            String msg = editText.getText().toString();
            EventBus.getDefault().post(MessageWrap.getInstance(msg));
            this.finish();
        }
    }
    
    

    经过安装编译后在指定目录下生成了ActivityB_ViewBinding.java。对应的方法都是指定在UI线程执行,且通过持有目标引用直接调用对应的属性或方法,所以要求注解的变量或方法必须为非private、static

    public class ActivityB_ViewBinding implements Unbinder {
      private ActivityB target;//目标类的引用
    
      private View view7f070044;//与click事件绑定的view
    
      @UiThread
      public ActivityB_ViewBinding(ActivityB target) {
        this(target, target.getWindow().getDecorView());
      }
    
      @UiThread
      public ActivityB_ViewBinding(final ActivityB target, View source) {
        this.target = target;
        //实现findViewById及setOnClickListener
        View view;
        target.editText = Utils.findRequiredViewAsType(source, R.id.edit_txt, "field 'editText'", EditText.class);
        view = Utils.findRequiredView(source, R.id.btn_one, "method 'postMsg'");
        view7f070044 = view;
        view.setOnClickListener(new DebouncingOnClickListener() {
          @Override
          public void doClick(View p0) {
            target.postMsg();
          }
        });
      }
        //解绑
      @Override
      @CallSuper
      public void unbind() {
        ActivityB target = this.target;
        if (target == null) throw new IllegalStateException("Bindings already cleared.");
        this.target = null;
    
        target.editText = null;
    
        view7f070044.setOnClickListener(null);
        view7f070044 = null;
      }
    }
    
    

    使用原理,在ActivityB.setContentView之后调用ButterKnife.bind(this)来完成绑定。-->ButterKnife.bind(target, sourceView)。target即为ActivityB的实例,sourceView为其DecorView

    @NonNull @UiThread
      public static Unbinder bind(@NonNull Object target, @NonNull View source) {
        //通过ActivityB的类名拼接获取到对应的ActivityB_ViewBinding.java类名
        Class<?> targetClass = target.getClass();
        if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
        //@5.获取对应ViewBinding类的构造器
        Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
    
        if (constructor == null) {
          return Unbinder.EMPTY;
        }
    
        //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
        try {
            //创建对应的ViewBinding类实例
            //即在ViewBinding初始化时完成了ActivityB的
            //findViewById及setOnClickListener等操作
          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);
        }
      }
    
    

    @5.获取对应ViewBinding类的构造器

    @Nullable @CheckResult @UiThread
      private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
        Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
        //从缓存中读取对应class的构造器
        if (bindingCtor != null || BINDINGS.containsKey(cls)) {
          if (debug) Log.d(TAG, "HIT: Cached in binding map.");
          return bindingCtor;
        }
        //java\android\androidx包下的类不处理
        String clsName = cls.getName();
        if (clsName.startsWith("android.") || clsName.startsWith("java.")
            || clsName.startsWith("androidx.")) {
          if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
          return null;
        }
        try {
            //获取ClassLoader并加载对应的ViewBinding类
          Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
          //通过反射获取ViewBinding的Class对象的Constructor即构造器
          bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
          if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
        } catch (ClassNotFoundException e) {
          if (debug) 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;
      }
    
    

    3-流程总结

    • (1)自定义AnnotationProcessor,ButterKnifeProcessor并注册
    • (2).java-->.class的过程中,通过ButterKnifeProcessor生成ViewBinding类文件
      • 处理@BindView@BindViews等注解,根据注解类型封装相关信息到ViewBinding、ResourceBinding等
      • 处理@OnClick@OnItemClick等Listener注解,将相关注解信息及对应的方法信息封装到MethodViewBinding
      • 这些注解信息都在class对应的注解辅助类BindingSet中,以绑定类的TypeElement为key,类中所有注解信息BindingSet为value,添加到bindingMap中
      • 遍历bindingMap,通过BindingSet相关信息生成绑定类的对应的_ViewBinding类文件。
    • (3)Activity中调用ButterKnife.bind(this)
    • (4)反射获取编译期生成的Activity_ViewBinding文件,通过ClassLoader加载,反射获取其构造方法并调用
    • (5)_ViewBinding类的构造方法中持有目标类的引用,调用目标类的findViewbyId、setOnClickListener等操作完成绑定。

    相关文章

      网友评论

        本文标题:08 ButterKnife源码解析

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