Android中利用APT生成代码

来源:互联网 发布:数据汇集平台 编辑:程序博客网 时间:2024/06/05 15:32

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

原创粉丝点击