介绍
apt即Annotation Processing Tool,通过在编译期间扫描相关注解最后生成.java文件的一种注解处理工具。
目的
通过自动生成.java文件来简化大量模板代码的书写,提高工作效率。
源码浅析
butterknife Bind Android views and callbacks to fields and methods.
下面就浅析下butterknife是如何实现绑定的
1.首先看下项目结构,我们主要关注3个模块
![](https://img.haomeiwen.com/i1480849/fc27ca9ea1381628.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层为例
![](https://img.haomeiwen.com/i1480849/a90df849d111da26.png)
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);
}
}
可以发现类似这部分的代码省掉了
![](https://img.haomeiwen.com/i1480849/d404337fe964250a.png)
最后做个小结吧,合理的利用apt可以省掉大量的模板代码,大大的提高开发效率,而掌握apt编程的核心是熟悉javapoet库的api。
网友评论