手记

Android butterknife源码浅析及APT实际应用

介绍

apt即Annotation Processing Tool,通过在编译期间扫描相关注解最后生成.java文件的一种注解处理工具。

目的

通过自动生成.java文件来简化大量模板代码的书写,提高工作效率。

源码浅析

butterknife Bind Android views and callbacks to fields and methods.
下面就浅析下butterknife是如何实现绑定的
1.首先看下项目结构,我们主要关注3个模块

image.png


  • annotation模块用于定义基础注解

  • apt模块用于处理annotation定义的基础注解,也是核心模块

  • app模块可以运行的demo例子

2.相关模块浅析

  • 注解如何定义
    以经常使用的BindView为例看一下


    image.png


    注解可以理解成一个TAG,更多注解相关的基础知识这篇文章不做细讲


  • apt重点来了

    第一步添加依赖
    implementation project(':butterknife-annotations') 添加注解模块的依赖
    api deps.javapoet 这个javapoet是反向生成代码的利器,后面在细讲
    compileOnly deps.auto.service auto-service是google帮助我们在编写apt代码时自动生成相关必要目录和文件的辅助工具

    第二步看下核心处理类 ButterKnifeProcessor

@AutoService(Processor.class)//这个注解帮助我们做了一些工作,后面再说/**
*  ButterKnifeProcessor 继承AbstractProcessor ,我们看关键几个重写的方法
*/public final class ButterKnifeProcessor extends AbstractProcessor {    @Override
    public Set<String> getSupportedAnnotationTypes() {//设置哪些注解可以支持处理
        Set<String> types = new LinkedHashSet<>();        for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
            types.add(annotation.getCanonicalName());
        }        return types;
    }    private Set<Class<? extends Annotation>> getSupportedAnnotations() {//具体的注解添加逻辑
        Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
        annotations.add(BindAnim.class);
        annotations.add(BindArray.class);
        annotations.add(BindBitmap.class);
        annotations.add(BindBool.class);
        annotations.add(BindColor.class);
        annotations.add(BindDimen.class);
        annotations.add(BindDrawable.class);
        annotations.add(BindFloat.class);
        annotations.add(BindFont.class);
        annotations.add(BindInt.class);
        annotations.add(BindString.class);
        annotations.add(BindView.class);
        annotations.add(BindViews.class);
        annotations.addAll(LISTENERS);        return annotations;
    }    //设置生成的.java源码支持的jdk版本
    @Override
    public SourceVersion getSupportedSourceVersion() {        return SourceVersion.latestSupported();
    }    //核心处理方法,在这个方法里面生成我们需要的.java文件
    @Override
    public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
        Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);//以BindView注解为例主要功能是收集id,变量名,变量类型,以及一些check处理

        for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
            TypeElement typeElement = entry.getKey();
            BindingSet binding = entry.getValue();//BindingSet是butterknife的核心类这里面处理各种各样模板代码的生成

            JavaFile javaFile = binding.brewJava(sdk, debuggable, useAndroidX);//组装JavaFile文件
            try {
                javaFile.writeTo(filer);//写出.java文件到build的apt目录下
            } catch (IOException e) {
                error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
            }
        }        return false;
    }

}

上面的代码是AbstractProcessor 的一些关键的回调,下面我们看下里面用到的核心方法
JavaFile javaFile = binding.brewJava(sdk, debuggable, useAndroidX);//组装JavaFile文件

    JavaFile brewJava(int sdk, boolean debuggable, boolean useAndroidX) {       //TypeSpec是什么?通俗的讲是类,接口的描述
        TypeSpec bindingConfiguration = createType(sdk, debuggable, useAndroidX);        return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
                .addFileComment("Generated code from Butter Knife. Do not modify!")
                .build();
    }    /**
     * 最核心的方法,也是javapoet的使用  https://github.com/square/javapoet
     * 为了方便大家理解我把自动生成的代码贴到对应位置,还是以BindView注解为例
     * @param sdk
     * @param debuggable
     * @param useAndroidX
     * @return
     */
    private TypeSpec createType(int sdk, boolean debuggable, boolean useAndroidX) {
        TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
                .addModifiers(PUBLIC);//public class SimpleActivity_ViewBinding, 其中addModifiers是增加修饰符
        if (isFinal) {
            result.addModifiers(FINAL);//类不可被继承加final修饰符
        }        if (parentBinding != null) {
            result.superclass(parentBinding.bindingClassName);//如果有父类添加继承关系
        } else {
            result.addSuperinterface(UNBINDER);//没有父类添加实现  public class SimpleActivity_ViewBinding implements Unbinder
        }        if (hasTargetField()) {
            result.addField(targetTypeName, "target", PRIVATE);//有target属性   private SimpleActivity target;
        }        if (isView) {
            result.addMethod(createBindingConstructorForView(useAndroidX));
        } else if (isActivity) {//通过getEnclosingElement()方法拿到包装类,如果是Activity那么执行下面的方法
            result.addMethod(createBindingConstructorForActivity(useAndroidX));//添加构造方法
            /***
             *     @UiThread
             *     public SimpleActivity_ViewBinding(SimpleActivity target) {
             *      this(target, target.getWindow().getDecorView());
             *  }
             */
        } else if (isDialog) {
            result.addMethod(createBindingConstructorForDialog(useAndroidX));
        }        if (!constructorNeedsView()) {            // Add a delegating constructor with a target type + view signature for reflective use.
            result.addMethod(createBindingViewDelegateConstructor(useAndroidX)); //deprecated 不看了
        }
        result.addMethod(createBindingConstructor(sdk, debuggable, useAndroidX));//这一句也是添加构造方法,在这个构造函数里面有具体的绑定生成
        /**
         *    @UiThread
        public SimpleActivity_ViewBinding(final SimpleActivity target, View source) {
        this.target = target;

        View view;
        target.title = Utils.findRequiredViewAsType(source, R.id.title, "field 'title'", TextView.class);
        target.subtitle = Utils.findRequiredViewAsType(source, R.id.subtitle, "field 'subtitle'", TextView.class);
        view = Utils.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', and method 'sayGetOffMe'");
        target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class);
        view7f06000a = view;
        view.setOnClickListener(new DebouncingOnClickListener() {        @Override
        public void doClick(View p0) {
        target.sayHello();
        }
        });
        view.setOnLongClickListener(new View.OnLongClickListener() {        @Override
        public boolean onLongClick(View p0) {
        return target.sayGetOffMe();
        }
        });
        view = Utils.findRequiredView(source, R.id.list_of_things, "field 'listOfThings' and method 'onItemClick'");
        target.listOfThings = Utils.castView(view, R.id.list_of_things, "field 'listOfThings'", ListView.class);
        view7f060012 = view;
        ((AdapterView<?>) view).setOnItemClickListener(new AdapterView.OnItemClickListener() {        @Override
        public void onItemClick(AdapterView<?> p0, View p1, int p2, long p3) {
        target.onItemClick(p2);
        }
        });
        target.footer = Utils.findRequiredViewAsType(source, R.id.footer, "field 'footer'", TextView.class);
        target.headerViews = Utils.listOf(
        Utils.findRequiredView(source, R.id.title, "field 'headerViews'"),
        Utils.findRequiredView(source, R.id.subtitle, "field 'headerViews'"),
        Utils.findRequiredView(source, R.id.hello, "field 'headerViews'"));

        Context context = source.getContext();
        Resources res = context.getResources();
        target.butterKnife = res.getString(R.string.app_name);
        target.fieldMethod = res.getString(R.string.field_method);
        target.byJakeWharton = res.getString(R.string.by_jake_wharton);
        target.sayHello = res.getString(R.string.say_hello);
        }
         */
        if (hasViewBindings() || parentBinding == null) {
            result.addMethod(createBindingUnbindMethod(result, useAndroidX));//添加unbind方法这个方法都不陌生,ondestory()方法里面经常调用
        }        return result.build();//最后build()方法在内部 new TypeSpec,这样就完成了TypeSpec的组装。
    }

javapoet的详细使用<-这个是apt的基础也是关键

补充一下 @AutoService(Processor.class)的作用
在使用注解处理器需要先声明以下步骤:
1、需要在 apt 库的 main 目录下新建 resources 资源文件夹;
2、在 resources文件夹下建立 META-INF/services 目录文件夹;
3、在 META-INF/services 目录文件夹下创建 javax.annotation.processing.Processor 文件;
4、在 javax.annotation.processing.Processor 文件写入注解处理器的全称,包括包路径;)
每次这样写太麻烦。这就是用引入auto-service的原因。
通过@AutoService可以自动生成上面的步骤,AutoService注解处理器是Google开发的,用来生成 META-INF/services/javax.annotation.processing.Processor 文件的

  • 使用apt

    第一步添加依赖
    implementation project(':butterknife-annotations')
    annotationProcessor project(':butterknife-compiler') 有没有经常看到这个gradle api?最早的时候是这样的apt project(':butterknife-compiler')现在被废弃了

    第二步代码调用,大家都懂不说了

public class SimpleActivity extends Activity { private Unbinder mUnbinder;  @BindView(R.id.title) TextView title;  @Override protected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_activity);
    mUnbinder= ButterKnife.bind(this);
    title.setText(butterKnife);
  }  @Override
  protected void onDestroy() {    super.onDestroy();    if (mUnbinder != null) {
        mUnbinder.unbind();
      }
    }
}

apt整个流程:定义注解->注解处理器结合javapoet去反向生成.java文件->调用这些.java文件相关代码绑定关系

实际应用

好了最后咱们实际应用下apt,看看项目中可以怎么使用,以自动生成Model层为例


示例项目结构

1.首先新建一个javalib 定义需要的注解

/**
 * 自动生成ApiFactory工具类的注解
 */@Retention(RetentionPolicy.SOURCE)@Target(ElementType.TYPE)//作用在类上面public @interface ApiFactory {    String path();//获取相关参数}

2.再建立一个javalib用来处理第一步定义的注解,apt模块

//添加依赖dependencies {
    compile 'com.google.auto.service:auto-service:1.0-rc2'//自动生成META-INF/services/javax.annotation.processing.Processor 文件
    compile 'com.squareup:javapoet:1.4.0'//反向生成代码的api库
    implementation project(':library-jcannotation')//注解定义module}
package com.jcgroup.jcapt;import com.google.auto.service.AutoService;import com.jcgroup.jcapt.processor.ApiFactoryProcessor;import com.jcgroup.jcapt.processor.InstanceProcessor;import java.util.Set;import javax.annotation.processing.AbstractProcessor;import javax.annotation.processing.Filer;import javax.annotation.processing.Messager;import javax.annotation.processing.Processor;import javax.annotation.processing.RoundEnvironment;import javax.annotation.processing.SupportedAnnotationTypes;import javax.annotation.processing.SupportedSourceVersion;import javax.lang.model.SourceVersion;import javax.lang.model.element.TypeElement;import javax.lang.model.util.Elements;@AutoService(Processor.class)//自动生成相关目录文件,简化开发步骤@SupportedSourceVersion(SourceVersion.RELEASE_8)//java版本支持@SupportedAnnotationTypes({//标注注解处理器支持的注解类型
        "com.jcgroup.jcannotation.apt.ApiFactory"})public class AnnotationProcessor extends AbstractProcessor {    public Filer mFiler; //文件相关的辅助类
    public Elements mElements; //元素相关的辅助类
    public Messager mMessager; //日志相关的辅助类

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        mFiler = processingEnv.getFiler();
        mElements = processingEnv.getElementUtils();
        mMessager = processingEnv.getMessager();        new ApiFactoryProcessor().process(roundEnv, this);        return true;
    }
}
package com.jcgroup.jcapt.processor;import com.jcgroup.jcannotation.apt.ApiFactory;import com.jcgroup.jcapt.AnnotationProcessor;import com.jcgroup.jcapt.inter.IProcessor;import com.jcgroup.jcapt.util.Utils;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 javax.annotation.processing.FilerException;import javax.annotation.processing.RoundEnvironment;import javax.lang.model.element.Element;import javax.lang.model.element.ExecutableElement;import javax.lang.model.element.TypeElement;import javax.lang.model.element.VariableElement;import javax.lang.model.util.ElementFilter;import javax.tools.Diagnostic;import static com.squareup.javapoet.TypeSpec.classBuilder;import static javax.lang.model.element.Modifier.FINAL;import static javax.lang.model.element.Modifier.PUBLIC;import static javax.lang.model.element.Modifier.STATIC;/**
 * Created by luohai on 2018.6.5.
 */public class ApiFactoryProcessor implements IProcessor {    @Override
    public void process(RoundEnvironment roundEnv, AnnotationProcessor mAbstractProcessor) {
        String CLASS_NAME = "ApiFactory";
        String DATA_ARR_CLASS = "DataArr";
        String LIST_CLASS = "ArrayList";
        TypeSpec.Builder tb = classBuilder(CLASS_NAME).addModifiers(PUBLIC, FINAL).addJavadoc("@ API工厂 此类由apt自动生成");        try {            for (TypeElement element : ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(ApiFactory.class))) {
                mAbstractProcessor.mMessager.printMessage(Diagnostic.Kind.NOTE, "正在处理: " + element.toString());
                ApiFactory apiFactory=element.getAnnotation(ApiFactory.class);
                String path=apiFactory.path();                for (Element e : element.getEnclosedElements()) {
                    ExecutableElement executableElement = (ExecutableElement) e;
                    MethodSpec.Builder methodBuilder =
                            MethodSpec.methodBuilder(e.getSimpleName().toString())
                                    .addJavadoc("@此方法由apt自动生成")
                                    .addModifiers(PUBLIC, STATIC);                    if (TypeName.get(executableElement.getReturnType()).toString().contains(DATA_ARR_CLASS) || TypeName.get(executableElement.getReturnType()).toString().contains(LIST_CLASS)) {//返回列表数据
                        methodBuilder.returns(ClassName.get("io.reactivex", "Flowable"));
                    } else {
                        methodBuilder.returns(TypeName.get(executableElement.getReturnType()));
                    }
                    ClassName apiUtil = ClassName.get(path+".util", "ApiHelper");
                    CodeBlock.Builder blockBuilder = CodeBlock.builder();
                    String paramsString = "";                    for (VariableElement ep : executableElement.getParameters()) {
                        methodBuilder.addParameter(TypeName.get(ep.asType()), ep.getSimpleName().toString());                        if (ep.asType().toString().equals(ClassName.get(path+".base.entity", "RequestBean").toString())) {
                            blockBuilder.add("$L.getBaseInfo($L,isEncrypt)", apiUtil, ep.getSimpleName().toString());
                            paramsString += blockBuilder.build().toString() + ",";
                        } else {
                            paramsString += ep.getSimpleName().toString() + ",";
                        }
                    }
                    methodBuilder.addParameter(TypeName.BOOLEAN, "isEncrypt");
                    methodBuilder.addParameter(TypeName.BOOLEAN, "isDecode");
                    methodBuilder.addStatement(                            "return  new $L().addResultCodeLogic($T.getInstance()" +                                    ".service.$L($L)" +                                    ".compose($T.io_main()),isDecode)", apiUtil
                            , ClassName.get(path+".api", "Api")
                            , e.getSimpleName().toString()
                            , paramsString.equals("") ? paramsString : paramsString.substring(0, paramsString.length() - 1)
                            , ClassName.get("com.jctv.tvhome.util.helper", "RxSchedulers"));
                    tb.addMethod(methodBuilder.build());
                }
            }
            JavaFile javaFile = JavaFile.builder(Utils.PackageName, tb.build()).build();// 生成源代码
            javaFile.writeTo(mAbstractProcessor.mFiler);// 在 app module/build/generated/source/apt 生成一份源代码
        } catch (FilerException e) {
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3.使用注解处理器

    implementation project(':library-jcannotation')//注解定义模块
    annotationProcessor project(':library-jcapt')//apt模块

调用自动生成的Model层,这里没有遵循MVP架构,演示直接在View层里面操作了Model层

public class SplashActivity extends AppCompatActivity {    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);
        ApiFactory.method1(new RequestBean(),false,false).subscribe(testRequestResponseBean -> {},throwable -> {});
    }
}

看下这个自动生成的Model

/**
 * @ API工厂 此类由apt自动生成 */public final class ApiFactory {  /**
   * @此方法由apt自动生成 */
  public static Flowable<ResponseBean<TestRequest>> method1(RequestBean bean, boolean isEncrypt, boolean isDecode) {    return  new com.jctv.tvhome.util.ApiHelper().addResultCodeLogic(Api.getInstance().service.method1(com.jctv.tvhome.util.ApiHelper.getBaseInfo(bean,isEncrypt)).compose(RxSchedulers.io_main()),isDecode);
  }
}



可以发现类似这部分的代码省掉了

工程里面的Model层.png



作者:西瓜太郎123
链接:https://www.jianshu.com/p/7474f4107cc3


0人推荐
随时随地看视频
慕课网APP

相关阅读

Dagger2 入门笔记