apt:
@Retention后面的值,设置的为CLASS,说明就是编译时动态处理的。一般这类注解会在编译的时候,根据注解标识,动态生成一些类或者生成一些xml都可以,在运行时期,这类注解是没有的~~会依靠动态生成的类做一些操作,因为没有反射,效率和直接调用方法没什么区别~~~
RUNTIME, 说明就是运行时动态处理,这个大家见得应该最多,在运行时拿到类的Class对象,然后遍历其方法、变量,判断有无注解声明,然后做一些事情。
SOURCE,标记一些信息,这么说可能太抽象,那么我说,你见过@Override、@SuppressWarnings等,这类注解就是用于标识,可以用作一些检验
@Target表示该注解可以用于什么地方,可能的类型TYPE(类),FIELD(成员变量)
1 public enum ElementType { 2 /** 3 * Class, interface or enum declaration. 4 */ 5 TYPE, 6 /** 7 * Field declaration. 8 */ 9 FIELD, 10 /** 11 * Method declaration. 12 */ 13 METHOD, 14 /** 15 * Parameter declaration. 16 */ 17 PARAMETER, 18 /** 19 * Constructor declaration. 20 */ 21 CONSTRUCTOR, 22 /** 23 * Local variable declaration. 24 */ 25 LOCAL_VARIABLE, 26 /** 27 * Annotation type declaration. 28 */ 29 ANNOTATION_TYPE, 30 /** 31 * Package declaration. 32 */ 33 PACKAGE 34 }
TypeElement :类
JavaPoet源码初探
TypeSpec是类型元素的抽象,通过Kind枚举定义class、interface、enum、annotation四种类型。
MethodSpec代表方法的抽象。
1 // 元素操作的辅助类 2 Elements elementUtils; 3 //文件相关的辅助类 4 private Filer mFiler; 5 //日志相关的辅助类 6 private Messager mMessager;
1、先添加两个java library,一个annotation,一个compiler:
annotation library:用于放置注解。
build.gradle文件:注意属于java library,不使用android library。
1 apply plugin: 'java' 2 3 sourceCompatibility = 1.7 4 targetCompatibility = 1.7 5 dependencies { 6 compile fileTree(dir: 'libs', include: ['*.jar']) 7 }
compile library:用于放置根据注解生成代码类。
build.gradle文件:注意属于java library,不使用android library。
1 apply plugin: 'java' 2 3 sourceCompatibility = 1.7 4 targetCompatibility = 1.7 5 dependencies { 6 compile fileTree(dir: 'libs', include: ['*.jar']) 7 8 compile 'com.google.auto.service:auto-service:1.0-rc2' 9 compile 'com.squareup:javapoet:1.7.0' 10 <!-- 引用annotation library --> 11 compile project(':annotation') 12 }
app module:android app
build.gradle文件:
1 apply plugin: 'com.android.application' 2 apply plugin: 'com.neenbedankt.android-apt' 3 4 android { 5 compileSdkVersion 23 6 buildToolsVersion "23.0.2" 7 8 defaultConfig { 9 applicationId "com.example.aptdemo" 10 minSdkVersion 15 11 targetSdkVersion 23 12 versionCode 1 13 versionName "1.0" 14 } 15 buildTypes { 16 release { 17 minifyEnabled false 18 proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 } 20 } 21 } 22 23 dependencies { 24 compile fileTree(dir: 'libs', include: ['*.jar']) 25 testCompile 'junit:junit:4.12' 26 compile 'com.android.support:appcompat-v7:24.0.0' 27 28 compile project(':annotation') 29 apt project(':compiler') 30 }
官方有一个列子的:生成一个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)
1 package com.example; 2 3 import com.google.auto.service.AutoService; 4 import com.squareup.javapoet.JavaFile; 5 import com.squareup.javapoet.MethodSpec; 6 import com.squareup.javapoet.TypeSpec; 7 import java.io.IOException; 8 import java.util.Collections; 9 import java.util.Set; 10 import javax.annotation.processing.AbstractProcessor; 11 import javax.annotation.processing.Processor; 12 import javax.annotation.processing.RoundEnvironment; 13 import javax.lang.model.SourceVersion; 14 import javax.lang.model.element.Modifier; 15 import javax.lang.model.element.TypeElement; 16 17 @AutoService(Processor.class) 18 public class TestProcessor extends AbstractProcessor { 19 20 /*** 21 package com.example.helloworld; 22 23 public final class HelloWorld { 24 public static void main(String[] args) { 25 System.out.println("Hello, JavaPoet!"); 26 } 27 } 28 */ 29 30 @Override 31 public Set<String> getSupportedAnnotationTypes() { 32 return Collections.singleton(Test.class.getCanonicalName()); 33 } 34 35 @Override 36 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 37 38 MethodSpec main = MethodSpec.methodBuilder("main") 39 .addModifiers(Modifier.PUBLIC, Modifier.STATIC) 40 .returns(void.class) 41 .addParameter(String[].class, "args") 42 .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!") 43 .build(); 44 45 TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") 46 .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 47 .addMethod(main) 48 .build(); 49 50 JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld) 51 .build(); 52 53 try { 54 javaFile.writeTo(processingEnv.getFiler()); 55 } catch (IOException e) { 56 e.printStackTrace(); 57 } 58 59 return false; 60 } 61 62 @Override 63 public SourceVersion getSupportedSourceVersion() { 64 return SourceVersion.RELEASE_7; 65 } 66 }
// 使用:
在随意一个类上面加上@Test,编译时,代码就会生成。
下面就是简单的view注解生成代码:
// 注解类 (目录:annotation library)
1、activity 注解 :作用于类,所以目标类型是类。
1 @Target(ElementType.TYPE) 2 @Retention(RetentionPolicy.CLASS) 3 public @interface DIActivity { 4 }
2、view 注解:作用于字段,所以目标类型是字段。
1 @Target(ElementType.FIELD) 2 @Retention(RetentionPolicy.RUNTIME) 3 public @interface DIView { 4 5 int value() default 0; 6 }
3、onClick 注解:作用于方法,所以目标类型是方法。
1 @Target(ElementType.METHOD) 2 @Retention(RetentionPolicy.RUNTIME) 3 public @interface DIOnClick { 4 5 int[] value() default 0; 6 }
// 使用工具类:
ViewInject:用于统一activity 使用方法,注入。
1 public class ViewInject { 2 3 public final static void bind(Activity activity){ 4 5 String className = activity.getClass().getName(); 6 try { 7 Class<?> aClass = Class.forName(className + "$ViewInject"); 8 Inject inject = (Inject) aClass.newInstance(); 9 inject.bindView(activity); 10 } catch (Exception e){ 11 e.printStackTrace(); 12 } 13 } 14 }
Inject:用于apt生成类继承,统一父类。
1 public interface Inject<T> { 2 3 void bindView(T host); 4 }
// apt生成类 (目录:compiler library)
DIProcessor:
1 @AutoService(Processor.class) 2 public class DIProcessor extends AbstractProcessor { 3 4 // 元素操作的辅助类 5 Elements elementUtils; 6 //文件相关的辅助类 7 private Filer mFiler; 8 //日志相关的辅助类 9 private Messager mMessager; 10 11 @Override 12 public synchronized void init(ProcessingEnvironment processingEnv) { 13 super.init(processingEnv); 14 // 元素操作的辅助类 15 elementUtils = processingEnv.getElementUtils(); 16 mFiler = processingEnv.getFiler(); 17 mMessager = processingEnv.getMessager(); 18 } 19 20 /** 21 * 指定哪些注解应该被注解处理器注册 22 * @return 23 */ 24 @Override 25 public Set<String> getSupportedAnnotationTypes() { 26 /** 27 * Set<String> types = new LinkedHashSet<>(); 28 types.add(BindView.class.getCanonicalName()); 29 types.add(OnClick.class.getCanonicalName()); 30 return types; 31 * */ 32 // 规定需要处理的注解 33 return Collections.singleton(DIActivity.class.getCanonicalName()); 34 } 35 36 @Override 37 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 38 39 Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(DIActivity.class); 40 41 for(Element element : elements){ 42 43 // 判断是否Class 44 TypeElement typeElement = (TypeElement) element; 45 46 List<? extends Element> members = elementUtils.getAllMembers(typeElement); 47 48 MethodSpec.Builder bindViewMethod = MethodSpec.methodBuilder("bindView") 49 .addModifiers(Modifier.PUBLIC) 50 .returns(TypeName.VOID) 51 .addParameter(ClassName.get(typeElement.asType()), "activity", Modifier.FINAL); 52 53 bindViewMethod.addStatement("mContext = activity"); 54 55 MethodSpec.Builder onClickMethod = MethodSpec.methodBuilder("onClick") 56 .addAnnotation(Override.class) 57 .addModifiers(Modifier.PUBLIC) 58 .returns(TypeName.VOID) 59 .addParameter(ClassName.get("android.view", "View"), "view"); 60 61 // TypeSpec.Builder listener = TypeSpec.anonymousClassBuilder("") 62 // .addSuperinterface(ClassName.get("android.view", "View", "OnClickListener")); 63 64 mMessager.printMessage(Diagnostic.Kind.NOTE, "非常厉害"); 65 66 // bindViewMethod.addStatement("View.OnClickListener listener = null"); 67 onClickMethod.addCode("switch(view.getId()) {"); 68 69 for (Element item : members) { 70 // handler the findViewById 71 DIView diView = item.getAnnotation(DIView.class); 72 if (diView != null){ 73 74 bindViewMethod.addStatement(String.format("activity.%s = (%s) activity.findViewById(%s)" 75 ,item.getSimpleName(), ClassName.get(item.asType()).toString(), diView.value())); 76 continue; 77 } 78 79 // handler the setOnViewClick 80 DIOnClick diOnClick = item.getAnnotation(DIOnClick.class); 81 if(diOnClick != null) { 82 83 if(diOnClick.value().length > 1) { 84 for (int index = 0; index < diOnClick.value().length; index++) { 85 onClickMethod.addCode("case $L: ", diOnClick.value()[index]); 86 87 bindViewMethod.addStatement("activity.findViewById($L).setOnClickListener(this)", diOnClick.value()[index]); 88 } 89 90 onClickMethod.addStatement("mContext.$N()", item.getSimpleName()); 91 92 onClickMethod.addStatement("break"); 93 } 94 95 if(diOnClick.value().length == 1) { 96 onClickMethod.addCode("case $L: ", diOnClick.value()[0]); 97 onClickMethod.addStatement("mContext.$N()", item.getSimpleName()); 98 onClickMethod.addStatement("break"); 99 100 bindViewMethod.addStatement("activity.findViewById($L).setOnClickListener(this)", diOnClick.value()[0]); 101 } 102 continue; 103 } 104 } 105 106 onClickMethod.addStatement("}"); 107 108 // TypeSpec onClickListener = listener.addMethod(onClickMethod.build()).build(); 109 // bindViewMethod.addStatement("listener = $L ", onClickListener); 110 111 List<MethodSpec> methodSpecs = new ArrayList<>(); 112 methodSpecs.add(bindViewMethod.build()); 113 methodSpecs.add(onClickMethod.build()); 114 115 List<TypeName> typeNames = new ArrayList<>(); 116 typeNames.add(ClassName.get("android.view", "View", "OnClickListener")); 117 typeNames.add(ParameterizedTypeName.get(ClassName.get("com.example.charles.aptdemo.inject", "Inject"), TypeName.get(typeElement.asType()))); 118 119 FieldSpec fieldSpec = FieldSpec.builder(TypeName.get(typeElement.asType()), "mContext").build(); 120 121 TypeSpec typeSpec = TypeSpec.classBuilder(element.getSimpleName() + "$ViewInject") 122 // .superclass(TypeName.get(typeElement.asType())) 123 .addSuperinterfaces(typeNames) 124 .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 125 .addMethods(methodSpecs) 126 .addField(fieldSpec) 127 .build(); 128 129 JavaFile javaFile = JavaFile.builder(getPackageName(typeElement), typeSpec).build(); 130 131 try { 132 javaFile.writeTo(processingEnv.getFiler()); 133 } catch (IOException e) { 134 e.printStackTrace(); 135 } 136 137 mMessager.printMessage(Diagnostic.Kind.NOTE, "完成"); 138 } 139 140 return true; 141 } 142 143 private String getPackageName(TypeElement type) { 144 return elementUtils.getPackageOf(type).getQualifiedName().toString(); 145 } 146 147 /** 148 * 指定使用的 Java 版本。通常返回SourceVersion.latestSupported()。 149 * @return 150 */ 151 @Override 152 public SourceVersion getSupportedSourceVersion() { 153 return SourceVersion.RELEASE_7; 154 } 155 }
// 使用方式:
1 @DIActivity 2 public class MainActivity extends AppCompatActivity { 3 4 @DIView(R.id.text) 5 public TextView textView; 6 7 @Override 8 protected void onCreate(Bundle savedInstanceState) { 9 super.onCreate(savedInstanceState); 10 setContentView(R.layout.activity_main); 11 12 ViewInject.bind(this); 13 14 textView.setText("我不是这样子的"); 15 } 16 17 @DIOnClick({R.id.btn_change_text, R.id.btn_change}) 18 public void changeTextView(){ 19 textView.setText("我被点击了"); 20 } 21 22 @DIOnClick(R.id.btn_change_new) 23 public void changeNew(){ 24 textView.setText("new"); 25 } 26 }
关于javapoet:
JavaPoet
是一个用来生成 .java源文件的Java API。
下列文章为apt
Android注解-编译时生成代码 (APT)
下列文章为反射:
Android 打造编译时注解解析框架 这只是一个开始
Android 进阶 教你打造 Android 中的 IOC 框架 【ViewInject】 (上)
Android 进阶 教你打造 Android 中的 IOC 框架 【ViewInject】 (下)