美文网首页
手写ButterKnife

手写ButterKnife

作者: Android_Jian | 来源:发表于2019-12-24 18:56 被阅读0次

了解了ButterKnife的原理后,今天我就带领大家手写一个简易的ButterKnife。因为ButterKnife使用到了编译时注解+反射,所以在手写项目之前,小伙伴们需要先熟悉下注解(Annotation)、反射和AbstractProcessor相关的知识。

话不多说,我们新建一个项目,命名为ButterknifeViewBind。这里先梳理下我们的逻辑,首先我们需要新建一个annotation库,专门用来存放我们的注解代码,其次还需要创建一个compiler库,用来放置编译的相关逻辑,最后我们还需要新建一个util工具库,提供注册的入口,供我们的业务使用。整个项目的目录结构如下: 工程结构

首先看下butterknife-annotations库,很简单,我们只声明了BindView这个注解类,相关代码如下:

package com.demo.butterknife_annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

好了,注解有了,现在我们需要做的就是针对我们特定的注解进行处理,这个时候我们的ButterKnifeProcessor就登场了:

package com.demo.butterknife_compiler;

import com.demo.butterknife_annotations.BindView;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;


@AutoService(Processor.class)
public class ButterKnifeProcessor extends AbstractProcessor {

    private static final ClassName UI_THREAD =
            ClassName.get("androidx.annotation", "UiThread");
    Map<TypeElement, List<FieldBinding>> map = new HashMap<>();

    private Types mTypeUtils;
    private Elements mElementUtils;
    private Filer mFiler;
    private Messager mMessager;

    @Override
    public synchronized void init(ProcessingEnvironment env) {
        super.init(env);

        //初始化我们需要的基础工具
        mTypeUtils = env.getTypeUtils();
        mElementUtils = env.getElementUtils();
        mFiler = env.getFiler();
        mMessager = env.getMessager();
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        //支持的Java版本
        return SourceVersion.latestSupported();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        //支持的注解
        Set<String> annotations = new LinkedHashSet<>();
        annotations.add(BindView.class.getCanonicalName());
        return annotations;
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {

        //解析特定注解,生成Java文件
        for (Element annotatedElement : roundEnvironment.getElementsAnnotatedWith(BindView.class)) {
            if (annotatedElement.getKind() != ElementKind.FIELD) {
                error(annotatedElement, "Only field can be annotated with @%s",
                        BindView.class.getSimpleName());
                return true;
            }

            analysisAnnotated(annotatedElement);
        }

        for (Map.Entry<TypeElement, List<FieldBinding>> item :
                map.entrySet()) {

            TypeElement enclosingElement = item.getKey();
            String packageName = ((PackageElement) enclosingElement.getEnclosingElement()).getQualifiedName().toString();
            String className = enclosingElement.getSimpleName().toString();
            ClassName originClassName = ClassName.get(packageName, className);
            ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");

            //System.out.println("packageName -------------------" + packageName);
            //System.out.println("className -------------------" + className);
            //System.out.println("originClassName -------------------" + originClassName.toString());
            //System.out.println("bindingClassName -------------------" + bindingClassName.toString());

            //构造方法中拼接代码
            CodeBlock.Builder codeBuilder = CodeBlock.builder();
            for (FieldBinding fieldBinding : item.getValue()) {
                codeBuilder.addStatement("target.$L=($T)target.findViewById($L)", fieldBinding.getFieldName(),
                        fieldBinding.getType(), fieldBinding.getFieldId());
            }

            //创建构造方法
            MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
                    .addModifiers(Modifier.PUBLIC)
                    .addAnnotation(UI_THREAD)
                    .addParameter(originClassName, "target")
                    .addCode(codeBuilder.build());

            //创建类
            TypeSpec typeSpec = TypeSpec.classBuilder(bindingClassName)
                    .addModifiers(Modifier.PUBLIC)
                    .addMethod(constructor.build())
                    .build();

            try {
                //生成java文件
                JavaFile.builder(packageName, typeSpec).build().writeTo(mFiler);
            } catch (IOException e) {
                e.printStackTrace();
            }


        }
        return false;
    }

    private void error(Element e, String msg, Object... args) {
        mMessager.printMessage(
                Diagnostic.Kind.ERROR,
                String.format(msg, args),
                e);
    }

    private void analysisAnnotated(Element element) {
        TypeElement activityElement = (TypeElement) element.getEnclosingElement();
        List<FieldBinding> activityBinding = map.get(activityElement);
        if (activityBinding == null) {
            activityBinding = new ArrayList<>();
            map.put(activityElement, activityBinding);
        }

        // Verify that the target type extends from View.
        TypeMirror elementType = element.asType();
        TypeName type = TypeName.get(elementType);
        int fieldId = element.getAnnotation(BindView.class).value();
        String fieldName = element.getSimpleName().toString();

        FieldBinding fieldBinding = new FieldBinding(fieldId, fieldName, type);
        activityBinding.add(fieldBinding);
    }
}

可以看到ButterKnifeProcessor使用了@AutoService注解,这个是用来做什么的呢?AutoService是Google开发的注解处理器,用来生成 META-INF/services/javax.annotation.processing.Processor 文件的,简化了我们创建注解处理器的流程。

AutoService的使用方式很简单,第一步,在我们butterknife_compiler库的buid.gradle文件中添加相关依赖:

implementation 'com.google.auto.service:auto-service:1.0-rc3'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc3'  

接着在我们创建的注解处理器上添加@AutoService(Processor.class)注解就OK了,有没有很简单哈哈。

我们接着看下ButterKnifeProcessor的process方法,该方法在项目中的作用就是解析@ BindView注解,生成“ _ViewBinding.java”文件。在生成“ _ViewBinding.java”文件的过程中,使用到了JavaPoet开源库,what? 这又是什么?其实JavaPoet 就是用来辅助我们生成 Java 代码的一个 Java Library。同样我们需要在buid.gradle文件中添加依赖:

implementation 'com.squareup:javapoet:1.8.0'

对JavaPoet 的API操作不太了解的童鞋可以查下相关博客,笔者这里也是一边查博客一边写的,一把辛酸泪啊~

好了到现在为止ButterKnifeProcessor也被我们搞定了,接下来就只剩下util工具库,提供注册的入口,这个就easy多了,只涉及到反射操作,我们首先新建butterknifeinject library,接着创建ButterknifeInject类,该类只有一个bind方法,代码如下:

package com.demo.butterknifeinject;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public final class ButterknifeInject {

    public static void bind(Object target) {

        try {
            String clsName = target.getClass().getName();
            Class<?> bindingClass = Class.forName(clsName + "_ViewBinding");
            try {
                Constructor constructor = bindingClass.getConstructor(target.getClass());
                try {
                    System.out.println("constructor-----------newInstance- before");
                    constructor.newInstance(target);
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

大功告成,最后我们测试一下。在app的build.gradle文件中添加我们的library依赖:

annotationProcessor project(':butterknife-compiler')
implementation project(':butterknife-annotations')
implementation project(':butterknifeInject')

ok,在MainActivity中我们现在就可以这么写啦:

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.tv_test)
    TextView mTvTest;

    @BindView(R.id.btn_test)
    Button mBtnTest;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ButterknifeInject.bind(this);

        initData();
    }

    private void initData(){
        mBtnTest.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                mTvTest.setText("哈哈哈哈哈嗝");
            }
        });
    }
}
最后我们run一把,一切正常,不出意外的话我们的“ _ViewBinding.java”文件已经生成了,我们看下: 1577184824451.png

至此为止,一个简易的ButterKnife就生成了。

相关文章

网友评论

      本文标题:手写ButterKnife

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