Android自定义注解原理及使用技巧

现在分析使用各种第三方库,诸如ARouterDBFlowDagger2ButterKnife等,自定义注解都是绕不过去的点。所以本文在此重新说叨一下Android的自定义注解,并分享一些自定义注解使用技巧给大家。

[TOC]

注解概念

注解是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。这些额外的工作包含但不限于比如用于生成Java doc,比如编译时进行格式检查,比如自动生成代码等。

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

JDK定义的元注解

Java提供了四种元注解,专门负责新注解的创建工作,即注解其他注解。

@Target

定义了Annotation所修饰的对象范围,取值:

• ElementType.CONSTRUCTOR: 用于描述构造器

• ElementType.FIELD: 用于描述域

• ElementType.LOCAL_VARIABLE: 用于描述局部变量

• ElementType.METHOD: 用于描述方法

• ElementType.PACKAGE: 用于描述包

• ElementType.PARAMETER: 用于描述参数

• ElementType.TYPE: 用于描述类、接口(包括注解类型) 或enum声明

@Retention

定义了该Annotation作用时机,及生成的文件的保留时间,取值:

• RetentionPoicy.SOURCE: 注解保留在源代码中,编译过程中可见,编译后会被编译器所丢弃,所以用于一些检查性操作,编译过程可见性分析等。比如@Override, @SuppressWarnings

• RetentionPoicy.CLASS: 这是默认的policy。注解会被保留在class文件中,但是在运行时期间就不会识别这个注解。用于生成一些辅助代码,辅助代码生成之后,该注解的任务就结束了。如ARouter、ButterKnife等

• RetentionPoicy.RUNTIME: 注解会被保留在class文件中,同时运行时期间也会被识别,和CLASS的差别也在此。所以可以在运行时使用反射机制获取注解信息。比如@Deprecated

@Inherited

是否可以被继承,默认为false。即子类自动拥有和父类一样的注解。

@Documented

是否会保存到 Javadoc 文档中。

Android SDK内置的注解

Android SDK 内置的注解都在包com.android.support:support-annotations里,如:

• 资源引用限制类:用于限制参数必须为对应的资源类型

@AnimRes @AnyRes @ArrayRes @AttrRes @BoolRes @ColorRes等

• 线程执行限制类:用于限制方法或者类必须在指定的线程执行

@AnyThread @BinderThread @MainThread @UiThread @WorkerThread

• 参数为空性限制类:用于限制参数是否可以为空

@NonNull @Nullable

• 类型范围限制类:用于限制标注值的值范围

@FloatRang @IntRange

• 类型定义类:用于限制定义的注解的取值集合

@IntDef @StringDef

• 其他的功能性注解:

@CallSuper @CheckResult @ColorInt @Dimension @Keep @Px @RequiresApi @RequiresPermission @RestrictTo @Size @VisibleForTesting

自定义注解实例

假定要实现这样的注解功能:使用注解在Android Activity上指定path,然后根据类名获取相应的path并实现跳转。

具体实现步骤如下

1) 创建Processor Module

File — New Module — Choose Java Library

确保该processor module package命名为 {base}.annotationprocessor

本例中module名定位为:compiler

2) 设置Processor Module Build Gradle

设置Java编译版本,如主app和processor module都采用java 1.7,则设置如下:


// 主app module

compileOptions {

 sourceCompatibility JavaVersion.VERSION_1_7

 targetCompatibility JavaVersion.VERSION_1_7

}


// processor module

sourceCompatibility = "1.7"

targetCompatibility = "1.7"

添加谷歌Auto-Service支持


dependencies {

 implementation fileTree(dir: 'libs', include: ['*.jar'])

 implementation 'com.google.auto.service:auto-service:1.0-rc2'

}

3) 创建Annotation


@Retention(RetentionPolicy.CLASS)

@Target(ElementType.TYPE)

public @interface TrackName {

 String name() default "";

}

4) 创建自定义Processor

这一步是最重要的一个步骤,自定义注解之所以能实现相应的功能,就是看自定义Processor如何解析了。针对TrackName,我们需要做的就是在编译期生成一个Java文件,自动将@TrackName标注的类和标注的信息记录下来。

CustomProcessor必须继承AbstractProcessor,并且需要使用Java提供的注解标注:


@AutoService(Processor.class)

@SupportedSourceVersion(SourceVersion.RELEASE_7)

@SupportedAnnotationTypes({ANNOTATION_TYPE_TRACKNAME})

先定义一个通用接口


public interface IData {

 /**

 * 载入数据

 */

 void loadInto(Map<String, String> map);

}

TrackNameProcessor的实现如下:

@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes({ANNOTATION_TYPE_TRACKNAME})
public class TrackNameProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (set != null && !set.isEmpty()) {
            generateJavaClassFile(set, roundEnvironment);
            return true;
        }
        return false;
    }

    // 生成Java源文件
    private void generateJavaClassFile(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // set of track
        Map<String, String> trackMap = new HashMap<>();
        // print on gradle console
        Messager messager = processingEnv.getMessager();

        // 遍历annotations获取annotation类型 @SupportedAnnotationTypes
        for (TypeElement te : annotations) {
            for (Element e : roundEnv.getElementsAnnotatedWith(te)) { // 获取所有被annotation标注的元素
                // 打印
                messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + e.toString());
                messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + e.getSimpleName());
                messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + e.getEnclosingElement().toString());
                // 获取注解
                TrackName annotation = e.getAnnotation(TrackName.class);
                // 获取名称
                String name = "".equals(annotation.name()) ? e.getSimpleName().toString() : annotation.name();
                // 保存映射信息
                trackMap.put(e.getSimpleName().toString(), name);
                messager.printMessage(Diagnostic.Kind.NOTE, "映射关系:" + e.getSimpleName().toString() + "-" + name);
            }
        }

        try {
            // 生成的包名
            String genaratePackageName = "com.xud.annotationprocessor";
            // 生成的类名
            String genarateClassName = "TrackManager$Helper";

            // 创建Java文件
            JavaFileObject f = processingEnv.getFiler().createSourceFile(genarateClassName);
            // 在控制台输出文件路径
            messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + f.toUri());
            Writer w = f.openWriter();
            try {
                PrintWriter pw = new PrintWriter(w);
                pw.println("package " + genaratePackageName + ";\n");
                pw.println("import java.util.Map;");
                pw.println("import com.xud.annotationprocessor.IData;\n");
                pw.println("/**");
                pw.println("* this file is auto-create by compiler,please don`t edit it");
                pw.println("* 页面路径映射关系表");
                pw.println("*/");
                pw.println("public class " + genarateClassName + " implements IData {");

                pw.println("\n    @Override");
                pw.println("    public void loadInto(Map<String, String> map) {");
                Iterator<String> keys = trackMap.keySet().iterator();
                while (keys.hasNext()) {
                    String key = keys.next();
                    String value = trackMap.get(key);
                    pw.println("        map.put(\"" + key + "\",\"" + value + "\");");
                }
                pw.println("    }");
                pw.println("}");
                pw.flush();
            } finally {
                w.close();
            }
        } catch (IOException x) {
            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, x.toString());
        }
    }
}

这里我把编译中生成的类贴出来,如下:

package com.xud.annotationprocessor;

import java.util.Map;
import com.xud.annotationprocessor.IData;

/**
* this file is auto-create by compiler,please don`t edit it
* 页面路径映射关系表
*/
public class TrackManager$Helper implements IData {

    @Override
    public void loadInto(Map<String, String> map) {
        map.put("RxJavaActivity","/page/rxJava");
        map.put("CustomViewActivity","/page/customView");
        map.put("CoordinatorActivity","/page/coordinator");
        map.put("BActivity","/page/b");
        map.put("MainActivity","/main");
    }
}

5) Use

接下来就是如何在主app module中使用了。由于本例annotation 和 processor都写在同一个module中,所以使用时通过如下方式引入即可:


api project(':compiler')

annotationProcessor project(':compiler')

为方便从统一的地方获取Activity和path的映射信息,创建TrackManager单例来获取:

public interface TrackInfoProvide {

    /**
     * 通过类名查找足迹定义信息
     *
     * @param className
     * @return
     */
    String getTrackNameByClass(String className);

    /**
     * 将所有路径信息打印出来
     */
    String getAllPagePath();

}

public class TrackManager implements TrackInfoProvide {

    private Map<String, String> trackNameMap;

    private static TrackManager instance;

    public static TrackManager getInstance() {
        if (instance == null) {
            instance = new TrackManager();
        }
        return instance;
    }


    private TrackManager() {
        trackNameMap = new HashMap<String,String>();
        String classFullName = "com.xud.annotationprocessor.TrackManager$Helper";
        try {
            Class<?> clazz = Class.forName(classFullName);
            IData data = (IData)clazz.newInstance();
            data.loadInto(trackNameMap);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public String getTrackNameByClass(String className) {
        String output = className;
        if(trackNameMap != null && !trackNameMap.isEmpty()) {
            String value = trackNameMap.get(className);
            output = (value == null?output:value);
        }
        return output;
    }

    @Override
    public String getAllPagePath() {
        if (trackNameMap.isEmpty()) {
            return "";
        }
        StringBuilder builder = new StringBuilder();
        for (Map.Entry<String, String> entry : trackNameMap.entrySet()) {
            builder.append("页面:" + entry.getKey());
            builder.append("\t");
            builder.append("路径:" + entry.getValue());
            builder.append("\n");
        }
        return builder.toString();
    }
}

这样,就可以直接通过以下方法实现调用


TrackManager.getInstance().getAllPagePath();

TrackManager.getInstance().getTrackNameByClass("MainActivity");

自定义注解使用技巧

  1. 从结构上来讲,应将Annotation定义和Annotation Processor实现分别写到不同的module,方便调用方按需使用;

  2. Processor生成的代码应该面向接口编程,以示例代码为例,面向接口IData生成代码,将信息写入IData传入的Map对象中,这就无需关心生成的代码结构,在实现中应用反射创建IData的实例并依接口调用。

关于第二点,这里简要的说说ARouter的实现:

假设loginModule中的MainActivity使用了ARouter,其注解为@Route(path = “/loginModule/main”),则ARouter编译时会生成文件

ARouter$$Root$$loginModule
“ARouter$$Group$$loginModule”

public class ARouter$$Root$$loginModule implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("loginModule", ARouter$$Group$$loginModule.class);
  }
}

public class ARouter$$Group$$loginModule implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/loginModule/main", RouteMeta.build(RouteType.ACTIVITY, MainActivity.class, "/loginmodule/main", "loginmodule", null, -1, -2147483648));
  }
}

public interface IRouteRoot {

    /**
     * Load routes to input
     * @param routes input
     */
    void loadInto(Map<String, Class<? extends IRouteGroup>> routes);
}

public interface IRouteGroup {
    /**
     * Fill the atlas with routes in group.
     */
    void loadInto(Map<String, RouteMeta> atlas);
}

ARouter在初始化的时候需要将这个映射关系载入内存的,其载入的方式就是通过反射来操作的,具体实现在源码中的类 LogisticsCenter,其中有一段代码如下,有兴趣的读者可以详阅源码:

// These class was generate by arouter-compiler.
List<String> classFileNames = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);

for (String className : classFileNames) {
    if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
        // This one of root elements, load root.
        ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
        // Load interceptorMeta
        ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
        // Load providerIndex
        ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
    }
}
    原文作者:浪淘沙xud
    原文地址: https://www.jianshu.com/p/f8489db1926b
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞