現(xiàn)在分析使用各種第三方庫,諸如ARouter钾唬、DBFlow、Dagger2侠驯、ButterKnife等抡秆,自定義注解都是繞不過去的點(diǎn)。所以本文在此重新說叨一下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所修飾的對象范圍桅打,取值:
? 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ù)必須為對應(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如何解析了奈懒。針對TrackName,我們需要做的就是在編譯期生成一個(gè)Java文件蚕涤,自動(dòng)將@TrackName標(biāo)注的類和標(biāo)注的信息記錄下來筐赔。
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("* 頁面路徑映射關(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());
}
}
}
這里我把編譯中生成的類貼出來揖铜,如下:
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
* 頁面路徑映射關(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
接下來就是如何在主app module中使用了茴丰。由于本例annotation 和 processor都寫在同一個(gè)module中,所以使用時(shí)通過如下方式引入即可:
api project(':compiler')
annotationProcessor project(':compiler')
為方便從統(tǒng)一的地方獲取Activity和path的映射信息天吓,創(chuàng)建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();
}
}
這樣,就可以直接通過以下方法實(shí)現(xiàn)調(diào)用
TrackManager.getInstance().getAllPagePath();
TrackManager.getInstance().getTrackNameByClass("MainActivity");
自定義注解使用技巧
從結(jié)構(gòu)上來講龄寞,應(yīng)將Annotation定義和Annotation Processor實(shí)現(xiàn)分別寫到不同的module汰规,方便調(diào)用方按需使用;
Processor生成的代碼應(yīng)該面向接口編程物邑,以示例代碼為例溜哮,面向接口IData生成代碼,將信息寫入IData傳入的Map對象中色解,這就無需關(guān)心生成的代碼結(jié)構(gòu)茂嗓,在實(shí)現(xiàn)中應(yīng)用反射創(chuàng)建IData的實(shí)例并依接口調(diào)用。
關(guān)于第二點(diǎn)科阎,這里簡要的說說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)存的锣笨,其載入的方式就是通過反射來操作的蝌矛,具體實(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);
}
}