开头

编码时使用注解,可以提高编码效率、简化代码增强可读性等优点;使用注解还是代码静态扫描的一部分,促进代码规范。安卓注解使用介绍一文中介绍了JDK/SDK提供的注解和support/ButterKnife等第三方提供的注解库,还有其他的一些库,这些基本已经能够满足需求。

support/ButterKnife是应用很广的注解库,它们也是属于“自定义注解”的范畴,只是有因为使用的多了,实际上成为了一个“标准”。

本文从“造库”的角度介绍自定义注解的相关支持,并提供一个示例实现。但是,本文不提供自定义注解相关的静态检查,这需要lint的支持,本文不做介绍,希望后面的文章有机会介绍一下,这里先占个坑

第三方注解库

引入一个注解库,以ButterKnife为例:

  • 添加注解库
implementation 'com.jakewharton:butterknife:8.4.0'
复制代码
  • 添加注解处理器
annotationProcessor 'com.jakewharton:butterknife:8.4.0'
复制代码

添加了这两个库之后,就可以使用这个注解库了。

如果是library项目】,还需要引入butterknife-gradle-plugin插件,在安卓注解使用介绍中有具体介绍。

定义注解

所有的注解都默认继承自java.lang.annotation.Annotation

定义注解时可以声明0..N个成员,例如下面的定义,可以用default为成员指定默认值;成员名称可以按照程序语言的变量命名规则任意给定,成员的类型也是有限制的。在使用时需要指定参数名:@StringAnnotation(value = "data"),当成员只有一个且命名为value时,可省略。

8中基本数据类型,String,Class,Annotation及子类,枚举;

上面列举类型的数组,例如:String[]

public @interface StringAnnotation /*extends Annotation*/{String value() default "";
}
复制代码

动态注解和静态注解

注解要在解析后才能最终发挥作用,解析过程有上面提到的 注解处理器 完成。依据注解处理器解析过程执行的时机,注解可以分为动态注解和静态注解。

动态注解

动态注解又叫运行时注解,注解的解析过程在执行期间进行,使用反射机制完成解析过程,会影响性能;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DynamicIntentKey {String value() default "";
}
复制代码
public class DynamicUtil {public static void inject(Activity activity) {Intent intent = activity.getIntent();// 反射for (Field field : activity.getClass().getDeclaredFields()) {if (field.isAnnotationPresent(DynamicIntentKey.class)) {// 获取注解DynamicIntentKey annotation = field.getAnnotation(DynamicIntentKey.class);String intentKey = annotation.value();// 读取实际的IntentExtra值Serializable serializable = intent.getSerializableExtra(intentKey);if (serializable == null) {if (field.getType().isAssignableFrom(String.class)) {serializable = "";}}try {// 插入值boolean accessible = field.isAccessible();field.setAccessible(true);field.set(activity, serializable);field.setAccessible(accessible);} catch (IllegalAccessException e) {e.printStackTrace();}}}}
复制代码

静态注解

静态注解出现在动态注解之后,并取代动态注解。静态注解相对于动态注解,把注解的解释过程放在编译阶段,在运行时不再需要解释,而是直接使用编译的结果。

因此,编译阶段需要使用相应的工具生成所需的代码。

  • 先定义一个注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface StaticIntentKey {String value();
}
复制代码
  • 然后为这个注解定义一个处理器

注解解释器需要继承自AbstractProcessor基类,并使用@AutoService(Processor.class)声明这个类是一个注解处理器。


import com.google.auto.service.AutoService;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;@AutoService(Processor.class)
public class StaticIntentProcessor extends AbstractProcessor {
}
复制代码

public abstract class AbstractProcessor implements Processor {
}复制代码
  • 注解处理器基类AbstractProcessor实现自Processor接口,其中init()和getSupportedOptions()在抽象类AbstractProcessor给出了实现,StaticIntentProcessor的主体功能是实现process()方法,完成类生成。
public interface Processor {Set<String> getSupportedOptions();// 支持的注解类的类名集合Set<String> getSupportedAnnotationTypes();// 支持的Java版本SourceVersion getSupportedSourceVersion();void init(ProcessingEnvironment var1);boolean process(Set<? extends TypeElement> var1, RoundEnvironment var2);Iterable<? extends Completion> getCompletions(Element var1, AnnotationMirror var2, ExecutableElement var3, String var4);
}
复制代码
  • 通过下面的注解处理器,为所有使用了这个注解的类生成处理代码,不再需要运行时通过反射获得。

因为这个实现没有专门实现一个对应的android-library类型的工程,所以在使用这个注解时,需要先编译完成,编译完成之后有了对应的注解处理器,才可以在Android工程中使用。

@AutoService(Processor.class)
public class StaticIntentProcessor extends AbstractProcessor {private TypeName activityClassName = ClassName.get("android.app", "Activity").withoutAnnotations();private TypeName intentClassName = ClassName.get("android.content", "Intent").withoutAnnotations();@Overridepublic SourceVersion getSupportedSourceVersion() {// 支持java1.7return SourceVersion.RELEASE_7;}@Overridepublic Set<String> getSupportedAnnotationTypes() {// 只处理 StaticIntentKey 注解return Collections.singleton(StaticIntentKey.class.getCanonicalName());}@Overridepublic boolean process(Set<? extends TypeElement> set, RoundEnvironment re) {// StaticMapper的bind方法MethodSpec.Builder method = MethodSpec.methodBuilder("bind").addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).addParameter(activityClassName, "activity");// 查找所有的需要注入的类描述List<InjectDesc> injectDescs = findInjectDesc(set, re);for (int i1 = 0; i1 < injectDescs.size(); i1++) {InjectDesc injectDesc = injectDescs.get(i1);// 创建需要注解的类的Java文件,如上面所述的 IntentActivity$BinderTypeName injectedType = createInjectClassFile(injectDesc);TypeName activityName = typeName(injectDesc.activityName);// $T导入类型// 生成绑定分发的代码method.addCode((i1 == 0 ? "" : " else ") + "if (activity instanceof $T) {\n", activityName);method.addCode("\t$T binder = new $T();\n", injectedType, injectedType);method.addCode("\tbinder.bind((" + activityName + ") activity);\n", activityName, activityName);method.addCode("}");}// 创建StaticMapper类createJavaFile("com.campusboy.annotationtest", "StaticMapper", method.build());return false;}private List<InjectDesc> findInjectDesc(Set<? extends TypeElement> set, RoundEnvironment re) {Map<TypeElement, List<String[]>> targetClassMap = new HashMap<>();// 先获取所有被StaticIntentKey标示的元素Set<? extends Element> elements = re.getElementsAnnotatedWith(StaticIntentKey.class);for (Element element : elements) {// 只关心类别是属性的元素if (element.getKind() != ElementKind.FIELD) {processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "only support field");continue;}// 此处找到的是类的描述类型// 因为我们的StaticIntentKey的注解描述是field,所以closingElement元素是类TypeElement classType = (TypeElement) element.getEnclosingElement();System.out.println(classType);// 对类做缓存,避免重复List<String[]> nameList = targetClassMap.get(classType);if (nameList == null) {nameList = new ArrayList<>();targetClassMap.put(classType, nameList);}// 被注解的值,如staticNameString fieldName = element.getSimpleName().toString();// 被注解的值的类型,如String,intString fieldTypeName = element.asType().toString();// 注解本身的值,如key_nameString intentName = element.getAnnotation(StaticIntentKey.class).value();String[] names = new String[]{fieldName, fieldTypeName, intentName};nameList.add(names);}List<InjectDesc> injectDescList = new ArrayList<>(targetClassMap.size());for (Map.Entry<TypeElement, List<String[]>> entry : targetClassMap.entrySet()) {String className = entry.getKey().getQualifiedName().toString();System.out.println(className);// 封装成自定义的描述符InjectDesc injectDesc = new InjectDesc();injectDesc.activityName = className;List<String[]> value = entry.getValue();injectDesc.fieldNames = new String[value.size()];injectDesc.fieldTypeNames = new String[value.size()];injectDesc.intentNames = new String[value.size()];for (int i = 0; i < value.size(); i++) {String[] names = value.get(i);injectDesc.fieldNames[i] = names[0];injectDesc.fieldTypeNames[i] = names[1];injectDesc.intentNames[i] = names[2];}injectDescList.add(injectDesc);}return injectDescList;}private void createJavaFile(String pkg, String classShortName, MethodSpec... method) {TypeSpec.Builder builder = TypeSpec.classBuilder(classShortName).addModifiers(Modifier.PUBLIC, Modifier.FINAL);for (MethodSpec spec : method) {builder.addMethod(spec);}TypeSpec clazzType = builder.build();try {JavaFile javaFile = JavaFile.builder(pkg, clazzType).addFileComment(" This codes are generated automatically. Do not modify!").indent("    ").build();// write to filejavaFile.writeTo(processingEnv.getFiler());} catch (IOException e) {e.printStackTrace();}}private TypeName createInjectClassFile(InjectDesc injectDesc) {ClassName activityName = className(injectDesc.activityName);ClassName injectedClass = ClassName.get(activityName.packageName(), activityName.simpleName() + "$Binder");MethodSpec.Builder method = MethodSpec.methodBuilder("bind").addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).addParameter(activityName, "activity");// $T导入作为类,$N导入作为纯值,$S导入作为字符串method.addStatement("$T intent = activity.getIntent()", intentClassName);for (int i = 0; i < injectDesc.fieldNames.length; i++) {TypeName fieldTypeName = typeName(injectDesc.fieldTypeNames[i]);method.addCode("if (intent.hasExtra($S)) {\n", injectDesc.intentNames[i]);method.addCode("\tactivity.$N = ($T) intent.getSerializableExtra($S);\n", injectDesc.fieldNames[i], fieldTypeName, injectDesc.intentNames[i]);method.addCode("}\n");}// 生成最终的XXX$Binder文件createJavaFile(injectedClass.packageName(), injectedClass.simpleName(), method.build());return injectedClass;}private TypeName typeName(String className) {return className(className).withoutAnnotations();}private ClassName className(String className) {// 基础类型描述符if (className.indexOf(".") <= 0) {switch (className) {case "byte":return ClassName.get("java.lang", "Byte");case "short":return ClassName.get("java.lang", "Short");case "int":return ClassName.get("java.lang", "Integer");case "long":return ClassName.get("java.lang", "Long");case "float":return ClassName.get("java.lang", "Float");case "double":return ClassName.get("java.lang", "Double");case "boolean":return ClassName.get("java.lang", "Boolean");case "char":return ClassName.get("java.lang", "Character");default:}}// 手动解析 java.lang.String,分成java.lang的包名和String的类名String packageD = className.substring(0, className.lastIndexOf('.'));String name = className.substring(className.lastIndexOf('.') + 1);return ClassName.get(packageD, name);}private static class InjectDesc {private String activityName;private String[] fieldNames;private String[] fieldTypeNames;private String[] intentNames;@Overridepublic String toString() {return "InjectDesc{" +"activityName='" + activityName + '\'' +", fieldNames=" + Arrays.toString(fieldNames) +", intentNames=" + Arrays.toString(intentNames) +'}';}}
}
复制代码

示例工程

示例工程:customize-annotation

代码生成库:javaPoet 使用这个库可以更方便地生成代码。

参考文章

  • 拆 Jake Wharton 系列之 ButterKnife
  • Android & Java 注解和自定义注解处理器
  • 使用Google开源库AutoService进行组件化开发

安卓自定义注解支持和示例实现相关推荐

  1. Spring Boot 自定义注解支持EL表达式(基于 MethodBasedEvaluationContext 实现)

    自定义注解 自定义 DistributeExceptionHandler 注解,该注解接收一个参数 attachmentId . 该注解用在方法上,使用该注解作为切点,实现标注该注解的方法抛异常后的统 ...

  2. Spring中自定义注解支持SpEl表达式(仅限在AOP中使用)

    大家平时在写代码的时候,安全方面一般都会考虑使用Shiro或者SpringSecurity,他们其中提供了很多注解可以直接使用,很方便,那么今天就来重复造个小轮子,如果不用他们的,自己在项目中如何基于 ...

  3. springboot记录用户访问次数_SpringBoot中自定义注解实现控制器访问次数限制示例...

    今天给大伙介绍一下SpringBoot中如何自定义注解实现控制器访问次数限制. 在Web中最经常发生的就是利用恶性URL访问刷爆服务器之类的攻击,今天我就给大伙介绍一下如何利用自定义注解实现这类攻击的 ...

  4. @value注解取不到值_教学笔记:Java注解及自定义注解示例

    现代的Java编程过程中,会经常需要使用到注解,各种流行框架,比如在使用spring进行应用构建的过程中会使用到非常多的spring注解. 本文简要谈一谈Java注解以及如何去定义自己的注解在程序中进 ...

  5. java 自定义注解实现不同对象之间的拷贝(支持大小写、驼峰转换)

    java 自定义注解实现不同对象之间的拷贝(支持大小写.驼峰转换) 1.需求 要实现两个属性名称完全不同的对象之间的拷贝.具体如下 原对象 /*** 原实体*/ @Data public class ...

  6. Android微信小尾巴,微信骚操作,微信聊天小尾巴这样自定义设置,安卓苹果都支持...

    原标题:微信骚操作,微信聊天小尾巴这样自定义设置,安卓苹果都支持 前段时间,在大家"拍一拍"玩得不亦乐乎的时候,有小伙伴问我微信聊天的小尾巴怎么实现? 当时,给我发来一张效果图,当 ...

  7. 自定义注解判空简单示例

    不说废话,上主代码: import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDes ...

  8. java自定义监听器例子_Java使用自定义注解实现为事件源绑定事件监听器操作示例...

    本文实例讲述了Java使用自定义注解实现为事件源绑定事件监听器操作.分享给大家供大家参考,具体如下: 一 定义注解 import java.lang.annotation.*; import java ...

  9. 谈谈 Java 中自定义注解及使用场景

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:快给我饭吃 www.jianshu.com/p/a7bedc ...

最新文章

  1. Codeforces Round #181 (Div. 2) C. Beautiful Numbers 排列组合 暴力
  2. keras中无法下载 https://s3.amazonaws.com/img-datasets/mnist.npz 解决方法
  3. HTTP 协议的三次握手
  4. 一个简单的Blob存取例子
  5. 冯诺依曼体系结构_极简体系结构之一:冯诺依曼体系结构
  6. 计算机六年级基础知识,六年级计算机试题
  7. IE浏览器无法通过ftp:\\192.168.xxx.xxx连接ftp服务器
  8. springboot定时备份MYSQL_spring boot 定时备份数据库
  9. 万能网卡驱动程序下载,适用于Win7_Win8.x平台
  10. docker中DVWA的安装
  11. 电阻式触摸屏UI设计
  12. mp4视频怎么转换成华为P10手机适配的分辨率
  13. 记录配置Jupyter kernels
  14. 集大计算机与科学的研究生,明天,我是研究生丨感谢集大,我遇见了更好的自己...
  15. Windows 远程桌面复制问题
  16. 次世代角色模型制作:低模制作(三)
  17. 关于bootstrap4 以下 与bootstrap5 的区别
  18. java实现牛牛算法
  19. 思科路由器、交换机的远程登录配置
  20. 树莓派之无屏幕下发现树莓派IP方法

热门文章

  1. 网易博客技巧(表格的高级样式)
  2. 如何入门AI?五大新手项目奉上
  3. 科大讯飞发布第三季度业绩报告:扣非净利润同比减少近9成
  4. 开源了!伯克利今年大热的DeepMimic开源了~
  5. 科大讯飞回应“同传造假”:承认转写人类同传,沟通不足造成误解
  6. 腾讯美的入股的语音AI公司SoundHound,拿什么挑战谷歌亚马逊?
  7. Google大调整:搜索与AI分家独立,原SVP引退,Jeff Dean终掌大权
  8. 比尔·盖茨:我不认为中国AI能弯道超车
  9. synchronized锁机制 之 代码块锁
  10. display:table与本身的table的区别