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

从这次实战我能学会什么

实战要实现的功能
完全仿写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的源码,会发现出奇的熟悉,一下就看懂了,有任何的问题欢迎留言,关注我,不迷路

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,158评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,600评论 1 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,785评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,655评论 0 220
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,075评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,002评论 1 225
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,146评论 2 318
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,918评论 0 211
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,671评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,838评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,318评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,636评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,343评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,187评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,982评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,126评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,934评论 2 279

推荐阅读更多精彩内容