安卓自定义注解支持和示例实现

栏目: IOS · Android · 发布时间: 6年前

内容简介:编码时使用注解,可以提高编码效率、简化代码增强可读性等优点;使用注解还是代码静态扫描的一部分,促进代码规范。安卓注解使用介绍一文中介绍了JDK/SDK提供的注解和support/ButterKnife等第三方提供的注解库,还有其他的一些库,这些基本已经能够满足需求。support/ButterKnife是应用很广的注解库,它们也是属于“自定义注解”的范畴,只是有因为使用的多了,实际上成为了一个“标准”。本文从“造库”的角度介绍自定义注解的相关支持,并提供一个示例实现。但是,本文不提供自定义注解相关的静态检查

编码时使用注解,可以提高编码效率、简化代码增强可读性等优点;使用注解还是代码静态扫描的一部分,促进代码规范。安卓注解使用介绍一文中介绍了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();

    @Override
    public SourceVersion getSupportedSourceVersion() {
        // 支持java1.7
        return SourceVersion.RELEASE_7;
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        // 只处理 StaticIntentKey 注解
        return Collections.singleton(StaticIntentKey.class.getCanonicalName());
    }

    @Override
    public 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$Binder
            TypeName 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);
            }

            // 被注解的值,如staticName
            String fieldName = element.getSimpleName().toString();
            // 被注解的值的类型,如String,int
            String fieldTypeName = element.asType().toString();
            // 注解本身的值,如key_name
            String 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 file
            javaFile.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;

        @Override
        public String toString() {
            return "InjectDesc{" +
                    "activityName='" + activityName + '\'' +
                    ", fieldNames=" + Arrays.toString(fieldNames) +
                    ", intentNames=" + Arrays.toString(intentNames) +
                    '}';
        }
    }
}
复制代码

以上所述就是小编给大家介绍的《安卓自定义注解支持和示例实现》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Just My Type

Just My Type

Simon Garfield / Profile Books / 2010-10-21 / GBP 14.99

What's your type? Suddenly everyone's obsessed with fonts. Whether you're enraged by Ikea's Verdanagate, want to know what the Beach Boys have in common with easy Jet or why it's okay to like Comic Sa......一起来看看 《Just My Type》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

随机密码生成器
随机密码生成器

多种字符组合密码

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具