安卓自定義註解支援和示例實現

429路發表於2019-05-03

開頭

編碼時使用註解,可以提高編碼效率、簡化程式碼增強可讀性等優點;使用註解還是程式碼靜態掃描的一部分,促進程式碼規範。安卓註解使用介紹一文中介紹了JDK/SDK提供的註解和support/ButterKnife等第三方提供的註解庫,還有其他的一些庫,這些基本已經能夠滿足需求。

support/ButterKnife是應用很廣的註解庫,它們也是屬於“自定義註解”的範疇,只是有因為使用的多了,實際上成為了一個“標準”。

本文從“造庫”的角度介紹自定義註解的相關支援,並提供一個示例實現。但是,本文不提供自定義註解相關的靜態檢查,這需要lint的支援,本文不做介紹,希望後面的文章有機會介紹一下,這裡先佔個坑

安卓自定義註解支援和示例實現

第三方註解庫

引入一個註解庫,以ButterKnife為例:

  • 新增註解庫
implementation 'com.jakewharton:butterknife:8.4.0'
複製程式碼
  • 新增註解處理器
annotationProcessor 'com.jakewharton:butterknife:8.4.0'
複製程式碼

新增了這兩個庫之後,就可以使用這個註解庫了。

如果是library專案】,還需要引入butterknife-gradle-plugin外掛,在安卓註解使用介紹中有具體介紹。

定義註解

所有的註解都預設繼承自java.lang.annotation.Annotation

定義註解時可以宣告0..N個成員,例如下面的定義,可以用default為成員指定預設值;成員名稱可以按照程式語言的變數命名規則任意給定,成員的型別也是有限制的。在使用時需要指定引數名:@StringAnnotation(value = "data"),當成員只有一個且命名為value時,可省略。

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 {
}

複製程式碼
  • 註解處理器基類AbstractProcessor實現自Processor介面,其中init()和getSupportedOptions()在抽象類AbstractProcessor給出了實現,StaticIntentProcessor的主體功能是實現process()方法,完成類生成。
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 使用這個庫可以更方便地生成程式碼。

參考文章

相關文章