注解处理器,顾名思义:就是专门处理注解的处理器.
上一篇 我们对java的注解进行了比较详细的认识和了解,系统提供的注解是由系统的注解处理器来执行处理,而我们自定义的注解系统是没法帮我们直接处理的,这样我们自定义的注解就失去了本身存在的意义了,实则不然,我们自定义的注解需要我们自己来处理,比如通过APT技术、反射就可以来处理我们自己的注解。
今天我们从APT技术的基础来进行了解。
APT ( Annotation Processing Tool),他是javac的一个工具,中文意思为注解处理器。他可以用>来在 编译时 扫描和处理注解。通过APT我们可以获取到注解和被注解对象的相关信息,在拿>到这些信息后我们可以根据需求来自动生成一些代码,省去了不少手动编写代码。
需要强调的一点:<u>获取注解和生成一些代码都是在编译时完成的。</u>
我们先来看看JDK提供给我们的注解处理器:
注解处理器类分析思维导图.png手把手教你自定义注解处理器:
背景:Android Studio新建工程 CustomAnnotation
1. 注解Annotation Module :CusAnnotation
<!--Java Library-->
自定义注解 BindView,保留级别是Class ,作用域是 Field , 参数是viewId,
如下代码:
@Documented
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int viewId() ;
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface ViewOnClick {
int[] viewId();
}
2. 注解处理器Annotation Processor Module: CusProcessor
2.1 ) 在该module的build.gradle 中添加:
api 'com.squareup:javapoet:1.7.0' //生成java文件 可以去查一下该库的作用
api project(':CusAnnotation') //应用自定义的注解module
2.2 ) 新建AnnotationProcessor.java 文件
@SupportedAnnotationTypes({"com.leon.annotation.BindView", "com.leon.annotation.ViewOnClick"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class AnnotationProcessor extends AbstractProcessor {
private static final String TAG = AnnotationProcessor.class.getSimpleName();
private Filer filer;
private Elements elementUtils;
private HashMap<String, List<Element>> mPackageClassSet = new HashMap<>();//存放解析的注解节点信息 注解名-包名-类名 作为key
private HashMap<String, TypeSpec> mFilerTypeSpec = new HashMap<>();//存放生成对应文件的type info 包名-类名 作为key
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
elementUtils = processingEnv.getElementUtils();
filer = processingEnv.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
if (set != null && set.size() > 0) {
//set中存放的是注解处理器支持并解析的注解类型
for (TypeElement typeElement : set) {
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(typeElement);
for (Element element : elements) {
//获取注解所属的类名
String className = element.getEnclosingElement().getSimpleName().toString();
String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
//使用annotationTypeName , packageName ,ClassName 拼接为mapkey ,中间用-连接,方便split来区分
if (!mPackageClassSet.containsKey(typeElement.toString() + "-" + packageName + "-" + className)) {
List<Element> subelements = new ArrayList<>();
subelements.add(element);
/*
* TODO 相同注解的存在在一起,便于做方法体时使用
*/
mPackageClassSet.put(typeElement.toString() + "-" + packageName + "-" + className, subelements);
} else {
mPackageClassSet.get(typeElement.toString() + "-" + packageName + "-" + className).add(element);
}
}
}
generateCodeByJavaPoet();
}
return false;
}
private void generateCodeByJavaPoet() {
Iterator entries = mPackageClassSet.entrySet().iterator();
while (entries.hasNext()) {
//取出hashmap的元素
Map.Entry<String, ArrayList<Element>> entry = (Map.Entry<String, ArrayList<Element>>) entries.next();
//解析key ,包名和类名
String[] subKeys = entry.getKey().split("-");
ClassName t = ClassName.bestGuess(subKeys[2]);
//用来存放创建的方法,因为如果同时存在多个注解,可以穿记得方法会比较多
List<MethodSpec> methods = new ArrayList<>();
if (subKeys[0].equals(BindView.class.getTypeName())) {
//创建绑定view的方法
MethodSpec.Builder bindMethodBuilder = MethodSpec.methodBuilder("BindViewById")
.addModifiers(Modifier.PUBLIC)
.addParameter(t, "activity");
for (Element element : entry.getValue()) {
//注解上的额值
int resId = element.getAnnotation(BindView.class).viewId();
//添加方法体
bindMethodBuilder.addStatement("activity.$L = activity.findViewById($L)", element.getSimpleName(), resId);
}
methods.add(bindMethodBuilder.build());
}
if (subKeys[0].equals(ViewOnClick.class.getTypeName())) {
//创建绑定点击事件的方法
MethodSpec.Builder onClickMethodSpecBuilder = MethodSpec.methodBuilder("BindViewOnClick")
.addModifiers(Modifier.PUBLIC)
.addParameter(t, "activity", Modifier.FINAL)
.returns(void.class);
for (Element element : entry.getValue()) {
int[] resId = element.getAnnotation(ViewOnClick.class).viewId();
for (int i = 0; i < resId.length; i++) {
String stateMent = String.format("activity.findViewById(%d).setOnClickListener(new android.view.View.OnClickListener() {" +
"\n @Override" +
"\n public void onClick(android.view.View v) {" +
"\n activity.onClick(%d);" +
"\n }" +
"\n })", resId[i], resId[i]);
onClickMethodSpecBuilder.addStatement(stateMent);
}
}
methods.add(onClickMethodSpecBuilder.build());
}
/*
* TODO 根据类名和包名进行分组存放,便于在不各自不同的文件中存放对应的代码
*/
String filerTypeKey = entry.getKey().substring(entry.getKey().indexOf("-") + 1);
if (mFilerTypeSpec.containsKey(filerTypeKey)) {
TypeSpec typeSpec = mFilerTypeSpec.get(filerTypeKey).toBuilder().addMethods(methods).build();
mFilerTypeSpec.put(filerTypeKey, typeSpec);
} else {
TypeSpec typeSpec = TypeSpec.classBuilder(String.format(subKeys[2] + "_customButterKnife"))//设置类名
.addModifiers(Modifier.PUBLIC).build();//添加修饰符
typeSpec = typeSpec.toBuilder().addMethods(methods).build();
mFilerTypeSpec.put(filerTypeKey, typeSpec);
}
if (!entries.hasNext()) {
//通过包名-类名和TypeSpec(类)生成一个java文件
Set<String> keySet = mFilerTypeSpec.keySet();
for (String key : keySet) {
String[] split = key.split("-");
JavaFile build = JavaFile.builder(split[0], mFilerTypeSpec.get(key)).build();
try {
//写入到filer中
build.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
2.3) 注册自定义的注解处理器
a. 在main目录下面新建resources 文件夹,
b. 在resources中新建 'META-INF/services'的文件夹,
c. 新建命名为:javax.annotation.processing.Processor文件,文件内容为自定义注解的包名+注解处理器名。如:
com.com.leon.processor.AnnotationProcessor
注:
上述每一步的名字都不能错,包括大小写
网上说可以使用com.google.auto.service:auto-service:1.0-rc6 ,参考:https://www.jianshu.com/p/402788e70b39
3. 注解处理器绑定
截止目前为止,注解和注解处理器都已经完成了,那么怎么样才可以让我们得注解处理器工作起来呢。我们新建一个module 来专门完成注解与注解处理器的绑定工作。
3.1 ) 引用自定义的注解 build.gradle
api project(':CusAnnotation')
3.2 ) 创建绑定类:CustomButterKnife
public class CustomButterKnife {
public static void bind(Activity activity) {
String activityName = activity.getClass().getName();
String genrateClass = activityName + "_customButterKnife";
try {
//调用构造器来实现bind
Object inject = Class.forName(genrateClass).getConstructor().newInstance();
/*
* TODO 调用生成的java类进行onclick事件的注解绑定
*/
if (null != inject) {
Method bindViewByIdMethond = inject.getClass().getDeclaredMethod("BindViewById", activity.getClass());
bindViewByIdMethond.invoke(inject, activity);
}
/*
* TODO 调用生成的java类进行onclick事件的注解绑定
*/
if (null != inject) {
Method bindViewOnClickMethod = inject.getClass().getDeclaredMethod("BindViewOnClick", activity.getClass());
bindViewOnClickMethod.invoke(inject, activity);
}
} catch (InstantiationException | ClassNotFoundException | NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
4.测试module
这一步就是在app module 中使用我们自己的注解。
public class MainActivity extends AppCompatActivity {
@BindView(viewId = R.id.name)
TextView mName;
@BindView(viewId = R.id.school)
TextView mSchool;
@Override
protected int getLayoutId() {
return R.layout.activity_main;
}
@Override
protected void onResume() {
super.onResume();
if (mName != null) {
mName.setText(mName.getText().toString() + "张三");
}
if (mSchool != null) {
mSchool.setText(mSchool.getText().toString() + "学校");
}
}
@ViewOnClick(viewId = {R.id.name, R.id.school})
public void onClick(int viewId) {
if (viewId == R.id.name) {
Toast.makeText(this, mName.getText().toString(), Toast.LENGTH_SHORT).show();
}
if (viewId == R.id.school) {
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);
}
}
}
记得要在build.gradle中添加注解和注解处理的引用
implementation 'com.squareup:javapoet:1.7.0' annotationProcessor project(":CusProcessor") api project (':CustomButterKnife')
至此,你可以将运行你的程序,自定义的注解处理器可以完成你安排的事情了。
笔者刚开始遇到注解无效的情况,那么debug 有无法调试注解处理器。不要慌,坑我已经踩过了,接着往下走。。。。
5. Debug调试注解处理器
注解处理器不是不可以调试,
5.1 修改根目录中的gradle.properties文件
- org.gradle.jvmargs= -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5006
- org.gradle.parallel=true
5.2 修改Edit configuation
EditConfiguation 新建Remote ,可以修改名字为APT ,其他配置和我们gradle.properties一致
Remote新建点击OK ,
5.3调试注解处理器
选择目标设备APT ,在自定义的注解处理器中打断点,然后 Clean Project , 然后Make Project 即可进入断点。
5.4调试注解处理器信息
Debug调试信息d. 查看注解处理器生成的java文件
生成的java文件以上测试代码的demo地址:CustomAnnotation 更新中。。。
网友评论