您的位置:首页 > 移动开发 > Android开发

Android中利用APT生成代码

2017-06-18 00:49 555 查看
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来生成代码

肯定是要顶一个类来实现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<String> getSupportedAnnotationTypes() {// 要处理的相关注解类
Set<String> 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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  apt android