Android 编译时注解生成代码

Android 编译时注解生成代码

* 本项目 只是学习使用,项目中推荐ButterKnife*

1 简介

  在现阶段的Android开发中,注解越来越流行起来,比如ButterKnife,Retrofit,Dragger,EventBus等等都选择使用注解来配置。按照处理时期,注解又分为两种类型,一种是运行时注解,另一种是编译时注解,运行时注解由于性能问题被一些人所诟病。编译时注解的核心依赖APT(Annotation Processing Tools)实现,原理是在某些代码元素上(如类型、函数、字段等)添加注解,在编译时编译器会检查AbstractProcessor的子类,并且调用该类型的process函数,然后将添加了注解的所有元素都传递到process函数中,使得开发人员可以在编译器进行相应的处理,例如,根据注解生成新的Java类,这也就是EventBus,Retrofit,Dragger等开源库的基本原理。
Java API已经提供了扫描源码并解析注解的框架,你可以继承AbstractProcessor类来提供实现自己的解析注解逻辑。下边我们将学习如何在android Studio中通过编译时注解生成java文件。[摘自:http://blog.csdn.net/industriously/article/details/53932425]

  本次,咱们打算做一个类似于butterKnife的框架,但是由于时间有限,暂时只做一下view的绑定,大致使用如下


package cn.yzl.abstractprocessor;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;

import cn.yzl.viewinject.ViewInjectHelper;
import cn.yzl.viewinject.annotation.ViewInject;

public class MainActivity extends AppCompatActivity {


    @ViewInject(R.id.myview)
    TextView myView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //类似于butterKnifer的使用方式
        ViewInjectHelper.inject(this);

        myView.setText("赋值成功了");

    }
}

生成的类


package cn.yzl.abstractprocessor;

import android.view.View;
import android.widget.TextView;
import cn.yzl.abstractprocessor.MainActivity;
import cn.yzl.viewinject.Utils;

class MainActivity_ViewInject {
    public MainActivity_ViewInject(MainActivity target, View rootView) {
        target.myView = (TextView)Utils.getViewById(rootView, 2131427422);
    }
}

关于生成的类,为什么要写成这个样子,而不是一个包含静态方法的类,这里是为了处理 onclick事件,而且希望能够把这个类缓存起来,不需要没进一次都用classloader加载一下这个类,,其实这里使用构造方法是不合理的,不过,,,,我懒….

2 项目结构

  • app 咱们的demo
  • viewinject-annotation 注解包,java-library module
  • viewinject-complie 编译处理包,java-library module
  • viewinject 项目真正需要依赖的包,Android-library module,查找咱们编译时生成的类,并执行,并且改包依赖于viewinject-annotation,这样在app中只需要添加本依赖包就好

3 自定义注解

package cn.yzl.viewinject.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * View绑定注解
 * Created by YZL on 2017/8/4.
 */
@Retention(RetentionPolicy.CLASS)//
@Target(ElementType.FIELD)
public @interface ViewInject {
    int value();
}

这个注解很简单,作用于变量,注意这里的Retention不再是以往的Runtime,而是CLASS,表示这个注解只保留到编译期,在运行的时候是没有的

4 编译时处理器

先看一下它的依赖库

    //生成代码的一个库,比手写方便的多,功能强大,自动导包
    compile 'com.squareup:javapoet:1.9.0'
    //google的 auto service库,自动生成service,省去了手动配置resources/META-INF/services
    compileOnly 'com.google.auto.service:auto-service:1.0-rc3'
    //依赖注解项目,因为要在这里读取注解
    compile project(':viewinject-annotation')
  • ViewInjectProcess

package cn.yzl.process.library;

import com.google.auto.service.AutoService;
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 java.util.ArrayList;
import java.util.List;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
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.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;

import cn.yzl.viewinject.annotation.ViewInject;
import cn.yzl.viewinject.annotation.ViewOnClick;

@AutoService(Processor.class)
@SupportedAnnotationTypes("cn.yzl.viewinject.annotation.ViewInject")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class ViewInjectProcess extends AbstractProcessor {

    //生成的类的 后缀名
    public static final String SUFFIX = "_ViewInject";

    //获取View类,用于javapoet 生成代码
    ClassName VIEW = ClassName.get("android.view", "View");

    //获取Utils类,就一个方法,findviewbyid,可有可无吧
    ClassName UTILS = ClassName.get("cn.yzl.viewinject", "Utils");

    //打印消息类
    private Messager messager;

    //最终的生成的文件要通过这个写入进去
    private Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {

        List<TypeBean> types = findAnnotations(roundEnv);

        if (types.size() == 0) {
            return false;
        } else {
            generateJavaFile(types);
        }

        return false;
    }


    /**
     * 查找被注解的 变量和其所在的class,封装成typeBean存到list里
     * @param roundEnv
     * @return
     */
    public List<TypeBean> findAnnotations(RoundEnvironment roundEnv) {
        List<TypeBean> types = new ArrayList<>();

        Set<? extends Element> eFilds = roundEnv
                .getElementsAnnotatedWith(ViewInject.class);
        Set<? extends Element> eMethods = roundEnv
                .getElementsAnnotatedWith(ViewOnClick.class);
        for (Element e : eFilds) {
            TypeElement typeElement = (TypeElement) e.getEnclosingElement();
            String qualifiedName = typeElement.getQualifiedName().toString();
            TypeBean typeBean = getTypeBeanByName(types, typeElement, qualifiedName);

            typeBean.addField((VariableElement) e);
        }

        for (Element e : eMethods) {
            TypeElement typeElement = (TypeElement) e.getEnclosingElement();
            String qualifiedName = typeElement.getQualifiedName().toString();
            TypeBean typeBean = getTypeBeanByName(types, typeElement, qualifiedName);
            if (typeBean.onClickmethod != null) {
//                error(e,msg);
            } else {
                typeBean.onClickmethod = (ExecutableElement) e;
            }
        }
        return types;
    }


    /**
     * 根据解析出来的类生成 辅助类
     * @param types
     */
    private void generateJavaFile(List<TypeBean> types) {
        for (TypeBean typebean : types) {
            generateJavaFileForOneType(typebean);
        }
    }

    /**
     * 生成辅助类
     * @param typebean
     */
    private void generateJavaFileForOneType(TypeBean typebean) {

        CodeBlock.Builder codeBuilder = CodeBlock.builder();

        //写findviewbyid 代码
        for (int i = 0; i < typebean.fields.size(); i++) {
            //findViewById
            VariableElement variableElement = typebean.fields.get(i);
            ViewInject annotation = variableElement.getAnnotation(ViewInject.class);
            codeBuilder.add(
                    "target." + variableElement.getSimpleName().toString() + "=" +
                            "$T.getViewById(rootView," + annotation.value() + ");\n"

                    , UTILS);
        }

        //TODO onclick

        // 生成构造方法
        MethodSpec method = MethodSpec
                .constructorBuilder()
                .addModifiers(Modifier.PUBLIC)
                .addParameter(TypeName.get(typebean.typeElement.asType()), "target")
                .addParameter(VIEW, "rootView")
                .addCode(codeBuilder.build())
                .build();

        //生成类文件
        TypeSpec typeSpec = TypeSpec.classBuilder(typebean.simpleName + SUFFIX)
                .addMethod(method)
                .build();
        writeJavaFile(typebean.packName, typeSpec);
    }

    /**
     * @param types
     * @param typeElement
     * @param className   @return
     */
    public TypeBean getTypeBeanByName(List<TypeBean> types, TypeElement typeElement, String className) {
        for (int i = 0; i < types.size(); i++) {
            if (types.get(i).equalsClass(className))
                return types.get(i);
        }
        TypeBean typeBean = new TypeBean(typeElement, className);
        types.add(typeBean);
        return typeBean;
    }

    /**
     * 写入类
     * @param packName
     * @param typeSpec
     */
    public void writeJavaFile(String packName, TypeSpec typeSpec) {
        JavaFile javaFile = JavaFile.builder(packName, typeSpec).build();
        try {
            javaFile.writeTo(filer);
        } catch (IOException e) {
            e.printStackTrace();
            messager.printMessage(Diagnostic.Kind.ERROR, e.getMessage());
        }
    }
}

这里面有一个类,TypeBean,里面就是存放的 一个类中所有的被Viewinject注解的方法,和类的包名和全称,这里不再做解析

使用

在app的gradle中

dependencies {
    compile project(':ViewInject')
    annotationProcessor project(':viewinject-complie')
}

这样就可以了,在编译的时候就会生成咱们需要的辅助类

这里有个问题,processor中不可以有中文,包括注释,否则会报以下错误

5 处理辅助类

  这个类特别简单,就是拿到咱们生成的class,获得咱们生成的方法,掉一下就ok

    package cn.yzl.viewinject;

import android.app.Activity;
import android.util.Log;
import android.view.View;

import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * Created by YZL on 2017/8/4.
 */

public class ViewInjectHelper {
    public static final String SUFFIX = "_ViewInject";

    public static void inject(Activity target) {
        ClassLoader classLoader = target.getClass().getClassLoader();
        try {
            //拿到生成的类的全名
            String proxyClazzName = target.getClass().getName() + SUFFIX;
            Log.e("ViewInjectHelper#inject", proxyClazzName);
            //加载类
            Class<?> aClass = classLoader.loadClass(proxyClazzName);

            //拿到docView
            View rootView = target.getWindow().getDecorView();

            //获得申明的构造函数,这里就一个
            Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
            //取消安全校验
            declaredConstructors[0].setAccessible(true);
            //执行构造方法
            declaredConstructors[0].newInstance(target, rootView);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

好了,这样我们就可以使用了

6 调试编译过程

  • gradle.properties文件中加入以下两行代码,然后同步gradle文件
org.gradle.daemon=true
org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8888
  • 编辑运行选项

编辑运行选项

-添加remote

添加remote

  • 采用默认配置就好

采用默认配置就好

  • 选择咱们配置好的remote,点击debug按钮,成功的话,下面会弹出连接成功的提示

选择咱们配置好的remote,点击debug按钮,成功的话,下面会弹出连接成功的提示

  • 在咱们的ViewInjectProcessor中打断点,然后rebuild项目,就可以debug了

在咱们的ViewInjectProcessor中打断点,然后rebuild项目,就可以debug了

7 补充代码参考

    //另外一种生成代码的方式
    JavaFileObject source = processingEnvironment.getFiler().createSourceFile("cn.yzl.library.generated.GeneratedClass");
    Writer writer = source.openWriter();
    writer.write("你生成的代码");
    writer.flush();
    writer.close();

8 参考

9 源码地址

https://github.com/yizeliang/ViewInject

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值