Android中注解的使用教程

導(dǎo)言

平常開發(fā)中經(jīng)常會(huì)使用到ButterKnife、EventBus這些有使用注解的第三方庫,直觀來看作用就是“明顯”,通過一個(gè)標(biāo)注說明當(dāng)前方法/屬性的意義,從而使得代碼的可讀性變強(qiáng),是一種不錯(cuò)的開發(fā)手段

注解基礎(chǔ)

1.定義一個(gè)注解

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface PageBackground {
}

@Retention:

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

SOURCE:當(dāng)前注解僅僅是聲明,只會(huì)在源代碼中留存,編譯的時(shí)候?qū)?huì)被刪除,這意味這無法在編譯期間和運(yùn)行時(shí)通過反射獲取到當(dāng)前注解的一些信息
CLASS:注解會(huì)在.class字節(jié)碼中,但是不需要由虛擬機(jī)在運(yùn)行時(shí)保留(注意實(shí)測(cè)通過華為P9,是可以在運(yùn)行時(shí)反射獲取的),這個(gè)也是注解的默認(rèn)行為
RUNTIME:注解會(huì)被保留到運(yùn)行時(shí),那么可以通過反射獲取

一般來說,推薦的使用模式為
SOURCE:單純閱讀使用
CLASS:單純編譯時(shí)使用
RUNTIME:運(yùn)行時(shí)需要反射使用

@Target:

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

有點(diǎn)多,看幾個(gè)平常可能會(huì)使用的
TYPE:作用在類/接口聲明上

@RequiresApi
public class AActivity extends Activity{
}

ANNOTATION_TYPE:作用在注解聲明上的,比方說TARGET自己就是這種類型的注解

@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

FIELD:作用在屬性上面,比方說類中的某個(gè)參數(shù)

@SerializedName("sss")
private Button loginButton;

METHOD:作用在類中的方法上面

@PageBackground
public Map<String,String> demo(){
    return null;
}

編譯期處理注解

1.創(chuàng)建一個(gè)android的module,用于定義注解等信息


用于定義一些注解類

2.創(chuàng)建一個(gè)java的module(需引入上述注解的module)
3.在java的module下定義類繼承AbstractProcessor

@SupportedAnnotationTypes("fanjh.mine.buriedpointannotation.PageBackground")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class AppBackgroundProcessor extends AbstractProcessor {
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        messager = processingEnv.getMessager();
        filer = processingEnv.getFiler();
        types = processingEnv.getTypeUtils();
    }
    
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        return false;
    }
}

4.在java的module下面新建一個(gè)文件,用于注冊(cè)AbstractProcessor


文件聲明(需要保證目錄路徑和文件名一致)

AbstractProcessor聲明

5.在主項(xiàng)目的gradle中引入當(dāng)前module


依賴導(dǎo)入

到這一步配置就已經(jīng)完成,接下來要進(jìn)入到具體的AbstractProcessor的編寫
6.用途的思考:
編譯的時(shí)候如何使用注解?參考一些現(xiàn)成的庫

EventBus:在沒有改成@Subscribe注解之前,通過的是反射OnEvent起頭類似的方法名(不同線程不一樣)來處理,要去記各種方法名,而且還要求不能重名,給人的感覺就是太過死板。通過注解之后,可以方便的標(biāo)記運(yùn)行線程,方法名可以自定義,而且閱讀起來也方便很多,直觀很多
ButterKnife:直接通過注解替代大量的findViewByID等手動(dòng)的重復(fù)代碼,如果結(jié)合插件的話就更加方便了
7.實(shí)際操作:
上面其實(shí)提到了,通過繼承AbstractProcessor可以完成編譯期的操作,如果想要替代大量的人工操作,那么首先需要有一個(gè)【服務(wù)類】,也就是說編譯期的操作應(yīng)該是生成一些新的類

compile 'com.squareup:javapoet:1.10.0'

這里推薦square的javapoet庫,通過Builder模式可以快速寫出一個(gè)類,封裝了大量的拼接操作,使用起來非常方便
看一個(gè)簡單的例子:

/**
 * @author fanjh
 * @date 2018/2/9 10:23
 * @description
 * @note SupportedAnnotationTypes指定當(dāng)前獲取到的注解,相當(dāng)于一個(gè)過濾器
 **/
@SupportedAnnotationTypes("fanjh.mine.buriedpointannotation.PageBackground")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class AppBackgroundProcessor extends AbstractProcessor {
    private Messager messager;
    private Filer filer;

    /**
     * 用于初始化一些工具
     * 后續(xù)可以使用這些工具進(jìn)行操作
     * @param processingEnv
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        //用于打印日志
        messager = processingEnv.getMessager();
        //用于寫出類文件
        filer = processingEnv.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        //可以通過當(dāng)前方法獲取指定的注解
        Set<Element> set = (Set<Element>) roundEnv.getElementsAnnotatedWith(PageBackground.class);
        if(null == set){
            return false;
        }
        Map<String,String> caches = new HashMap<>();
        //遍歷當(dāng)前代碼中所有的指定注解
        for (Element element : set) {
            //獲取當(dāng)前注解的作用對(duì)象
            if (element.getKind() == ElementKind.METHOD) {
                //這里實(shí)際上就是把有當(dāng)前注解的類名和方法名進(jìn)行緩存
                ExecutableElement executableElement = (ExecutableElement) element;
                TypeElement typeElement = (TypeElement) executableElement.getEnclosingElement();

                String className = typeElement.getQualifiedName().toString();
                log(className);

                if(executableElement.getParameters().size() > 0){
                    throw new IllegalArgumentException("當(dāng)前注解標(biāo)記方法不能有參數(shù)!");
                }

                TypeMirror typeMirror = executableElement.getReturnType();
                TypeKind typeKind = typeMirror.getKind();
                if(TypeKind.DECLARED != typeKind || !"java.util.Map<java.lang.String,java.lang.String>".equals(typeMirror.toString())){
                    throw new IllegalArgumentException("當(dāng)前注解標(biāo)記方法返回值類型有誤!");
                }

                String methodName = executableElement.getSimpleName().toString();

                caches.put(className,methodName);
            }

        }
        //當(dāng)前有指定的注解
        if(caches.size() > 0) {
            //通過javapoet生成指定的類
            
            FieldSpec fieldSpec = FieldSpec.builder(HashMap.class, "cache",
                    Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC).
                    initializer("new HashMap<String,String>()").
                    build();

            CodeBlock.Builder staticBuilder = CodeBlock.builder();

            for(Map.Entry<String,String> entry:caches.entrySet()){
                staticBuilder.addStatement("cache.put($S,$S)", entry.getKey(), entry.getValue());
            }

            MethodSpec methodSpec = MethodSpec.methodBuilder("getMethod").
                    addModifiers(Modifier.PUBLIC).
                    addParameter(String.class,"className").
                    returns(String.class).
                    addStatement("return (String)cache.get(className)").
                    build();

            TypeSpec typeSpec = TypeSpec.classBuilder(Const.APP_BACKGROUND_CLASSNAME).
                    addModifiers(Modifier.PUBLIC).
                    addStaticBlock(staticBuilder.build()).
                    addSuperinterface(IBuriedPointApt.class).
                    addField(fieldSpec).
                    addMethod(methodSpec).
                    build();

            JavaFile javaFile = JavaFile.builder(Const.PACKAGE_NAME, typeSpec).build();
            try {
                javaFile.writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        log("-----------------------------");
        return false;
    }

    /**
     * 打印日志
     * @param content
     */
    private void log(String content){
        messager.printMessage(Diagnostic.Kind.NOTE, content);
    }

}

當(dāng)代碼寫完之后,運(yùn)行程序,可以在指定的位置看到編譯期生成的類


查看編譯期生成的類

編譯期生成的類

思路很簡單:
1.定義一個(gè)接口,后期通過反射可以轉(zhuǎn)型為接口
2.通過注解生成一個(gè)緩存
3.運(yùn)行時(shí)通過接口的方法從緩存中獲取想要的數(shù)據(jù)即可
4.這個(gè)例子就是通過反射指定的類來調(diào)用指定的方法
比方說

public class AnnotationFinder {
    private IBuriedPointApt iBuriedPointApt;
    private boolean hasApt = true;
    private String className;
    private Class an;

    public AnnotationFinder(String className,Class an) {
        this.className = className;
        this.an = an;
    }

    /**
     * 編譯期已經(jīng)生成指定的索引
     * 當(dāng)前通過索引來獲取參數(shù)
     * @param activity 當(dāng)前活動(dòng)
     * @return 指定的參數(shù)
     */
    private HashMap<String,String> getAptParams(Activity activity){
        String methodName = iBuriedPointApt.getMethod(activity.getClass().getCanonicalName());
        if(null == methodName){
            return null;
        }
        Method method = null;
        try {
            method = activity.getClass().getMethod(methodName);
            return (HashMap<String, String>) method.invoke(activity);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 從緩存中獲取,這個(gè)對(duì)應(yīng)編譯期生成的類
     * @param activity 當(dāng)前活動(dòng)
     * @return 指定的參數(shù)
     */
    private HashMap<String,String> getParamsFromIndex(Activity activity){
        //當(dāng)前編譯期沒有生成對(duì)應(yīng)的類
        if(!hasApt){
            return null;
        }
        //嘗試直接使用之前已經(jīng)反射出指定的輔助類
        if(null != iBuriedPointApt){
            return getAptParams(activity);
        }
        try {
            //通過之前定義的規(guī)則來反射指定的類
            Class cls = Class.forName(Const.PACKAGE_NAME + "." + className);
            iBuriedPointApt = (IBuriedPointApt) cls.newInstance();
            return getAptParams(activity);
            //出現(xiàn)任何的異常都不允許再使用索引了
        } catch (ClassNotFoundException e) {
            hasApt = false;
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            hasApt = false;
            e.printStackTrace();
        } catch (InstantiationException e) {
            hasApt = false;
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 通過反射來獲取參數(shù)
     * @param activity 當(dāng)前活動(dòng)
     * @param c 注解
     * @return 指定的參數(shù)
     */
    private HashMap<String,String> getParamsFromReflect(Activity activity,Class c){
        HashMap<String,String> params = new HashMap<>();
        //獲取當(dāng)前類中定義的所有方法
        Method[] methods = activity.getClass().getDeclaredMethods();
        for(Method method:methods){
            //嘗試從當(dāng)前方法獲取指定的注解
            Annotation annotation = method.getAnnotation(c);
            if(null != annotation){
                try {
                    params = (HashMap<String, String>) method.invoke(activity);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
                break;
            }
        }
        return params;
    }

    public HashMap<String,String> getParams(Activity activity,boolean useIndex){
        //是否使用索引,實(shí)際上就是緩存
        if(useIndex){
            return getParamsFromIndex(activity);
        }else{
            return getParamsFromReflect(activity,an);
        }
    }

}

實(shí)際上這里就是為了一個(gè)簡單的功能

    @PageBackground
    public Map<String,String> background(){
        HashMap<String,String> params = new HashMap<>();
        params.put("type","report");
        params.put("background",getClass().getSimpleName());
        return params;
    }

通過指定的注解,來返回想要的參數(shù),這里的場(chǎng)景是埋點(diǎn)的時(shí)候,當(dāng)App進(jìn)入后臺(tái)的時(shí)候上報(bào)當(dāng)前頁面的一些數(shù)據(jù)

結(jié)語

通過注解的使用,確實(shí)可以方便一些特定場(chǎng)景的使用,更加成熟的應(yīng)用,可以去看EventBus、ButterKnife等庫的源碼

最后編輯于
?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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