美文网首页
编译时注解-ButterKnife框架原理分析及手写

编译时注解-ButterKnife框架原理分析及手写

作者: _风听雨声 | 来源:发表于2020-04-06 11:59 被阅读0次

引言:

本文基于ButterKnife当前的最新版本(10.2.1)(部分内容借鉴于https://www.jianshu.com/p/100708625605,感谢Darren),ButterKnife这个框架,应该很多人都不会陌生,这个框架帮我们省略了自己写findViewById,setOnClickListener...等等步骤,是一个比较知名的优秀框架之一。那么,它的原理是怎样呢?


butterknife框架结构

可以看到,作者将主要功能其分成了几个部分,butterknife-annotations、butter-compiler、butterknife,他为什么要这样分呢?让我们先看下他的每个部分是干什么的。


butterknife-annotations

是一个javaLib(为什么用javaLib,而不是用AndroidLib,因为不需要,哈哈,废话...),主要是存放annotation的

butterknife-compiler

是一个javaLib(为什么用javaLib,因为要用java的注解处理类AbstractProcessor,这个类javaLib才有,AndroidLib没有),butterknife-compiler主要是做注解处理,并在编译时期生成XXX_ViewBinding的类和代码,如果在Activity中使用,这个XXX就是Activity的类名。生成的代码如下:

public class MainActivity_ViewBinding implements Unbinder {
  private MainActivity target;

  @UiThread
  public MainActivity_ViewBinding(MainActivity target) {
    this(target, target.getWindow().getDecorView());
  }

  @UiThread
  public MainActivity_ViewBinding(MainActivity target, View source) {
    this.target = target;

    target.tv1 = Utils.findRequiredViewAsType(source, R.id.tv1, "field 'tv1'", TextView.class);
    target.tv2 = Utils.findRequiredViewAsType(source, R.id.tv2, "field 'tv2'", TextView.class);
  }

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

    target.tv1 = null;
    target.tv2 = null;
  }
butterknife

是一个androidLib,butterknife主要职责是通过反射初始化生成的XXX_ViewBinding类,如果是在Activity中使用,就是将当前的Activity作为参数传入XXX_ViewBinding的构造方法。主要代码:

@NonNull @UiThread
    public static Unbinder bind(@NonNull Object target, @NonNull View source) {
        Class<?> targetClass = target.getClass();
        Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
        if (constructor == null) {
            return Unbinder.EMPTY;
        }   
    }

    @Nullable
    @CheckResult
    @UiThread
    private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
        Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
        try {
            Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding")
            bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
            if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
        } catch (ClassNotFoundException e) 
        }
        BINDINGS.put(cls, bindingCtor);
        return bindingCtor;
    }
小结:作者将其分成几个lib的主要原因应该是butterknife-compiler主要职责处理注解和生成代码,需要使用annotationProcessor,来添加依赖,butterknife-compiler在运行时不会被使用,所以需要把它分出来,符合单一职责思想。annotationProcessor仅仅是在编译期使用,并不会打包到apk中。butterknife-annotations里面的注解在项目需要依赖,butterknife-compiler也需要依赖,所以作者也将其分了出来。butterknife是反射获取XXX_ViewBinding,项目需要依赖,butterknife-compiler不需要依赖,所以也分了lib出来。

关于手写ButterKnife

此处并非要去重复造轮子,主要是为了更好的吸取作者的一些好的编程思想,以运用到工作中,仅供学习用途。接下来让我们开始吧。

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

1.新建javaLib,butterknife-annotations,写好第一个编译期注解BindView
2.新建javaLib,butterknife-compiler,导入依赖 javapoet和auto-service,使用javapoet生成java类,auto-service在这里的作用是自动让IDE生成META-INF,表明我们接下来写的ButterKnifeProcessor是一个注解处理类。

implementation 'com.google.auto.service:auto-service:1.0-rc6'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6' //gradle3.0以上版本需要
implementation 'com.squareup:javapoet:1.12.1'

3.butterknife-compiler新建ButterKnifeProcessor继承自AbstractProcessor,使用@AutoService(Processor.class)注解。

@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
    private Filer mFiler;
    private Elements mElementUtils;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        mElementUtils = processingEnv.getElementUtils();
        mFiler = processingEnv.getFiler();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
            types.add(annotation.getCanonicalName());
        }
        return types;
    }

    private Set<Class<? extends Annotation>> getSupportedAnnotations() {
        Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
        annotations.add(BindView.class);
        return annotations;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
}

4.ButterKnifeProcessor内处理BindView注解和生成代码

@Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //获取所有BindView注解的view
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
        //创建map key -> activity value -> activity下所有BindView注解的view
        Map<Element, List<Element>> elementListMap = new LinkedHashMap<>();
        for (Element element : elements) {
            //activity
            Element enclosingElement = element.getEnclosingElement();
            List<Element> elementList = elementListMap.get(enclosingElement);
            if (elementList == null) {
                elementList = new ArrayList<>();
                elementListMap.put(enclosingElement, elementList);
            }
            elementList.add(element);
        }
        //遍历elementListMap
        for (Map.Entry<Element, List<Element>> entry : elementListMap.entrySet()) {
            Element element = entry.getKey();
            List<Element> viewElements = entry.getValue();
            String activityClassNameStr = element.getSimpleName().toString();
            ClassName activityClassName = ClassName.bestGuess(activityClassNameStr);
            ClassName unBinderClassName = ClassName.get("com.butterknife", "Unbinder");
            TypeSpec.Builder classBuilder = TypeSpec.classBuilder(activityClassNameStr + "_ViewBinding")
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                    .addSuperinterface(unBinderClassName)
                    .addField(activityClassName, "target", Modifier.PRIVATE);

            //构造函数
            ClassName UIThreadAnnotationClassName = ClassName.get("androidx.annotation",
                    "UiThread");
            MethodSpec.Builder constructorMethodBuilder = MethodSpec.constructorBuilder()
                    .addAnnotation(UIThreadAnnotationClassName)
                    .addParameter(activityClassName, "target")
                    .addModifiers(Modifier.PUBLIC)
                    .addCode("this(target, target.getWindow().getDecorView());");

            ClassName viewClassName = ClassName.get("android.view", "View");
            MethodSpec.Builder constructorMethodBuilder2 = MethodSpec.constructorBuilder()
                    .addAnnotation(UIThreadAnnotationClassName)
                    .addParameter(activityClassName, "target")
                    .addParameter(viewClassName, "source")
                    .addModifiers(Modifier.PUBLIC)
                    .addCode("this.target = target;\n");

            ClassName utilsClassName = ClassName.get("com.butterknife", "Utils");
            for (Element viewElement : viewElements) {
                String fieldName = viewElement.getSimpleName().toString();
                int resId = viewElement.getAnnotation(BindView.class).value();
                String type = viewElement.asType().toString();
                constructorMethodBuilder2.addCode("target.$L = $T.findRequiredViewAsType(source," +
                        " $L, \"field '$L'\", $L.class);\n", fieldName, utilsClassName, resId, fieldName, type);
            }

            //接口方法
            ClassName callSuperAnnotationClassName = ClassName.get("androidx.annotation",
                    "CallSuper");
            MethodSpec.Builder interfaceMethodBuilder = MethodSpec.methodBuilder("unbind")
                    .addAnnotation(Override.class)
                    .addAnnotation(callSuperAnnotationClassName)
                    .addModifiers(Modifier.PUBLIC)
                    .addCode("$L target = this.target;\n", activityClassName)
                    .addCode("if (target == null) throw new IllegalStateException(\"Bindings already cleared.\");\n")
                    .addCode("this.target = null;\n");
            for (Element viewElement : viewElements) {
                String fieldName = viewElement.getSimpleName().toString();
                interfaceMethodBuilder.addCode("target.$L = null;\n", fieldName);
            }

            classBuilder.addMethod(constructorMethodBuilder.build());
            classBuilder.addMethod(constructorMethodBuilder2.build());
            classBuilder.addMethod(interfaceMethodBuilder.build());
            String packageName = mElementUtils.getPackageOf(element).getQualifiedName().toString();
            try {
                JavaFile.builder(packageName, classBuilder.build())
                        .build().writeTo(mFiler);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

5.新建butterknife,AndroidLib,反射调用生成的XXX_ViewBinding构造方法,传入参数。

public class ButterKnife {
    private ButterKnife() {
        throw new AssertionError("No instances.");
    }

    private static final String TAG = "ButterKnife";
    private static boolean debug = false;

    @VisibleForTesting
    static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();

    @NonNull
    @UiThread
    public static Unbinder bind(@NonNull Activity target) {
        View sourceView = target.getWindow().getDecorView();
        return bind(target, sourceView);
    }

    @NonNull
    @UiThread
    public static Unbinder bind(@NonNull Object target, @NonNull View source) {
        Class<?> targetClass = target.getClass();
        Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
        if (constructor == null) {
            return Unbinder.EMPTY;
        }
        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);
        }
    }

    @Nullable
    @CheckResult
    @UiThread
    private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
        Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
        if (bindingCtor != null || BINDINGS.containsKey(cls)) {
            if (debug) Log.d(TAG, "HIT: Cached in binding map.");
            return bindingCtor;
        }
        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 {
            Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
            //noinspection unchecked
            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;
    }
}

相关文章

网友评论

      本文标题:编译时注解-ButterKnife框架原理分析及手写

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