介紹
apt即Annotation Processing Tool,通過在編譯期間掃描相關(guān)注解最后生成.java文件的一種注解處理工具。
目的
通過自動生成.java文件來簡化大量模板代碼的書寫,提高工作效率。
源碼淺析
butterknife Bind Android views and callbacks to fields and methods.
下面就淺析下butterknife是如何實(shí)現(xiàn)綁定的
1.首先看下項(xiàng)目結(jié)構(gòu),我們主要關(guān)注3個(gè)模塊

- annotation模塊用于定義基礎(chǔ)注解
- apt模塊用于處理annotation定義的基礎(chǔ)注解,也是核心模塊
- app模塊可以運(yùn)行的demo例子
2.相關(guān)模塊淺析
-
注解如何定義
以經(jīng)常使用的BindView為例看一下
image.png
注解可以理解成一個(gè)TAG,更多注解相關(guān)的基礎(chǔ)知識這篇文章不做細(xì)講
-
apt重點(diǎn)來了
第一步添加依賴
implementation project(':butterknife-annotations')添加注解模塊的依賴
api deps.javapoet這個(gè)javapoet是反向生成代碼的利器,后面在細(xì)講
compileOnly deps.auto.serviceauto-service是google幫助我們在編寫apt代碼時(shí)自動生成相關(guān)必要目錄和文件的輔助工具第二步看下核心處理類
ButterKnifeProcessor
@AutoService(Processor.class)//這個(gè)注解幫助我們做了一些工作,后面再說
/**
* ButterKnifeProcessor 繼承AbstractProcessor ,我們看關(guān)鍵幾個(gè)重寫的方法
*/
public final class ButterKnifeProcessor extends AbstractProcessor {
@Override
public Set<String> getSupportedAnnotationTypes() {//設(shè)置哪些注解可以支持處理
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;
}
//設(shè)置生成的.java源碼支持的jdk版本
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
//核心處理方法,在這個(gè)方法里面生成我們需要的.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 的一些關(guān)鍵的回調(diào),下面我們看下里面用到的核心方法
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
* 為了方便大家理解我把自動生成的代碼貼到對應(yīng)位置,還是以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);//如果有父類添加繼承關(guān)系
} else {
result.addSuperinterface(UNBINDER);//沒有父類添加實(shí)現(xiàn) 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那么執(zhí)行下面的方法
result.addMethod(createBindingConstructorForActivity(useAndroidX));//添加構(gòu)造方法
/***
* @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));//這一句也是添加構(gòu)造方法,在這個(gè)構(gòu)造函數(shù)里面有具體的綁定生成
/**
* @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方法這個(gè)方法都不陌生,ondestory()方法里面經(jīng)常調(diào)用
}
return result.build();//最后build()方法在內(nèi)部 new TypeSpec,這樣就完成了TypeSpec的組裝。
}
javapoet的詳細(xì)使用<-這個(gè)是apt的基礎(chǔ)也是關(guān)鍵
補(bǔ)充一下
@AutoService(Processor.class)的作用
在使用注解處理器需要先聲明以下步驟:
1、需要在 apt 庫的 main 目錄下新建 resources 資源文件夾;
2、在 resources文件夾下建立 META-INF/services 目錄文件夾;
3、在 META-INF/services 目錄文件夾下創(chuàng)建 javax.annotation.processing.Processor 文件;
4、在 javax.annotation.processing.Processor 文件寫入注解處理器的全稱,包括包路徑;)
每次這樣寫太麻煩。這就是用引入auto-service的原因。
通過@AutoService可以自動生成上面的步驟,AutoService注解處理器是Google開發(fā)的,用來生成 META-INF/services/javax.annotation.processing.Processor 文件的
-
使用apt
第一步添加依賴
implementation project(':butterknife-annotations')
annotationProcessor project(':butterknife-compiler')有沒有經(jīng)??吹竭@個(gè)gradle api?最早的時(shí)候是這樣的apt project(':butterknife-compiler')現(xiàn)在被廢棄了第二步代碼調(diào)用,大家都懂不說了
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整個(gè)流程:定義注解->注解處理器結(jié)合javapoet去反向生成.java文件->調(diào)用這些.java文件相關(guān)代碼綁定關(guān)系
實(shí)際應(yīng)用
好了最后咱們實(shí)際應(yīng)用下apt,看看項(xiàng)目中可以怎么使用,以自動生成Model層為例

1.首先新建一個(gè)javalib 定義需要的注解
/**
* 自動生成ApiFactory工具類的注解
*/
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)//作用在類上面
public @interface ApiFactory {
String path();//獲取相關(guān)參數(shù)
}
2.再建立一個(gè)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)//自動生成相關(guān)目錄文件,簡化開發(fā)步驟
@SupportedSourceVersion(SourceVersion.RELEASE_8)//java版本支持
@SupportedAnnotationTypes({//標(biāo)注注解處理器支持的注解類型
"com.jcgroup.jcannotation.apt.ApiFactory"
})
public class AnnotationProcessor extends AbstractProcessor {
public Filer mFiler; //文件相關(guān)的輔助類
public Elements mElements; //元素相關(guān)的輔助類
public Messager mMessager; //日志相關(guān)的輔助類
@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)) {//返回列表數(shù)據(jù)
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模塊
調(diào)用自動生成的Model層,這里沒有遵循MVP架構(gòu),演示直接在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 -> {});
}
}
看下這個(gè)自動生成的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);
}
}
可以發(fā)現(xiàn)類似這部分的代碼省掉了
最后做個(gè)小結(jié)吧,合理的利用apt可以省掉大量的模板代碼,大大的提高開發(fā)效率,而掌握apt編程的核心是熟悉javapoet庫的api。
