栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

安卓自定义注解基础(一)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

安卓自定义注解基础(一)


学习博客:https://juejin.cn/post/6844903437834911757

1.概念篇 什么是注解

先来看看Java文档中的定义

An annotation is a form of metadata, that can be added to Java source code. Classes, methods, variables, parameters and packages may be annotated. Annotations have no direct effect on the operation of the code they annotate.

注解是一种元数据, 可以添加到java代码中. 类、方法、变量、参数、包都可以被注解,注解对注解的代码没有直接影响.

TYPE:类、接口、枚举、注解类型

首先, 明确一点: 注解并没有什么魔法, 之所以产生作用, 是对其解析后做了相应的处理. 注解仅仅只是个标记罢了.

定义注解用的关键字是@interface

元注解

java内置的注解有Override,Deprecated, SuppressWarnings等, 作用相信大家都知道.

现在查看Override注解的源码

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

发现Override注解上面有两个注解, 这就是元注解. 元注解就是用来定义注解的注解.其作用就是定义注解的作用范围, 使用在什么元素上等等, 下面来详细介绍.

元注解共有四种@Retention, @Target, @Inherited, @documented

  • @Retention 保留的范围,默认值为CLASS. 可选值有三种

    • SOURCE, 只在源码中可用

      注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;用于做一些检查性的操作,比如 @Override 和 @SuppressWarnings

    • CLASS, 在源码和字节码中可用

      注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;用于在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife)

    • RUNTIME, 在源码,字节码,运行时均可用

      注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;用于在运行时去动态获取注解信息。这个注解大都会与反射一起使用

  • @Target 可以用来修饰哪些程序元素,如 TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER等,未标注则表示可修饰所有

  • @Inherited 是否可以被继承,默认为false

  • @documented 是否会保存到 Javadoc 文档中

其中, @Retention是定义保留策略, 直接决定了我们用何种方式解析. SOUCE级别的注解是用来标记的, 比如Override, SuppressWarnings. 我们真正使用的类型是CLASS(编译时)和RUNTIME(运行时)

自定义注解

结合例子讲解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface TestAnnotation {
    String value();
    String[] value2() default "value2";
}

元注解的的意义参考上面的讲解, 不再重复, 这里看注解值的写法:

类型 参数名() default 默认值;

其中默认值是可选的, 可以定义, 也可以不定义.

处理运行时注解

Retention的值为RUNTIME时, 注解会保留到运行时, 因此使用反射来解析注解.

使用的注解就是上一步的@TestAnnotation, 解析示例如下:

public class Demo {

    @TestAnnotation("Hello Annotation!")
    private String testAnnotation;

    public static void main(String[] args) {
        try {
            // 获取要解析的类
            Class cls = Class.forName("myAnnotation.Demo");
            // 拿到所有Field
            Field[] declaredFields = cls.getDeclaredFields();
            for(Field field : declaredFields){
                // 获取Field上的注解
                TestAnnotation annotation = field.getAnnotation(TestAnnotation.class);
                if(annotation != null){
                    // 获取注解值
                    String value = annotation.value();
                    System.out.println(value);
                }

            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
解析编译时注解

解析编译时注解需要继承AbstractProcessor类, 实现其抽象方法

public boolean process(Set annotations, RoundEnvironment roundEnv)

该方法返回ture表示该注解已经被处理, 后续不会再有其他处理器处理; 返回false表示仍可被其他处理器处理.

处理示例:

// 指定要解析的注解
@SupportedAnnotationTypes("myAnnotation.TestAnnotation")
// 指定JDK版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class MyAnnotationProcesser extends AbstractProcessor {
    @Override
    public boolean process(Set annotations, RoundEnvironment roundEnv) {
        for (TypeElement te : annotations) {
            for (Element element : roundEnv.getElementsAnnotatedWith(te)) {
                TestAnnotation testAnnotation = element.getAnnotation(TestAnnotation.class);
                // do something
            }
        }
        return true;
    }
}
2.自定义注解之 BindView

可能遇到问题:AutoService注解无法生成meta-INF文件

1.创建java-Libary 1.创建注解接口


2.注解绑定



3.编写生成注解代码
package com.tsp.apt_process;

import com.tsp.apt_annotation.BindView;
import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.JavaFileObject;



@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes("com.tsp.apt_annotation.BindView")
public class BindViewProcessor extends AbstractProcessor {

    private Filer mFilerUtils;       // 文件管理工具类
    private Types mTypesUtils;    // 类型处理工具类
    private Elements mElementsUtils;  // Element处理工具类
    private Map> mToBindMap = new HashMap<>(); //用于记录需要绑定的View的名称和对应的id


    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mFilerUtils = processingEnv.getFiler();
        mTypesUtils = processingEnv.getTypeUtils();
        mElementsUtils = processingEnv.getElementUtils();
    }

    @Override
    public boolean process(Set set, RoundEnvironment roundEnvironment) {
        System.out.println("start process");
        if (set != null && set.size() != 0) {
            Set elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);//获得被BindView注解标记的element

            categories(elements);//对不同的Activity进行分类

            //对不同的Activity生成不同的帮助类
            for (TypeElement typeElement : mToBindMap.keySet()) {
                String code = generateCode(typeElement);    //获取要生成的帮助类中的所有代码
                String helperClassName = typeElement.getQualifiedName() + "$$Autobind"; //构建要生成的帮助类的类名
                //输出帮助类的java文件,在这个例子中就是MainActivity$$Autobind.java文件
                //输出的文件在build->source->apt->目录下
                try {
                    JavaFileObject jfo = mFilerUtils.createSourceFile(helperClassName, typeElement);
                    Writer writer = jfo.openWriter();
                    writer.write(code);
                    writer.flush();
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return true;
        }
        return false;
    }


    private void categories(Set elements) {
        for (Element element : elements) {
            VariableElement variableElement = (VariableElement) element;    //被@BindView标注的应当是变量,这里简单的强制类型转换
            TypeElement enclosingElement = (TypeElement) variableElement.getEnclosingElement(); //获取代表Activity的TypeElement
            Set views = mToBindMap.get(enclosingElement); //views储存着一个Activity中将要绑定的view的信息
            if (views == null) {    //如果views不存在就new一个
                views = new HashSet<>();
                mToBindMap.put(enclosingElement, views);
            }
            BindView bindAnnotation = variableElement.getAnnotation(BindView.class);    //获取到一个变量的注解
            int id = bindAnnotation.value();    //取出注解中的value值,这个值就是这个view要绑定的xml中的id
            views.add(new ViewInfo(variableElement.getSimpleName().toString(), id));    //把要绑定的View的信息存进views中
        }
    }

    
    private String generateCode(TypeElement typeElement) {
        //1.获取需要绑定的view所在的类名称
        String rawClassName = typeElement.getSimpleName().toString();
        //2.获取需要绑定的view所在的包名
        String packageName = mElementsUtils.getPackageOf(typeElement).getQualifiedName().toString();
        //3.生成需要帮助的类名称
        String helperClassName = rawClassName + "$$Autobind";
        //4.生成代码
        StringBuilder sb = new StringBuilder();
        sb.append("package ").append(packageName).append(";n");   //构建定义包的代码
        sb.append("import com.tsp.apt_api.template.IBindHelper;nn");//导包
        sb.append("public class ").append(helperClassName).append(" implements ").append("IBindHelper");   //构建定义帮助类的代码
        sb.append(" {n"); //代码格式,可以忽略
        sb.append("t@Overriden");    //声明这个方法为重写IBindHelper中的方法
        sb.append("tpublic void inject(" + "Object" + " target ) {n");   //构建方法的代码
        for (ViewInfo viewInfo : mToBindMap.get(typeElement)) { //遍历每一个需要绑定的view
            sb.append("tt"); //代码格式,可以忽略
            sb.append(rawClassName + " substitute = " + "(" + rawClassName + ")" + "target;n");    //强制类型转换
            sb.append("tt"); //代码格式,可以忽略
            sb.append("substitute." + viewInfo.viewName).append(" = ");    //构建赋值表达式
            sb.append("substitute.findViewById(" + viewInfo.id + ");n");  //构建赋值表达式
        }
        sb.append("t}n");    //代码格式,可以忽略
        sb.append('n');   //代码格式,可以忽略
        sb.append("}n");  //代码格式,可以忽略
        return sb.toString();
    }


    //要绑定的View的信息载体
    class ViewInfo {
        String viewName;    //view的变量名
        int id; //xml中的id

        public ViewInfo(String viewName, int id) {
            this.viewName = viewName;
            this.id = id;
        }
    }
}


需要生成resources文件夹下的services文件,才能在编译的时候生成对应的java代码

4.编译生成的代码

5.在Activity中应用

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/389760.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号