apt 根据注解,编译时生成代码

时间:2023-03-08 16:05:51

apt:

@Retention后面的值,设置的为CLASS,说明就是编译时动态处理的。一般这类注解会在编译的时候,根据注解标识,动态生成一些类或者生成一些xml都可以,在运行时期,这类注解是没有的~~会依靠动态生成的类做一些操作,因为没有反射,效率和直接调用方法没什么区别~~~

RUNTIME, 说明就是运行时动态处理,这个大家见得应该最多,在运行时拿到类的Class对象,然后遍历其方法、变量,判断有无注解声明,然后做一些事情。

SOURCE,标记一些信息,这么说可能太抽象,那么我说,你见过@Override、@SuppressWarnings等,这类注解就是用于标识,可以用作一些检验

@Target表示该注解可以用于什么地方,可能的类型TYPE(类),FIELD(成员变量)

 public enum ElementType {
/**
* Class, interface or enum declaration.
*/
TYPE,
/**
* Field declaration.
*/
FIELD,
/**
* Method declaration.
*/
METHOD,
/**
* Parameter declaration.
*/
PARAMETER,
/**
* Constructor declaration.
*/
CONSTRUCTOR,
/**
* Local variable declaration.
*/
LOCAL_VARIABLE,
/**
* Annotation type declaration.
*/
ANNOTATION_TYPE,
/**
* Package declaration.
*/
PACKAGE
}

TypeElement  :类

JavaPoet源码初探

TypeSpec是类型元素的抽象,通过Kind枚举定义class、interface、enum、annotation四种类型。

MethodSpec代表方法的抽象。

     // 元素操作的辅助类
Elements elementUtils;
//文件相关的辅助类
private Filer mFiler;
//日志相关的辅助类
private Messager mMessager;

1、先添加两个java library,一个annotation,一个compiler:

apt 根据注解,编译时生成代码

annotation library:用于放置注解。

build.gradle文件:注意属于java library,不使用android library。

  apply plugin: 'java'

  sourceCompatibility = 1.7
targetCompatibility = 1.7
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}

compile library:用于放置根据注解生成代码类。

build.gradle文件:注意属于java library,不使用android library。

 apply plugin: 'java'

 sourceCompatibility = 1.7
targetCompatibility = 1.7
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.google.auto.service:auto-service:1.0-rc2'
compile 'com.squareup:javapoet:1.7.0'
<!-- 引用annotation library -->
compile project(':annotation')
}

app module:android app

build.gradle文件:

 apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt' android {
compileSdkVersion 23
buildToolsVersion "23.0.2" defaultConfig {
applicationId "com.example.aptdemo"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
} dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:24.0.0' compile project(':annotation')
apt project(':compiler')
}

官方有一个列子的:生成一个java类,然后java 类里面有个main函数:

// 注解类 (目录:annotation library)

package com.example;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Test {
}

// apt生成类 (目录:compiler library)

 package com.example;

 import com.google.auto.service.AutoService;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import java.io.IOException;
import java.util.Collections;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement; @AutoService(Processor.class)
public class TestProcessor extends AbstractProcessor { /***
package com.example.helloworld; public final class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, JavaPoet!");
}
}
*/ @Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton(Test.class.getCanonicalName());
} @Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
.build(); TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build(); JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build(); try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
} return false;
} @Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_7;
}
}

官方例子

// 使用:

在随意一个类上面加上@Test,编译时,代码就会生成。

下面就是简单的view注解生成代码:

// 注解类 (目录:annotation library)

1、activity 注解 :作用于类,所以目标类型是类。

 @Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface DIActivity {
}

2、view 注解:作用于字段,所以目标类型是字段。

 @Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DIView { int value() default 0;
}

3、onClick 注解:作用于方法,所以目标类型是方法。

 @Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DIOnClick { int[] value() default 0;
}

// 使用工具类:

ViewInject:用于统一activity 使用方法,注入。

 public class ViewInject {

     public final static void bind(Activity activity){

         String className = activity.getClass().getName();
try {
Class<?> aClass = Class.forName(className + "$ViewInject");
Inject inject = (Inject) aClass.newInstance();
inject.bindView(activity);
} catch (Exception e){
e.printStackTrace();
}
}
}

Inject:用于apt生成类继承,统一父类。

 public interface Inject<T> {

     void bindView(T host);
}

// apt生成类 (目录:compiler library)

DIProcessor:

 @AutoService(Processor.class)
public class DIProcessor extends AbstractProcessor { // 元素操作的辅助类
Elements elementUtils;
//文件相关的辅助类
private Filer mFiler;
//日志相关的辅助类
private Messager mMessager; @Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
// 元素操作的辅助类
elementUtils = processingEnv.getElementUtils();
mFiler = processingEnv.getFiler();
mMessager = processingEnv.getMessager();
} /**
* 指定哪些注解应该被注解处理器注册
* @return
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
/**
* Set<String> types = new LinkedHashSet<>();
types.add(BindView.class.getCanonicalName());
types.add(OnClick.class.getCanonicalName());
return types;
* */
// 规定需要处理的注解
return Collections.singleton(DIActivity.class.getCanonicalName());
} @Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(DIActivity.class); for(Element element : elements){ // 判断是否Class
TypeElement typeElement = (TypeElement) element; List<? extends Element> members = elementUtils.getAllMembers(typeElement); MethodSpec.Builder bindViewMethod = MethodSpec.methodBuilder("bindView")
.addModifiers(Modifier.PUBLIC)
.returns(TypeName.VOID)
.addParameter(ClassName.get(typeElement.asType()), "activity", Modifier.FINAL); bindViewMethod.addStatement("mContext = activity"); MethodSpec.Builder onClickMethod = MethodSpec.methodBuilder("onClick")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(TypeName.VOID)
.addParameter(ClassName.get("android.view", "View"), "view"); // TypeSpec.Builder listener = TypeSpec.anonymousClassBuilder("")
// .addSuperinterface(ClassName.get("android.view", "View", "OnClickListener")); mMessager.printMessage(Diagnostic.Kind.NOTE, "非常厉害"); // bindViewMethod.addStatement("View.OnClickListener listener = null");
onClickMethod.addCode("switch(view.getId()) {"); for (Element item : members) {
// handler the findViewById
DIView diView = item.getAnnotation(DIView.class);
if (diView != null){ bindViewMethod.addStatement(String.format("activity.%s = (%s) activity.findViewById(%s)"
,item.getSimpleName(), ClassName.get(item.asType()).toString(), diView.value()));
continue;
} // handler the setOnViewClick
DIOnClick diOnClick = item.getAnnotation(DIOnClick.class);
if(diOnClick != null) { if(diOnClick.value().length > 1) {
for (int index = 0; index < diOnClick.value().length; index++) {
onClickMethod.addCode("case $L: ", diOnClick.value()[index]); bindViewMethod.addStatement("activity.findViewById($L).setOnClickListener(this)", diOnClick.value()[index]);
} onClickMethod.addStatement("mContext.$N()", item.getSimpleName()); onClickMethod.addStatement("break");
} if(diOnClick.value().length == 1) {
onClickMethod.addCode("case $L: ", diOnClick.value()[0]);
onClickMethod.addStatement("mContext.$N()", item.getSimpleName());
onClickMethod.addStatement("break"); bindViewMethod.addStatement("activity.findViewById($L).setOnClickListener(this)", diOnClick.value()[0]);
}
continue;
}
} onClickMethod.addStatement("}"); // TypeSpec onClickListener = listener.addMethod(onClickMethod.build()).build();
// bindViewMethod.addStatement("listener = $L ", onClickListener); List<MethodSpec> methodSpecs = new ArrayList<>();
methodSpecs.add(bindViewMethod.build());
methodSpecs.add(onClickMethod.build()); List<TypeName> typeNames = new ArrayList<>();
typeNames.add(ClassName.get("android.view", "View", "OnClickListener"));
typeNames.add(ParameterizedTypeName.get(ClassName.get("com.example.charles.aptdemo.inject", "Inject"), TypeName.get(typeElement.asType()))); FieldSpec fieldSpec = FieldSpec.builder(TypeName.get(typeElement.asType()), "mContext").build(); TypeSpec typeSpec = TypeSpec.classBuilder(element.getSimpleName() + "$ViewInject")
// .superclass(TypeName.get(typeElement.asType()))
.addSuperinterfaces(typeNames)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethods(methodSpecs)
.addField(fieldSpec)
.build(); JavaFile javaFile = JavaFile.builder(getPackageName(typeElement), typeSpec).build(); try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
} mMessager.printMessage(Diagnostic.Kind.NOTE, "完成");
} return true;
} private String getPackageName(TypeElement type) {
return elementUtils.getPackageOf(type).getQualifiedName().toString();
} /**
* 指定使用的 Java 版本。通常返回SourceVersion.latestSupported()。
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_7;
}
}

DIProcessor

// 使用方式:

 @DIActivity
public class MainActivity extends AppCompatActivity { @DIView(R.id.text)
public TextView textView; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); ViewInject.bind(this); textView.setText("我不是这样子的");
} @DIOnClick({R.id.btn_change_text, R.id.btn_change})
public void changeTextView(){
textView.setText("我被点击了");
} @DIOnClick(R.id.btn_change_new)
public void changeNew(){
textView.setText("new");
}
}

关于javapoet:

JavaPoet 是一个用来生成 .java源文件的Java API。

下列文章为apt

Android注解-编译时生成代码 (APT)

下列文章为反射:

Android 打造编译时注解解析框架 这只是一个开始

Android 进阶 教你打造 Android 中的 IOC 框架 【ViewInject】 (上)

Android 进阶 教你打造 Android 中的 IOC 框架 【ViewInject】 (下)