Android 编译时注解生成代码

xiaoxiao2021-02-28  159

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 咱们的demoviewinject-annotation 注解包,java-library moduleviewinject-complie 编译处理包,java-library moduleviewinject 项目真正需要依赖的包,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,点击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 参考

http://blog.csdn.net/industriously/article/details/53932425http://blog.csdn.net/lmj623565791/article/details/43452969https://github.com/square/javapoet

9 源码地址

https://github.com/yizeliang/ViewInject

转载请注明原文地址: https://www.6miu.com/read-30534.html

最新回复(0)