Android中利用APT生成代码

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值