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

現(xiàn)在分析使用各種第三方庫(kù),諸如ARouter、DBFlowDagger2、ButterKnife等,自定義注解都是繞不過去的點(diǎn)。所以本文在此重新說(shuō)叨一下Android的自定義注解,并分享一些自定義注解使用技巧給大家。

[TOC]

注解概念

注解是代碼里的特殊標(biāo)記,這些標(biāo)記可以在編譯、類加載、運(yùn)行時(shí)被讀取,并執(zhí)行相應(yīng)的處理。這些額外的工作包含但不限于比如用于生成Java doc,比如編譯時(shí)進(jìn)行格式檢查,比如自動(dòng)生成代碼等。

定義注解用的關(guān)鍵字是@interface

JDK定義的元注解

Java提供了四種元注解,專門負(fù)責(zé)新注解的創(chuàng)建工作,即注解其他注解。

@Target

定義了Annotation所修飾的對(duì)象范圍,取值:

? ElementType.CONSTRUCTOR: 用于描述構(gòu)造器

? ElementType.FIELD: 用于描述域

? ElementType.LOCAL_VARIABLE: 用于描述局部變量

? ElementType.METHOD: 用于描述方法

? ElementType.PACKAGE: 用于描述包

? ElementType.PARAMETER: 用于描述參數(shù)

? ElementType.TYPE: 用于描述類、接口(包括注解類型) 或enum聲明

@Retention

定義了該Annotation作用時(shí)機(jī),及生成的文件的保留時(shí)間,取值:

? RetentionPoicy.SOURCE: 注解保留在源代碼中,編譯過程中可見,編譯后會(huì)被編譯器所丟棄,所以用于一些檢查性操作,編譯過程可見性分析等。比如@Override, @SuppressWarnings

? RetentionPoicy.CLASS: 這是默認(rèn)的policy。注解會(huì)被保留在class文件中,但是在運(yùn)行時(shí)期間就不會(huì)識(shí)別這個(gè)注解。用于生成一些輔助代碼,輔助代碼生成之后,該注解的任務(wù)就結(jié)束了。如ARouter、ButterKnife等

? RetentionPoicy.RUNTIME: 注解會(huì)被保留在class文件中,同時(shí)運(yùn)行時(shí)期間也會(huì)被識(shí)別,和CLASS的差別也在此。所以可以在運(yùn)行時(shí)使用反射機(jī)制獲取注解信息。比如@Deprecated

@Inherited

是否可以被繼承,默認(rèn)為false。即子類自動(dòng)擁有和父類一樣的注解。

@Documented

是否會(huì)保存到 Javadoc 文檔中。

Android SDK內(nèi)置的注解

Android SDK 內(nèi)置的注解都在包c(diǎn)om.android.support:support-annotations里,如:

? 資源引用限制類:用于限制參數(shù)必須為對(duì)應(yīng)的資源類型

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

? 線程執(zhí)行限制類:用于限制方法或者類必須在指定的線程執(zhí)行

@AnyThread @BinderThread @MainThread @UiThread @WorkerThread

? 參數(shù)為空性限制類:用于限制參數(shù)是否可以為空

@NonNull @Nullable

? 類型范圍限制類:用于限制標(biāo)注值的值范圍

@FloatRang @IntRange

? 類型定義類:用于限制定義的注解的取值集合

@IntDef @StringDef

? 其他的功能性注解:

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

自定義注解實(shí)例

假定要實(shí)現(xiàn)這樣的注解功能:使用注解在Android Activity上指定path,然后根據(jù)類名獲取相應(yīng)的path并實(shí)現(xiàn)跳轉(zhuǎn)。

具體實(shí)現(xiàn)步驟如下

1) 創(chuàng)建Processor Module

File -- New Module -- Choose Java Library

確保該processor module package命名為 {base}.annotationprocessor

本例中module名定位為:compiler

2) 設(shè)置Processor Module Build Gradle

設(shè)置Java編譯版本,如主app和processor module都采用java 1.7,則設(shè)置如下:


// 主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) 創(chuàng)建Annotation


@Retention(RetentionPolicy.CLASS)

@Target(ElementType.TYPE)

public @interface TrackName {

 String name() default "";

}

4) 創(chuàng)建自定義Processor

這一步是最重要的一個(gè)步驟,自定義注解之所以能實(shí)現(xiàn)相應(yīng)的功能,就是看自定義Processor如何解析了。針對(duì)TrackName,我們需要做的就是在編譯期生成一個(gè)Java文件,自動(dòng)將@TrackName標(biāo)注的類和標(biāo)注的信息記錄下來(lái)。

CustomProcessor必須繼承AbstractProcessor,并且需要使用Java提供的注解標(biāo)注:


@AutoService(Processor.class)

@SupportedSourceVersion(SourceVersion.RELEASE_7)

@SupportedAnnotationTypes({ANNOTATION_TYPE_TRACKNAME})

先定義一個(gè)通用接口


public interface IData {

 /**

 * 載入數(shù)據(jù)

 */

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

}

TrackNameProcessor的實(shí)現(xiàn)如下:

@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標(biāo)注的元素
                // 打印
                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, "映射關(guān)系:" + e.getSimpleName().toString() + "-" + name);
            }
        }

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

            // 創(chuàng)建Java文件
            JavaFileObject f = processingEnv.getFiler().createSourceFile(genarateClassName);
            // 在控制臺(tái)輸出文件路徑
            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("* 頁(yè)面路徑映射關(guān)系表");
                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());
        }
    }
}

這里我把編譯中生成的類貼出來(lái),如下:

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
* 頁(yè)面路徑映射關(guān)系表
*/
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

接下來(lái)就是如何在主app module中使用了。由于本例annotation 和 processor都寫在同一個(gè)module中,所以使用時(shí)通過如下方式引入即可:


api project(':compiler')

annotationProcessor project(':compiler')

為方便從統(tǒng)一的地方獲取Activity和path的映射信息,創(chuàng)建TrackManager單例來(lái)獲?。?/p>

public interface TrackInfoProvide {

    /**
     * 通過類名查找足跡定義信息
     *
     * @param className
     * @return
     */
    String getTrackNameByClass(String className);

    /**
     * 將所有路徑信息打印出來(lái)
     */
    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("頁(yè)面:" + entry.getKey());
            builder.append("\t");
            builder.append("路徑:" + entry.getValue());
            builder.append("\n");
        }
        return builder.toString();
    }
}

這樣,就可以直接通過以下方法實(shí)現(xiàn)調(diào)用


TrackManager.getInstance().getAllPagePath();

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

自定義注解使用技巧

  1. 從結(jié)構(gòu)上來(lái)講,應(yīng)將Annotation定義和Annotation Processor實(shí)現(xiàn)分別寫到不同的module,方便調(diào)用方按需使用;

  2. Processor生成的代碼應(yīng)該面向接口編程,以示例代碼為例,面向接口IData生成代碼,將信息寫入IData傳入的Map對(duì)象中,這就無(wú)需關(guān)心生成的代碼結(jié)構(gòu),在實(shí)現(xiàn)中應(yīng)用反射創(chuàng)建IData的實(shí)例并依接口調(diào)用。

關(guān)于第二點(diǎn),這里簡(jiǎn)要的說(shuō)說(shuō)ARouter的實(shí)現(xiàn):

假設(shè)loginModule中的MainActivity使用了ARouter,其注解為@Route(path = "/loginModule/main"),則ARouter編譯時(shí)會(huì)生成文件

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在初始化的時(shí)候需要將這個(gè)映射關(guān)系載入內(nèi)存的,其載入的方式就是通過反射來(lái)操作的,具體實(shí)現(xiàn)在源碼中的類 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);
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容