編碼時使用註解,能夠提升編碼效率、簡化代碼加強可讀性等優勢;使用註解仍是代碼靜態掃描的一部分,促進代碼規範。安卓註解使用介紹一文中介紹了JDK/SDK提供的註解和support/ButterKnife等第三方提供的註解庫,還有其餘的一些庫,這些基本已經可以知足需求。java
support/ButterKnife是應用很廣的註解庫,它們也是屬於「自定義註解」的範疇,只是有由於使用的多了,實際上成爲了一個「標準」。android
本文從「造庫」的角度介紹自定義註解的相關支持,並提供一個示例實現。可是,本文不提供自定義註解相關的靜態檢查,這須要lint的支持,本文不作介紹,但願後面的文章有機會介紹一下,這裏先佔個坑。git
引入一個註解庫,以ButterKnife爲例:github
implementation 'com.jakewharton:butterknife:8.4.0'
複製代碼
annotationProcessor 'com.jakewharton:butterknife:8.4.0'
複製代碼
添加了這兩個庫以後,就能夠使用這個註解庫了。數組
【若是是library項目】,還須要引入butterknife-gradle-plugin插件,在安卓註解使用介紹中有具體介紹。緩存
全部的註解都默認繼承自java.lang.annotation.Annotationapp
定義註解時能夠聲明0..N個成員,例以下面的定義,能夠用default爲成員指定默認值;成員名稱能夠按照程序語言的變量命名規則任意給定,成員的類型也是有限制的。在使用時須要指定參數名:@StringAnnotation(value = "data"),當成員只有一個且命名爲value時,可省略。ide
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 {
}
複製代碼
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) +
'}';
}
}
}
複製代碼
示例工程:customize-annotation
代碼生成庫:javaPoet 使用這個庫能夠更方便地生成代碼。