美文网首页进阶经验总结Android 日常收录
Android自定义注解原理及使用技巧

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

作者: 浪淘沙xud | 来源:发表于2018-04-01 00:23 被阅读454次

    现在分析使用各种第三方库,诸如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);
        }
    }
    

    相关文章

      网友评论

      本文标题:Android自定义注解原理及使用技巧

      本文链接:https://www.haomeiwen.com/subject/ehtocftx.html