安卓自定義注解實戰(zhàn)之從零仿寫B(tài)utterKnife源碼的BindView功能

從這次實戰(zhàn)我能學(xué)會什么

實戰(zhàn)要實現(xiàn)的功能
完全仿寫B(tài)utterKnife的bindView注解原理鹤竭,實現(xiàn)在Activity中控件id賦值功能

實戰(zhàn)看點
這次實戰(zhàn)是從源碼出發(fā)猖闪,仿照ButterKnife的源碼實現(xiàn)其中的BindView的功能宏侍,實際上就是把ButterKnife的BindView的邏輯剝離出來單獨實現(xiàn)徽千,代碼量少了許多但與源碼差別不大爱榔,達到了練習(xí)注解的目的挟伙。所以我們學(xué)會了這個,你會發(fā)現(xiàn)荐开,看ButterKnife的源碼突然變得簡單了

oh

實戰(zhàn)基礎(chǔ)
了解并能運用AutoService,JavaPoet,AbstractProcessor
不熟悉的付翁,請先參考我之前的文章:安卓使用注解處理器自動生成代碼操作詳解(AutoService,JavaPoet,AbstractProcessor)

下面我們就來通過實戰(zhàn)探究ButterKnife的注解奧秘

ButterKnife原理的簡單介紹

既然要仿寫,必然要先對仿寫對象的實現(xiàn)原理要有一些了解
這是它的簡單使用:

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();
    }
}

運行后我們會發(fā)現(xiàn)生成了一個MainActivity_ViewBinding文件晃听,結(jié)合我上篇文章我們知道這是使用了注解處理器:

// 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;
  }
}

欸百侧,是不是執(zhí)行了MainActivity_ViewBinding的構(gòu)造方法就完成了控件賦值了,那么這個方法在哪執(zhí)行的呢杂伟,看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);這行代碼仍翰,這是通過反射獲取到類然后執(zhí)行這個類的構(gòu)造方法赫粥,這樣一分析,實現(xiàn)流程是不是一目了然了予借,先通過BindView注解獲取到控件的id,控件的類型和控件父類的名稱(用于生成特定的類文件名和控件賦值)越平,然后生成代碼,最后通過反射執(zhí)行生成的類的構(gòu)造方法灵迫,是不是就實現(xiàn)了我們想要的功能秦叛。
下面開始擼碼

BindView代碼實現(xiàn)

既然是仿,我們仿的像一點瀑粥,先新建一個項目挣跋,創(chuàng)建3個library:

  • bindview-annotation 注意是java library,用于存放注解
  • bindview-compiler 注意還是java library狞换,用于處理注解避咆,生成代碼
  • bindviewlib 安卓library,用于反射調(diào)用生成的代碼
    bindviewlib 和bindview-compiler都需要依賴bindview-annotation

項目結(jié)構(gòu)如圖所示:


project.jpg

我們先在bindview-annotation中創(chuàng)建一個BindView注解:

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

隨后,我們就按照源碼在bindview-compiler中新建4個類:

  • BindingSet 代碼統(tǒng)籌管理類修噪,處理代碼生成邏輯
  • 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類,仿照源碼用了構(gòu)建者模式黄琼,保存了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了樊销,看看是怎么來創(chuàng)建代碼的吧:

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;
    }

    /**
     * 從這個方法開始構(gòu)建代碼,這里只實現(xiàn)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() {
        //第一步 先創(chuàng)建類
        TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
                .addModifiers(Modifier.PUBLIC)
                .addOriginatingElement(enclosingElement); //設(shè)置注解處理器的源元素
        //添加解綁接口
        result.addSuperinterface(ClassName.get("com.jay.bindviewlib", "Unbinder"));
        //添加activity字段target
        result.addField(targetTypeName, "target");
        //添加構(gòu)造方法
        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");

    /**
     * 創(chuàng)建構(gòu)造方法,這個方法里包含找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");
        //這里循環(huán)創(chuàng)建控件賦值代碼
        for (ViewBinding binding : viewBindings) {
            addViewBinding(constructor, binding);
        }
        return constructor.build();
    }

    //創(chuàng)建一條賦值代碼
    //示例: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()); //添加強轉(zhuǎn)代碼
        builder.add("source.findViewById($L)", binding.getId().code); //找id
        result.addStatement("$L", builder.build()); //將代碼添加到方法中
    }


    /**
     * 創(chuàng)建解綁的方法
     *
     * @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  規(guī)則 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");
    }

    /**
     * 創(chuàng)建一個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的賦值邏輯剂府,先來看用到的四個參數(shù)的作用:

    -這個是控件的父類的類型名稱比然,用于生成target的值
    private final TypeName targetTypeName; 
   -這個是生成的文件名稱
    private final ClassName bindingClassName; 
   -這個是注解的父注解元素
    private final TypeElement enclosingElement; 
   -這個就是我們的字段信息的緩存集合了
    private final ImmutableList<ViewBinding> viewBindings; 

我們得先獲取到這4個參數(shù)的值,這是一個構(gòu)建者模式周循,構(gòu)建者的賦值邏輯在newBuilder方法中:

    /**
     * 創(chuàng)建一個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  規(guī)則 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() {
        //第一步 先創(chuàng)建類
        TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
                .addModifiers(Modifier.PUBLIC)
                .addOriginatingElement(enclosingElement); //設(shè)置注解處理器的源元素
        //添加解綁接口
        result.addSuperinterface(ClassName.get("com.jay.bindviewlib", "Unbinder"));
        //添加activity字段target
        result.addField(targetTypeName, "target");
        //添加構(gòu)造方法
        result.addMethod(createBindingConstructorForActivity());
        //添加找id的方法
        result.addMethod(createBindingConstructor());
        //添加解綁的方法
        result.addMethod(createBindingUnbindMethod());
        return result.build();
    }

通過TypeSpec.Builder依次添加各個部分的代碼临扮,邏輯還是比較清晰的论矾,需要注意的是,添加Unbinder類傳入包名的時候要填寫正確的路徑哦杆勇,不要直接把我的包名復(fù)制進去了贪壳。看不太懂的結(jié)合生成的代碼比較著看蚜退,相信很容易就能看懂闰靴,這里與源碼是有差別的,源碼中是使用的Utils來尋找id,我這里為了方便直接生成了findviewbyid的代碼钻注,注意區(qū)別!!
代碼生成的邏輯寫好了蚂且,接下來就到了我們的注解處理器上了,我們的BindingSet需要從處理器中獲取到字段信息和控件的父元素信息才能創(chuàng)建代碼對吧幅恋,接下來請看:
我們新建一個類名叫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);
        //第二步杏死,循環(huán)獲取BindingSet并執(zhí)行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 當前元素環(huán)境
     * @return 元素集合
     */
    private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
        Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
        Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();

        //這里循環(huán)生成了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));
    }

    /**
     * 創(chuàng)建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,我們就相當于在代碼中持有了這個類的實例苦始,我們就能直接把它作為參數(shù)傳入到JavaPoet方法構(gòu)建中去了寞钥,最后我們通過builder.addField(new ID(id), new FieldViewBinding(name, type));方法傳入IDFieldViewBinding類,保存到了viewBindings中,需要的值也就都賦值完畢了陌选。

既然值都獲取到了理郑,我們回到process方法,我們已經(jīng)獲取到了所有用BindView標記的控件的一個集合咨油,接下來當然是循環(huán)調(diào)用brewJava方法構(gòu)建代碼啦:

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //緩存BindingSet并給BindingSet賦值
        Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(roundEnvironment);
        //第二步您炉,循環(huán)獲取BindingSet并執(zhí)行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;
    }

核心代碼已經(jīng)寫好了,接下來就剩下調(diào)用方法啦役电,我們在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目錄下發(fā)現(xiàn)成功生成了文件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;
  }
}

大功告成!!

結(jié)尾

先給個demo地址,方便讀者查閱代碼:https://github.com/Jay-huangjie/BindView

希望讀者能參照我的代碼重新寫一遍驮瞧,代碼并不復(fù)雜氓扛,相信對注解會有新的理解,然后你再去看ButterKnife的源碼论笔,會發(fā)現(xiàn)出奇的熟悉采郎,一下就看懂了,有任何的問題歡迎留言狂魔,關(guān)注我蒜埋,不迷路

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市最楷,隨后出現(xiàn)的幾起案子整份,更是在濱河造成了極大的恐慌待错,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烈评,死亡現(xiàn)場離奇詭異火俄,居然都是意外死亡,警方通過查閱死者的電腦和手機讲冠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門瓜客,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人竿开,你說我怎么就攤上這事谱仪。” “怎么了否彩?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵芽卿,是天一觀的道長。 經(jīng)常有香客問我胳搞,道長卸例,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任肌毅,我火速辦了婚禮筷转,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘悬而。我一直安慰自己呜舒,他們只是感情好,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布笨奠。 她就那樣靜靜地躺著袭蝗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪般婆。 梳的紋絲不亂的頭發(fā)上到腥,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音蔚袍,去河邊找鬼乡范。 笑死,一個胖子當著我的面吹牛啤咽,可吹牛的內(nèi)容都是我干的晋辆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼宇整,長吁一口氣:“原來是場噩夢啊……” “哼瓶佳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鳞青,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤霸饲,失蹤者是張志新(化名)和其女友劉穎索赏,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贴彼,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡潜腻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了器仗。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片融涣。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖精钮,靈堂內(nèi)的尸體忽然破棺而出威鹿,到底是詐尸還是另有隱情,我是刑警寧澤轨香,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布忽你,位于F島的核電站,受9級特大地震影響臂容,放射性物質(zhì)發(fā)生泄漏科雳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一脓杉、第九天 我趴在偏房一處隱蔽的房頂上張望糟秘。 院中可真熱鬧,春花似錦球散、人聲如沸尿赚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凌净。三九已至,卻和暖如春屋讶,著一層夾襖步出監(jiān)牢的瞬間冰寻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工丑婿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留性雄,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓羹奉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親约计。 傳聞我的和親對象是個殘疾皇子诀拭,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容