Android-注解篇

1.什么是注解

​ 从JDK 5 开始,Java 增加了注解,注解是代码里的特殊标记,这些标记可以在编译、类加载、运
行时被读取,并执行相应的处理。通过使用注解,开发人员可以在不改变原有逻辑的情况下,
在源文件中嵌入一些补充的信息。

2.注解的分类

注解包含:标准注解和元注解。

  • 标准注解

    • @Override:对覆盖超类中的方法进行标记,如果被标记的方法并没有实际覆盖超类中
      的方法,则编译器会发出错误警告。
    • @Deprecated:对不鼓励使用或者已过时的方法添加注解,当编程人员使用这些方法时,
      将会在编译时显示提示信息。
    • @SuppressWarnings:选择性地取消特定代码段中的警告。
    • @SafeVarargs:JDK 7 新增,用来声明使用了可变长度参数的方法,其在与泛型类一起
      使用时不会出现类型安全问题。
  • 元注解: 它用来注解其他注解,从而创建新的注解。

    • @Targe:注解所修饰的对象范围。
      • ElementType.TYPE:能修饰类、接口或枚举类型。
      • ElementType.FIELD:能修饰成员变量。
      • ElementType.METHOD:能修饰方法。
      • ElementType.PARAMETER:能修饰参数。
      • ElementType.CONSTRUCTOR:能修饰构造方法。
      • ElementType.LOCAL_VARIABLE:能修饰局部变量。
      • ElementType.ANNOTATION_TYPE:能修饰注解。
      • ElementType.PACKAGE:能修饰包。
      • ElementType.TYPE_PARAMETER:类型参数声明。
      • ElementType.TYPE_USE:使用类型。
    • @Inherited:表示注解可以被继承。
    • @Documented:表示这个注解应该被 JavaDoc 工具记录。
    • @Retention:用来声明注解的保留策略。
      • RetentionPolicy.SOURCE:源码级注解。注解信息只会保留在.java 源码中,源码在编译
        后,注解信息被丢弃,不会保留在.class 中。
      • RetentionPolicy.CLASS:编译时注解。注解信息会保留在.java 源码以及.class 中。当运
        行 Java 程序时,JVM 会丢弃该注解信息,不会保留在 JVM 中。
      • RetentionPolicy.RUNTIME:运行时注解。当运行 Java 程序时,JVM 也会保留该注解信
        息,可以通过反射获取该注解信息。
    • @Repeatable:JDK 8 新增,允许一个注解在同一声明类型(类、属性或方法)上多次
      使用。

3.注解的使用

使用@interface来申明注解,注解里边可以添加属性,属性默认是public的并且只能是public的,

可以使用default 来设置默认值。

public @interface SoulMaster {
    String name() default "唐三";
    int age() default 24;
}

定义运行时注解

@Retention(RetentionPolicy.RUNTIME)
public @interface SoulMaster {
    String name() default "唐三";
    int age() default 24;
}

如果将@Retention 的保留策略设定为 RetentionPolicy.CLASS,这个注解就是编译时注解

@Retention(RetentionPolicy.CLASS)
public @interface SoulMaster {
    String name() default "唐三";
    int age() default 24;
}

4.注解处理器

如果没有处理注解的工具,那么注解也不会有什么大的作用。对于不同的注解有不同的注解处理器。

  • 针对运行时注解会采用反射机制处理。
  • 针对编译时注解会采用 AbstractProcessor 来处理。

5.运行时注解处理器

下面来通过一个例子演示:获取运行时注解的属性

首先定义一个运行时注解,在方法上使用

@Documented							 //JavaDoc 应该包含这个注解
@Target(ElementType.METHOD)          //在方法上修饰
@Retention(RetentionPolicy.RUNTIME)   //运行时注解
public @interface POST {
    String value();     //带一个参数
}

声明一个类使用这个注解

public class LoginManager {

    @POST(value = "https://login.meishe.com")
    public void login(){

    }
}

测试方法来获取这个注解,并输出注解的value

public class AnnotationTest {

    /**
     * 获取运行时注解
     */
    @Test
    public void testLogin(){
        Method[] declaredMethods = LoginManager.class.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            if (declaredMethod.getName().equals("login")){
                POST annotation = declaredMethod.getAnnotation(POST.class);
                System.out.println("注解的value="+annotation.value());
            }
        }
    }
}

输出:

注解的value=https://login.meishe.com
Process finished with exit code 0

6.编译时注解处理器

编译时在编译的时候也会存在的注解,这类的注解,如果想拿到注解的内容并且执行一定的代码逻辑,就需要借助注解处理器以及poet才行。

定义一个编译时注解,只有一个属性value

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface BindClass {
    String value();
}

定义一个类使用这个注解,这个类没有内容,只是添加了上面定义的注解

@BindClass(value = "注解处理器")
public class BindClassView {
}

准备工作做好了,下面是就是注解处理器的部分了

注解处理器部分需要添加gradle依赖

// 这是Google对注解处理器所提供的服务,目的是为了生成注解配置,并 可以生成java文件代码等
compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'

// 此JavaPoet可以去生成Java文件  以面向对象的思维,生成代码
implementation 'com.squareup:javapoet:1.9.0'

注解处理器源码

/**
 * Google的这个AutoService可以去生成配置文件
 */
@AutoService(Processor.class)
/**
 * 配置版本(Java编译时的版本)
 */
@SupportedSourceVersion(SourceVersion.RELEASE_7)
/**
 * 允许注解处理器  可以去处理的注解,不是所有的注解处理器都可以去处理
 */
@SupportedAnnotationTypes({"com.meishe.annotation.BindClass"})
/**
 *  注解处理器能够接收的参数(例如:如果想把AndroidApp信息传递到这个注解处理器(Java工程),是没法实现的,所以需要通过这个才能接收到)
 */
@SupportedOptions("value")
public class BindClassProcess extends AbstractProcessor {

    /**
     * 注解节点
     */
    Elements mElementTool;

    /**
     * 类信息
     */
    Types mTypesTool;

    /**
     * 专用日志
     */
    Messager mMessager;

    /**
     * 过滤器
     */
    Filer mFiler;

    /**
     * 用于做一些初始化的操作
     * @param processingEnv
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        mElementTool =processingEnv.getElementUtils();
        mTypesTool =processingEnv.getTypeUtils();
        mMessager =processingEnv.getMessager();
        mFiler =processingEnv.getFiler();
        String value = processingEnv.getOptions().get("value");
        mMessager.printMessage(Diagnostic.Kind.NOTE,"init---->从Android App Gradle中传递过来的value="+value);
    }


    
  	/**
     * 只要使用了定义好的注解,可以统一的执行一定的代码逻辑
     * @param annotations 使用注解的节点数组
     * @param roundEnv 封装的环境内容,使用注解的类的包名字 类名等信息都可以在这个上面拿到
     * @return 
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

        for (TypeElement element : annotations) {
            String packageName = mElementTool.getPackageOf(element).getQualifiedName().toString();
            // 获取简单的 类名
            String className = element.getSimpleName().toString();
            mMessager.printMessage(Diagnostic.Kind.NOTE, "packageName---"+packageName+"  className---"+className);
            // 打印一下信息

            // 最终要生成的类名
            String finalResultClassNmae = className + "$BinderViewClass";
            mMessager.printMessage(Diagnostic.Kind.NOTE,"finalResultClassNmae----"+finalResultClassNmae);


            // 开始真正的使用JavaPoet的方式来生成 Java代码文件
            MethodSpec methodSpec = MethodSpec.methodBuilder("main")
                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                    .returns(void.class)
                    .addParameter(String[].class, "args")
                    .addStatement("$T.out.println($S)", System.class, "刘恬希 is my daughter")
                    .build();

            TypeSpec typeSpec = TypeSpec.classBuilder(finalResultClassNmae)
                    .addModifiers(Modifier.PUBLIC)
                    .addMethod(methodSpec)
                    .build();

            JavaFile javaFile=JavaFile.builder(packageName,typeSpec).build();

            try {
                javaFile.writeTo(mFiler);
            } catch (IOException e) {
                e.printStackTrace();
                mMessager.printMessage(Diagnostic.Kind.NOTE,e.getMessage());
            }
            mMessager.printMessage(Diagnostic.Kind.NOTE, "代码注入完成...");
        }

        return true;
    }
}
  • init()方法一般就是进行一些初始化的操作。
  • process 这个方法是关键的部分,用于根据使用注解的类,生成相应的代码部分。使用javaPoet输出代码非常友好方便。

然后进行项目构建,在build->intermediates->javac->debug->classes->包名下面就能看到通过注解处理期生成的class源码。

public class BindClass$BinderViewClass {
  public static void main(String[] args) {
    System.out.println("刘恬希 is my daughter");
  }
}

这个方式进行的源码注入,仅仅是在编译的时候进行的,不影响运行时的运行速度,越来越多的开源框架采用这个方式替代反射的方式。

源码链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值