安卓自定義註解實戰之從零仿寫ButterKnife源碼的BindView功能

從這次實戰我能學會什麼

實戰要實現的功能
完全仿寫ButterKnife的bindView註解原理,實現在Activity中控件id賦值功能

實戰看點
這次實戰是從源碼出發,仿照ButterKnife的源碼實現其中的BindView的功能,實際上就是把ButterKnife的BindView的邏輯剝離出來單獨實現,代碼量少了許多但與源碼差別不大,達到了練習註解的目的。所以我們學會了這個,你會發現,看ButterKnife的源碼突然變得簡單了

實戰基礎
瞭解並能運用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

項目結構如圖所示:



我們先在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的源碼,會發現出奇的熟悉,一下就看懂了,有任何的問題歡迎留言,關注我,不迷路

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章