Annotation Proccessor注解处理器在android 开源项目中应用广泛,比如大神JakeWharton的ButterKnife。它通过在编译期扫描注解,生成模板类,运行时通过反射调用生成的模板类,以解耦项目,减少模板代码。在项目进行组件化时,页面路由是一个比较核心的问题,从ARouter到JIMU都采用了Annotation Proccessor来实现页面路由。
下边将通过一个简单的自定义Annotation Proccessor例子,来帮你快速掌握这项技能。
AbstractProcessor 主要重写4个函数:
另外,注解处理器要起作用,其jar包需要符合一定的结构,这样系统才能识别,为了方便使用一般会在注解处理器类上添加@AutoService({Processor.class})注解,这个注解的作用就是自动生成对应的结构。 这个注解是google的auto-service里边的。
自定义注解处理器时,从项目工程结构是上来说,一般会分为三个部分:
这个例子主要实现类似butterKnife的自动findViewById操作功能,名字叫BindKnife。
注解类:
package com.github.eklir.androidproccessor.bindknife.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface AutoBind {
int value();
}
常量类:
package com.github.eklir.androidproccessor.bindknife.constants;
public class BindKnifeConstants {
//生成的模板类的后缀
public static final String BIND_SUFFIX = "$$BindKnife$$AutoBind";
}
gradle配置:
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
sourceCompatibility = "8"
targetCompatibility = "8"
package com.github.eklir.androidprocessor.bindknife.proccessor;
import com.github.eklir.androidproccessor.bindknife.annotation.AutoBind;
import com.github.eklir.androidproccessor.bindknife.constants.BindKnifeConstants;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
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.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
@AutoService({Processor.class})
public class BindProccessor extends AbstractProcessor {
private static final String TAG = "BindKnife";
private Map<TypeElement, List<Element>> parentAndChildren = new HashMap<>();
private Filer filer;
private Elements elementUtils;
private Messager messager;
private Types typeUtils;
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> retVal = new HashSet<>();
retVal.add("com.github.eklir.androidproccessor.bindknife.annotation.AutoBind");
return retVal;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
filer = processingEnvironment.getFiler();
elementUtils = processingEnvironment.getElementUtils();
messager = processingEnvironment.getMessager();
typeUtils = processingEnvironment.getTypeUtils();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
Set<? extends Element> autoBindElements = roundEnvironment.getElementsAnnotatedWith(AutoBind.class);
if (autoBindElements == null || autoBindElements.isEmpty()) {
return false;
}
try {
categories(autoBindElements);
generateHelper();
} catch (Exception e) {
messager.printMessage(Diagnostic.Kind.ERROR, "generateHelper failed:"+ e.getMessage());
}
return true;
}
private void generateHelper() throws IOException {
if (!parentAndChildren.isEmpty()) {
Set<Map.Entry<TypeElement, List<Element>>> entries = parentAndChildren.entrySet();
for (Map.Entry<TypeElement, List<Element>> entry : entries) {
//class
TypeElement parent = entry.getKey();
//filed
List<Element> fileds = entry.getValue();
String parentName = parent.getQualifiedName().toString();
int lastDotIndex = parentName.lastIndexOf('.');
String packageName = parentName.substring(0, lastDotIndex);
//定义方法的参数
ParameterSpec paramsSpec = ParameterSpec.builder(TypeName.OBJECT, "target").build();
//定义方法体
MethodSpec.Builder injectMethodBuilder = MethodSpec.methodBuilder("inject")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(TypeName.VOID)
.addParameter(paramsSpec)
.addStatement("$T act = ($T) target", ClassName.get(parent), ClassName.get(parent));
//对所有的字段赋值
for (Element filed : fileds) {
TypeMirror typeMirror = filed.asType();
String fieldName = filed.getSimpleName().toString();
AutoBind annotation = filed.getAnnotation(AutoBind.class);
injectMethodBuilder.addStatement("act." + fieldName + " = ($T)act.findViewById(" + annotation.value() + ")", ClassName.get(typeMirror));
}
MethodSpec injectMethodSpec = injectMethodBuilder.build();
//定义类文件
TypeSpec helper = TypeSpec.classBuilder(parent.getSimpleName() + BindKnifeConstants.BIND_SUFFIX)
.addSuperinterface(ClassName.get("com.github.eklir.android.bindknife", "IKnife"))
.addMethod(injectMethodSpec)
.build();
//生成类文件
JavaFile.builder(packageName, helper).build().writeTo(filer);
}
}
}
/**
* 按类对注解的字段分组
*
* @param autoBindElements
* @throws IllegalAccessException
*/
private void categories(Set<? extends Element> autoBindElements) throws IllegalAccessException {
for (Element element : autoBindElements) {
//字段的封装类型是类;
TypeElement parent = (TypeElement) element.getEnclosingElement();
if (element.getModifiers().contains(Modifier.PRIVATE)) {
throw new IllegalAccessException("AutoBind 注解的字段不能为private!");
}
if (parentAndChildren.containsKey(parent)) {
parentAndChildren.get(parent).add(element);
} else {
List<Element> eleList = new ArrayList<>();
eleList.add(element);
parentAndChildren.put(parent, eleList);
}
}
}
}
gradle配置
apply plugin: 'java-library'
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.google.auto.service:auto-service:1.0-rc4'
implementation 'com.squareup:javapoet:1.11.1'
implementation project(':bindknife_annotation')
}
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
sourceCompatibility = "8"
targetCompatibility = "8"
这个工程主要定义了两个类,一个是调用的入口BindKnife,一个为了方便注解操而定义的IKnife接口
package com.github.eklir.android.bindknife;
public interface IKnife {
void inject(Object target);
}
package com.github.eklir.android.bindknife;
import android.app.Activity;
import com.github.eklir.androidproccessor.bindknife.constants.BindKnifeConstants;
public class BindKnife {
public static void inject(Activity activity) {
String canonicalName = activity.getClass().getCanonicalName();
//拼出模板类的全类名
String bindClassName = canonicalName + BindKnifeConstants.BIND_SUFFIX;
try {
Class<?> bindClazz = Class.forName(bindClassName);
IKnife knife = (IKnife) bindClazz.newInstance();
knife.inject(activity);
} catch (ClassNotFoundException e) {
throw new RuntimeException(String.format("AutoBind helper class [%s] not found",bindClassName));
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
gradle 配置:
apply plugin: 'com.android.library'
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 19
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:28.0.0'
api project(':bindknife_annotation')
}
package com.github.eklir.androidprocessor;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.TextView;
import com.github.eklir.android.bindknife.BindKnife;
import com.github.eklir.androidproccessor.bindknife.annotation.AutoBind;
public class MainActivity extends Activity {
@AutoBind(R.id.tvTest)
TextView textView;
@AutoBind(R.id.ivTest)
ImageView ivTest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//执行入口调用
BindKnife.inject(this);
textView.setText("我是注入的哈哈哈");
ivTest.setImageResource(R.mipmap.ic_launcher);
}
}
gradle配置:
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.github.eklir.androidprocessor"
minSdkVersion 19
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation project(':bindknife')
annotationProcessor project(':bindknife_anno_proccessor')
}
测试时,建议在terminal中输入
gradlew clean assembleDebug
命令来测试,如果有错误,会在命令行里提示。执行命令后,生成的java源文件在 app\build\generated\source\apt\debug\xxx目录下,查看java源码是否正确,来调整调用javapoet的api。
bindknife