APT已经不新鲜了,虽然我们都知道这是个什么东西:
APT(Annotation Processing Tool 的简称),可以在代码编译期解析注解,并且生成新的 Java 文件。
但是为了能自己动手采用APT写一个框架那才能说是真的了解它、所以本文模仿butterknife自己写一个方便加深印象。
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
String key = getIntent().getStringExtra("key");
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// on click
}
});
很熟悉吧、然后很烦心吧。
每个界面都要这么写表示心累..
@BindView(R.id.fab) FloatingActionButton fab;
@Intent("key") String key;
@OnClick({R.id.fab}) public void fabClick() { Toast.makeText(this, "Neacy", Toast.LENGTH_LONG).show(); }
这才对嘛,这样子我们才能高效的愉快的开发代码…
BindView、Intent、OnClick因为有这些东西那么肯定是注解了,撩起袖子马上定义这几个注解了、这就不一一写出来随便举个做解释一下:
@Retention(RetentionPolicy.CLASS)// 表示我们用于编译注解
@Target(ElementType.FIELD)// 表示我们是用于属性上
public @interface BindView {
int value();
}
肯定是要顶一个类来实现AbstractProcessor这里给一个初始化的模板
@AutoService(Processor.class)
public class NeacyProcesser extends AbstractProcessor {
private Filer mFiler; //文件相关的辅助类
private Elements mElementUtils; //元素相关的辅助类
private Messager mMessager; //日志相关的辅助类
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mFiler = processingEnvironment.getFiler();
mElementUtils = processingEnvironment.getElementUtils();
mMessager = processingEnvironment.getMessager();
}
@Override
public Set getSupportedAnnotationTypes() {// 要处理的相关注解类
Set types = new LinkedHashSet<>();
types.add(BindView.class.getCanonicalName());
types.add(OnClick.class.getCanonicalName());
types.add(Intent.class.getCanonicalName());
return types;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
return true;
}
}
上面的”模板”代码写好了之后其中的process就是用于生成代码用的,所以在这里我们才要真正的实现APT的难点,同样我们拿Intent来做解释:
for (Element element : roundEnvironment.getElementsAnnotatedWith(Intent.class)) {
// ...some codes
}
通过getElementsAnnotatedWith我们可以根据注解来获取Element对象、这个时候我们就要来解释一些Element相关的copy网上一个例子(文章末尾一起给出例子的出处)。
package com.example;
public class Foo { // TypeElement如果你的注解是用于处理类的时候
private int a; // VariableElement如果你的注解是用于处理属性的时候
private Foo other; // VariableElement
public Foo() {} // ExecuteableElement如果你的注解是用于处理方法的时候
public void setA( // ExecuteableElement
int newA // TypeElement
) {
}
}
看了一下上面我添加的注释然后结合代码就能明白这几个Element是干嘛用的了~
public JavaFile generateFinder() {
// method inject(final T host, Object source, Provider provider)
MethodSpec.Builder injectMethodBuilder = MethodSpec.methodBuilder("inject")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.addParameter(TypeName.get(mClassElement.asType()), "host", Modifier.FINAL)
.addParameter(TypeName.OBJECT, "source")
.addParameter(TypeUtil.PROVIDER, "provider");
for (IntentField field : mIntents) {
injectMethodBuilder.addStatement("host.$N = host.getIntent().getStringExtra($S)", field.getFieldName(), field.getKey());
}
// generate whole class
TypeSpec finderClass = TypeSpec.classBuilder(mClassElement.getSimpleName() + "$$Finder")
.addModifiers(Modifier.PUBLIC)
.addSuperinterface(ParameterizedTypeName.get(TypeUtil.FINDER, TypeName.get(mClassElement.asType())))
.addMethod(injectMethodBuilder.build())
.build();
String packageName = mElementUtils.getPackageOf(mClassElement).getQualifiedName().toString();
JavaFile javaFile = JavaFile.builder(packageName, finderClass).build();
return javaFile;
}
这里是采用神奇的Square公司开源的JavaPoet来生成代码,当然如果要用字符串拼接也是可以的。
这里推荐一篇JavaPoet的文章看了你就能懂得上面的代码是什么一下了JavaPoet
最后在process方法中
try {
javaFile.writeTo(mFiler);
} catch (IOException e) {
return true;
}
那么很快就能生成一个java文件了,这里把我项目里面生成的代码复制出来
public class MainActivity$$Finder implements Finder<MainActivity> {
@Override
public void inject(final MainActivity host, Object source, Provider provider) {
host.fab = (FloatingActionButton)(provider.findView(source, 2131558523));
View.OnClickListener listener;
host.key = host.getIntent().getStringExtra("key");
listener = new View.OnClickListener() {
@Override
public void onClick(View view) {
host.fabClick();
}
} ;
provider.findView(source, 2131558523).setOnClickListener(listener);
}
}
其实主要弄懂了这些Elemet是什么意思、怎么用然后就是利用JavaPoet来拼接代码即可了。
最后我们再跟butterknife类似的中间多一层封装
public static void inject(Object host, Object source, Provider provider) {
String className = host.getClass().getName();
try {
Class> finderClass = Class.forName(className + FINDER_SUFFIX);
Finder finder = mFinderArrayMap.get(className);
if (finder == null) {
finder = (Finder) finderClass.newInstance();
mFinderArrayMap.put(className, finder);
}
finder.inject(host, source, provider);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
这样我们就能想butterknife那样子在Activity中一行代码之后所以有标记注解的都帮我们完成了。
NeacyFinder.inject(this);
http://brucezz.itscoder.com/use-apt-in-android
http://blog.csdn.net/crazy1235/article/details/51876192
https://github.com/Neacy/NeacyFinder