美文网首页Android新优化Android技术知识Android开发
安卓自定义注解实战之从零仿写ButterKnife源码的Bind

安卓自定义注解实战之从零仿写ButterKnife源码的Bind

作者: 我是黄教主啊 | 来源:发表于2019-11-14 17:43 被阅读0次

    从这次实战我能学会什么

    实战要实现的功能
    完全仿写ButterKnife的bindView注解原理,实现在Activity中控件id赋值功能

    实战看点
    这次实战是从源码出发,仿照ButterKnife的源码实现其中的BindView的功能,实际上就是把ButterKnife的BindView的逻辑剥离出来单独实现,代码量少了许多但与源码差别不大,达到了练习注解的目的。所以我们学会了这个,你会发现,看ButterKnife的源码突然变得简单了

    oh

    实战基础
    了解并能运用AutoService,JavaPoet,AbstractProcessor
    不熟悉的,请先参考我之前的文章:安卓使用注解处理器自动生成代码操作详解(AutoService,JavaPoet,AbstractProcessor)

    下面我们就来通过实战探究ButterKnife的注解奥秘

    ButterKnife原理的简单介绍

    既然要仿写,必然要先对仿写对象的实现原理要有一些了解
    这是它的简单使用:

    public class MainActivity extends AppCompatActivity {
        @BindView(R.id.tv1)
        public TextView textView1;
    
        private Unbinder unbinder;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            unbinder = ButterKnife.bind(this);
            //试着运行一下吧
            textView1.setText("这是一个赋值测试");
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            unbinder.unbind();
        }
    }
    

    运行后我们会发现生成了一个MainActivity_ViewBinding文件,结合我上篇文章我们知道这是使用了注解处理器:

    // Generated code from Butter Knife. Do not modify!
    package com.jay.bindview;
    
    import android.view.View;
    import android.widget.TextView;
    import androidx.annotation.CallSuper;
    import androidx.annotation.UiThread;
    import butterknife.Unbinder;
    import butterknife.internal.Utils;
    import java.lang.IllegalStateException;
    import java.lang.Override;
    
    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.textView1 = Utils.findRequiredViewAsType(source, R.id.tv1, "field 'textView1'", TextView.class);
      }
    
      @Override
      @CallSuper
      public void unbind() {
        MainActivity target = this.target;
        if (target == null) throw new IllegalStateException("Bindings already cleared.");
        this.target = null;
    
        target.textView1 = null;
      }
    }
    
    

    欸,是不是执行了MainActivity_ViewBinding的构造方法就完成了控件赋值了,那么这个方法在哪执行的呢,看ButterKnife.bind(this)方法,最终会到这:

      @NonNull @UiThread
      public static Unbinder bind(@NonNull Object target, @NonNull View source) {
        Class<?> targetClass = target.getClass();
        if (debug) 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 (Error) cause;
          }
          throw new RuntimeException("Unable to create binding instance.", cause);
        }
      }
    

    注意constructor.newInstance(target, source);这行代码,这是通过反射获取到类然后执行这个类的构造方法,这样一分析,实现流程是不是一目了然了,先通过BindView注解获取到控件的id,控件的类型和控件父类的名称(用于生成特定的类文件名和控件赋值),然后生成代码,最后通过反射执行生成的类的构造方法,是不是就实现了我们想要的功能。
    下面开始撸码

    BindView代码实现

    既然是仿,我们仿的像一点,先新建一个项目,创建3个library:

    • bindview-annotation 注意是java library,用于存放注解
    • bindview-compiler 注意还是java library,用于处理注解,生成代码
    • bindviewlib 安卓library,用于反射调用生成的代码
      bindviewlib 和bindview-compiler都需要依赖bindview-annotation

    项目结构如图所示:


    project.jpg

    我们先在bindview-annotation中创建一个BindView注解:

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

    随后,我们就按照源码在bindview-compiler中新建4个类:

    • BindingSet 代码统筹管理类,处理代码生成逻辑
    • FieldViewBinding 用来保存字段的信息
    • ID 用来保存id信息
    • ViewBinding 用来保存FieldViewBinding 和 ID的实例,方便管理和缓存

    先看FieldViewBinding,就简单的保存了两个字段信息,TypeName是字段的类型,相当于我的生成的代码里的TextView,而name相当于我们的代码里的textview1

    final class FieldViewBinding {
        //字段名称
        private final String name;
        //字段类型
        private final TypeName type;
    
        FieldViewBinding(String name,TypeName type){
            this.name = name;
            this.type = type;
        }
    
        public String getName() {
            return name;
        }
    
        public TypeName getType() {
            return type;
        }
    
        public ClassName getRawType() {
            if (type instanceof ParameterizedTypeName) {
                return ((ParameterizedTypeName) type).rawType;
            }
            return (ClassName) type;
        }
    }
    

    再看ID类,value就是我们从注解中获取到的id,传入到CodeBlock中方便生成代码

    final class ID {
        /**
         * value及注解中的value id
         */
        final CodeBlock code;
    
        ID(int value){
            this.code = CodeBlock.of("$L", value);
        }
    }
    

    再来看一下ViewBinding类,仿照源码用了构建者模式,保存了IDFieldViewBinding的值:

    final class ViewBinding {
    
        private final ID id;
    
        @Nullable
        private final FieldViewBinding fieldBinding;
    
    
        private ViewBinding(ID id, @Nullable FieldViewBinding fieldBinding) {
            this.id = id;
            this.fieldBinding = fieldBinding;
        }
    
        public ID getId() {
            return id;
        }
    
        @Nullable
        public FieldViewBinding getFieldBinding() {
            return fieldBinding;
        }
    
        static final class Builder {
            private final ID id;
    
            @Nullable
            private FieldViewBinding fieldBinding;
    
            Builder(ID id) {
                this.id = id;
            }
    
            public void setFieldBinding(FieldViewBinding fieldBinding) {
                if (this.fieldBinding != null) {
                    throw new AssertionError();
                }
                this.fieldBinding = fieldBinding;
            }
    
            public ViewBinding build() {
                return new ViewBinding(id, fieldBinding);
            }
        }
    }
    

    最后就是我们的核心类BindingSet了,看看是怎么来创建代码的吧:

    final class BindingSet{
    
        private final TypeName targetTypeName; //示例值 MainActivity
        private final ClassName bindingClassName; //示例值 MainActivity_ViewBinding
        private final TypeElement enclosingElement; //这是注解元素的父类Element,用于获取父类元素
        private final ImmutableList<ViewBinding> viewBindings; //保存了每一个字段的元素
    
        private BindingSet(
                TypeName targetTypeName, ClassName bindingClassName, TypeElement enclosingElement,
                ImmutableList<ViewBinding> viewBindings) {
            this.targetTypeName = targetTypeName;
            this.bindingClassName = bindingClassName;
            this.enclosingElement = enclosingElement;
            this.viewBindings = viewBindings;
        }
    
        /**
         * 从这个方法开始构建代码,这里只实现BindView的代码逻辑
         *
         * @return JavaFile
         */
        JavaFile brewJava() {
            TypeSpec bindingConfiguration = createType();
            return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
                    .addFileComment("Generated code from Butter Knife. Do not modify!")
                    .build();
        }
    
        private TypeSpec createType() {
            //第一步 先创建类
            TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
                    .addModifiers(Modifier.PUBLIC)
                    .addOriginatingElement(enclosingElement); //设置注解处理器的源元素
            //添加解绑接口
            result.addSuperinterface(ClassName.get("com.jay.bindviewlib", "Unbinder"));
            //添加activity字段target
            result.addField(targetTypeName, "target");
            //添加构造方法
            result.addMethod(createBindingConstructorForActivity());
            //添加找id的方法
            result.addMethod(createBindingConstructor());
            //添加解绑的方法
            result.addMethod(createBindingUnbindMethod());
            return result.build();
        }
    
        /**
         * 示例:MainActivity_BindView(MainActivity target){
         * this(target, target.getWindow().getDecorView())
         * }
         *
         * @return MethodSpec
         */
        private MethodSpec createBindingConstructorForActivity() {
            MethodSpec.Builder builder = MethodSpec.constructorBuilder()
                    .addModifiers(PUBLIC)
                    .addParameter(targetTypeName, "target");
            builder.addStatement("this(target, target.getWindow().getDecorView())");
            return builder.build();
        }
    
        private static final ClassName VIEW = ClassName.get("android.view", "View");
    
        /**
         * 创建构造方法,这个方法里包含找id的代码
         *
         * @return MethodSpec
         */
        private MethodSpec createBindingConstructor() {
            MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
                    .addModifiers(PUBLIC);
            constructor.addParameter(targetTypeName, "target");
            constructor.addParameter(VIEW, "source");
            constructor.addStatement("this.target = target");
            constructor.addCode("\n");
            //这里循环创建控件赋值代码
            for (ViewBinding binding : viewBindings) {
                addViewBinding(constructor, binding);
            }
            return constructor.build();
        }
    
        //创建一条赋值代码
        //示例:target.textview1 = (TextView)source.findViewById(id)
        //这里的source = target.getWindow().getDecorView() target是Activity
        private void addViewBinding(MethodSpec.Builder result, ViewBinding binding) {
            FieldViewBinding fieldBinding = requireNonNull(binding.getFieldBinding());
            CodeBlock.Builder builder = CodeBlock.builder()
                    .add("target.$L = ", fieldBinding.getName()); //添加代码 target.textview1 =
            builder.add("($T) ", fieldBinding.getType()); //添加强转代码
            builder.add("source.findViewById($L)", binding.getId().code); //找id
            result.addStatement("$L", builder.build()); //将代码添加到方法中
        }
    
    
        /**
         * 创建解绑的方法
         *
         * @return MethodSpec
         */
        private MethodSpec createBindingUnbindMethod() {
            MethodSpec.Builder result = MethodSpec.methodBuilder("unbind")
                    .addAnnotation(Override.class)
                    .addModifiers(PUBLIC);
            result.addStatement("$T target = this.target", targetTypeName);
            result.addStatement("if (target == null) throw new $T($S)", IllegalStateException.class,
                    "Bindings already cleared.");
            result.addStatement("$N = null","this.target");
            result.addCode("\n");
            for (ViewBinding binding : viewBindings) {
                if (binding.getFieldBinding() != null) {
                    result.addStatement("target.$L = null", binding.getFieldBinding().getName());
                }
            }
            return result.build();
        }
    
        /**
         * 生成代码生成的类的类名
         * @return Name  规则 ActivityName__ViewBinding
         */
        static ClassName getBindingClassName(TypeElement typeElement) {
            String packageName = getPackage(typeElement).getQualifiedName().toString();
            String className = typeElement.getQualifiedName().toString().substring(
                    packageName.length() + 1).replace('.', '$');
            return ClassName.get(packageName, className + "_ViewBinding");
        }
    
        /**
         * 创建一个Builder
         * @param enclosingElement 父类元素,也就是那个Activity
         * @return 这里生成了类名称与类target
         */
        static Builder newBuilder(TypeElement enclosingElement) {
            TypeMirror typeMirror = enclosingElement.asType();
    
            TypeName targetType = TypeName.get(typeMirror);
            if (targetType instanceof ParameterizedTypeName) {
                targetType = ((ParameterizedTypeName) targetType).rawType;
            }
            ClassName bindingClassName = getBindingClassName(enclosingElement);
            return new Builder(targetType, bindingClassName, enclosingElement);
        }
    
        static final class Builder {
            private final TypeName targetTypeName;
            private final ClassName bindingClassName;
            private final TypeElement enclosingElement;
    
            //缓存ViewBinding实例,提升性能
            private final Map<ID, ViewBinding.Builder> viewIdMap = new LinkedHashMap<>();
    
            private Builder(
                    TypeName targetTypeName, ClassName bindingClassName, TypeElement enclosingElement) {
                this.targetTypeName = targetTypeName;
                this.bindingClassName = bindingClassName;
                this.enclosingElement = enclosingElement;
            }
    
    
            void addField(ID id, FieldViewBinding binding) {
                getOrCreateViewBindings(id).setFieldBinding(binding);
            }
    
            private ViewBinding.Builder getOrCreateViewBindings(ID id) {
                ViewBinding.Builder viewId = viewIdMap.get(id);
                if (viewId == null) {
                    viewId = new ViewBinding.Builder(id);
                    viewIdMap.put(id, viewId);
                }
                return viewId;
            }
    
            BindingSet build() {
                ImmutableList.Builder<ViewBinding> viewBindings = ImmutableList.builder();
                for (ViewBinding.Builder builder : viewIdMap.values()) {
                    viewBindings.add(builder.build());
                }
                return new BindingSet(targetTypeName, bindingClassName, enclosingElement, viewBindings.build());
            }
        }
    }
    

    这个类完全仿照源码编写,只保留了Activity的赋值逻辑,先来看用到的四个参数的作用:

        -这个是控件的父类的类型名称,用于生成target的值
        private final TypeName targetTypeName; 
       -这个是生成的文件名称
        private final ClassName bindingClassName; 
       -这个是注解的父注解元素
        private final TypeElement enclosingElement; 
       -这个就是我们的字段信息的缓存集合了
        private final ImmutableList<ViewBinding> viewBindings; 
    

    我们得先获取到这4个参数的值,这是一个构建者模式,构建者的赋值逻辑在newBuilder方法中:

        /**
         * 创建一个Builder
         * @param enclosingElement 父类元素,也就是那个Activity
         * @return 这里生成了类名称与类target
         */
        static Builder newBuilder(TypeElement enclosingElement) {
            TypeMirror typeMirror = enclosingElement.asType();
    
            TypeName targetType = TypeName.get(typeMirror);
            if (targetType instanceof ParameterizedTypeName) {
                targetType = ((ParameterizedTypeName) targetType).rawType;
            }
            ClassName bindingClassName = getBindingClassName(enclosingElement);
            return new Builder(targetType, bindingClassName, enclosingElement);
        }
    

    看一下getBindingClassName方法是如何获取到名称的:

        /**
         * 生成代码生成的类的类名
         * @return Name  规则 ActivityName__ViewBinding
         */
        static ClassName getBindingClassName(TypeElement typeElement) {
            String packageName = getPackage(typeElement).getQualifiedName().toString();
            String className = typeElement.getQualifiedName().toString().substring(
                    packageName.length() + 1).replace('.', '$');
            return ClassName.get(packageName, className + "_ViewBinding");
        }
    

    可以看到是通过父元素的getQualifiedName方法获取到标准格式的类名后截取的,随后手动添加了_ViewBinding后缀。
    我们还有一个viewBindings没有赋值,这个值需要从注解器里去拿到,这个随后再说,下面我们看代码生成逻辑,入口是brewJava方法:

        JavaFile brewJava() {
            TypeSpec bindingConfiguration = createType();
            return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
                    .addFileComment("Generated code from Butter Knife. Do not modify!")
                    .build();
        }
    

    可以看到通过createType获取到TypeSpec就直接生成JavaFile了,看一下createType方法做了什么:

        private TypeSpec createType() {
            //第一步 先创建类
            TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
                    .addModifiers(Modifier.PUBLIC)
                    .addOriginatingElement(enclosingElement); //设置注解处理器的源元素
            //添加解绑接口
            result.addSuperinterface(ClassName.get("com.jay.bindviewlib", "Unbinder"));
            //添加activity字段target
            result.addField(targetTypeName, "target");
            //添加构造方法
            result.addMethod(createBindingConstructorForActivity());
            //添加找id的方法
            result.addMethod(createBindingConstructor());
            //添加解绑的方法
            result.addMethod(createBindingUnbindMethod());
            return result.build();
        }
    

    通过TypeSpec.Builder依次添加各个部分的代码,逻辑还是比较清晰的,需要注意的是,添加Unbinder类传入包名的时候要填写正确的路径哦,不要直接把我的包名复制进去了。看不太懂的结合生成的代码比较着看,相信很容易就能看懂,这里与源码是有差别的,源码中是使用的Utils来寻找id,我这里为了方便直接生成了findviewbyid的代码,注意区别!!
    代码生成的逻辑写好了,接下来就到了我们的注解处理器上了,我们的BindingSet需要从处理器中获取到字段信息和控件的父元素信息才能创建代码对吧,接下来请看:
    我们新建一个类名叫BindViewProcessor

    @AutoService(Processor.class)
    public class BindViewProcessor extends AbstractProcessor {
    
        private Filer mFiler;
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
            //我们可以从这里获取一些工具类
            mFiler = processingEnvironment.getFiler();
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            //缓存BindingSet并给BindingSet赋值
            Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(roundEnvironment);
            //第二步,循环获取BindingSet并执行brewJava开始绘制代码
            for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
                TypeElement typeElement = entry.getKey();
                BindingSet binding = entry.getValue();
                JavaFile javaFile = binding.brewJava();
                try {
                    javaFile.writeTo(mFiler);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return false;
        }
    
    
        /**
         * 给BindingSet赋值并生成一个map
         * @param env 当前元素环境
         * @return 元素集合
         */
        private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
            Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
            Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
    
            //这里循环生成了BindingSet.Builder并将值放入了builderMap中
            Set<? extends Element> envs = env.getElementsAnnotatedWith(BindView.class);
            for (Element element : envs) {
                try {
                    parseBindView(element, builderMap);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            //从builderMap中取出值并生成BindingSet放入bindingMap中,源码是用的while,并有处理父类的super逻辑,这里直接用for
            Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();
            for (Map.Entry<TypeElement, BindingSet.Builder> entry:builderMap.entrySet()) {
                TypeElement type = entry.getKey();
                BindingSet.Builder builder = entry.getValue();
                bindingMap.put(type, builder.build());
            }
            return bindingMap;
        }
    
        /**
         * 为BindingSet赋值,从Element元素中获取Activity与控件信息,并保存到BindingSet中
         */
        private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap) {
            //获取父类的Element
            TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
    
            TypeMirror elementType = element.asType();
            if (elementType.getKind() == TypeKind.TYPEVAR) {
                TypeVariable typeVariable = (TypeVariable) elementType;
                elementType = typeVariable.getUpperBound();
            }
            Name qualifiedName = enclosingElement.getQualifiedName();
            Name simpleName = element.getSimpleName();
    
            int id = element.getAnnotation(BindView.class).value();
            BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    
            String name = simpleName.toString();
            TypeName type = TypeName.get(elementType);
            builder.addField(new ID(id), new FieldViewBinding(name, type));
        }
    
        /**
         * 创建BindingSet 并且将BindingSet缓存到builderMap中
         */
        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;
        }
    
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            Set<String> types = new LinkedHashSet<>();
            types.add(BindView.class.getCanonicalName()); //将我们自定义的注解添加进去
            return types;
        }
    
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }
    
    }
    

    其他3个方法基本都是样板代码,着重看process方法,首先是通过findAndParseTargets方法获取到BindingSet的缓存,BindingSet的赋值逻辑在parseBindView方法中:

        /**
         * 为BindingSet赋值,从Element元素中获取Activity与控件信息,并保存到BindingSet中
         */
        private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap) {
            //获取父类的Element
            TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
    
            TypeMirror elementType = element.asType();
            if (elementType.getKind() == TypeKind.TYPEVAR) {
                TypeVariable typeVariable = (TypeVariable) elementType;
                elementType = typeVariable.getUpperBound();
            }
            Name qualifiedName = enclosingElement.getQualifiedName();
            Name simpleName = element.getSimpleName();
    
            int id = element.getAnnotation(BindView.class).value();
            BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    
            String name = simpleName.toString();
            TypeName type = TypeName.get(elementType);
            builder.addField(new ID(id), new FieldViewBinding(name, type));
        }
    

    我们通过element.getEnclosingElement();方法就能获取到控件的父元素,这是BindingSet需要的值得其中之一,随后通过element.getAnnotation(BindView.class).value();获取到id并将它保存到ID类中,随后通过getSimpleName获取到控件的名称,也就是我们生成的代码的那个textview1名称,控件类型,也就是我们的那个TextView,可以通过element.asType()先获取到控件的信息类TypeMirror,随后通过TypeName.get方法获取到TypeName的实例,知道了TypeName,我们就相当于在代码中持有了这个类的实例,我们就能直接把它作为参数传入到JavaPoet方法构建中去了,最后我们通过builder.addField(new ID(id), new FieldViewBinding(name, type));方法传入IDFieldViewBinding类,保存到了viewBindings中,需要的值也就都赋值完毕了。

    既然值都获取到了,我们回到process方法,我们已经获取到了所有用BindView标记的控件的一个集合,接下来当然是循环调用brewJava方法构建代码啦:

        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            //缓存BindingSet并给BindingSet赋值
            Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(roundEnvironment);
            //第二步,循环获取BindingSet并执行brewJava开始绘制代码
            for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
                TypeElement typeElement = entry.getKey();
                BindingSet binding = entry.getValue();
                JavaFile javaFile = binding.brewJava();
                try {
                    javaFile.writeTo(mFiler);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return false;
        }
    

    核心代码已经写好了,接下来就剩下调用方法啦,我们在bindviewlib中新建一个类BindViewHelper,我们可以直接将ButterKnife类的代码搬过来,偷一波懒,最后,我们在申明一个解绑的接口:

    public interface Unbinder {
        @UiThread
        void unbind();
    
        Unbinder EMPTY = () -> { };
    }
    

    整个代码就写完了,接下来就在Activity中实验一下吧,我们在app的build.gradle中引入这两个包:

        implementation project(':bindviewlib')
        annotationProcessor project(':bindview-compiler')
    

    在代码中使用:

    /**
     * 自动生成的代码位于build>generated>source>apt目录下
     */
    public class MainActivity extends AppCompatActivity {
        @BindView(R.id.tv1)
        public TextView textView1;
        @BindView(R.id.tv2)
        public TextView textView2;
    
        private Unbinder unbinder;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            unbinder = BindViewHelper.bind(this);
            //试着运行一下吧
            textView1.setText("这是一个赋值测试");
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            unbinder.unbind();
        }
    }
    

    运行一下,我们在build>generated>source>apt目录下发现成功生成了文件MainActivity_ViewBinding

    // Generated code from Butter Knife. Do not modify!
    package com.jay.bindview;
    
    import android.view.View;
    import android.widget.TextView;
    import com.jay.bindviewlib.Unbinder;
    import java.lang.IllegalStateException;
    import java.lang.Override;
    
    public class MainActivity_ViewBinding implements Unbinder {
      MainActivity target;
    
      public MainActivity_ViewBinding(MainActivity target) {
        this(target, target.getWindow().getDecorView());
      }
    
      public MainActivity_ViewBinding(MainActivity target, View source) {
        this.target = target;
    
        target.textView1 = (TextView) source.findViewById(2131165312);
        target.textView2 = (TextView) source.findViewById(2131165313);
      }
    
      @Override
      public void unbind() {
        MainActivity target = this.target;
        if (target == null) throw new IllegalStateException("Bindings already cleared.");
        this.target = null;
    
        target.textView1 = null;
        target.textView2 = null;
      }
    }
    
    

    大功告成!!

    结尾

    先给个demo地址,方便读者查阅代码:https://github.com/Jay-huangjie/BindView

    希望读者能参照我的代码重新写一遍,代码并不复杂,相信对注解会有新的理解,然后你再去看ButterKnife的源码,会发现出奇的熟悉,一下就看懂了,有任何的问题欢迎留言,关注我,不迷路

    相关文章

      网友评论

        本文标题:安卓自定义注解实战之从零仿写ButterKnife源码的Bind

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