Android進(jìn)階之Annotation(注解)的使用

注解支持的版本

Android support library從19.1版本開始就引入了一個(gè)新的注解庫。

添加支持注解庫依賴項(xiàng)

要在您的項(xiàng)目中啟用注解,請(qǐng)向您的庫或應(yīng)用添加 support-annotations 依賴項(xiàng)。
支持注解庫是Android 支持庫的一部分。要向您的項(xiàng)目添加注解,您必須下載支持存儲(chǔ)庫并向 build.gradle文件中添加 support-annotations依賴項(xiàng)。

添加支持注解庫依賴項(xiàng)

1,打開 SDK 管理器,方法是點(diǎn)擊工具欄中的 SDK Manager或者選擇 Tools > Android > SDK Manager

SDK Manager

2,點(diǎn)擊 SDK Tools 標(biāo)簽;

SDK Tools

3,展開 Support Repository 并選中 Android Support Repository 復(fù)選框;
4,點(diǎn)擊 OK;
5,將以下代碼行添加到 build.gradle 文件的 dependencies 塊中,向您的項(xiàng)目添加 support-annotations 依賴項(xiàng):

dependencies { compile 'com.android.support:support-annotations:24.2.0' } 

6,Sync Now;

PS:

如果您使用appcompat庫,則無需添加support-annotations依賴項(xiàng)。因?yàn)?appcompat庫已經(jīng)依賴注解庫。

那有關(guān)注解的環(huán)境配置就已經(jīng)構(gòu)建好了,那接下來就是創(chuàng)建自己的注解的時(shí)候了。

介紹注解

在實(shí)現(xiàn)自己的注解時(shí),我們需要來了解其中兩個(gè)注解。

@Retention(RetentionPolicy.*)

Retention,保存策略。傳入的RetentionPolicy參數(shù)有如下三種:

package java.lang.annotation;

/**
 * Annotation retention policy.  The constants of this enumerated type
 * describe the various policies for retaining annotations.  They are used
 * in conjunction with the {@link Retention} meta-annotation type to specify
 * how long annotations are to be retained.
 *
 * @author  Joshua Bloch
 * @since 1.5
 */
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
}

PS:

1,SOURCR:只在*.java源文件的時(shí)候有效;
2,CLASS:只在*.java或者*.class中的文件有效,但是在運(yùn)行環(huán)境無效;
3,RUNTIME:包含以上兩種,并且運(yùn)行時(shí)也會(huì)有效果,一般我們都會(huì)選用該參數(shù)。

@Target(ElementType.*)

Target,注解的目標(biāo)類型。傳入的ElementType類型有如下幾種:

package java.lang.annotation;

/**
 * A program element type.  The constants of this enumerated type
 * provide a simple classification of the declared elements in a
 * Java program.
 *
 * <p>These constants are used with the {@link Target} meta-annotation type
 * to specify where it is legal to use an annotation type.
 *
 * @author  Joshua Bloch
 * @since 1.5
 */
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
     * @hide 1.8
     */
    TYPE_PARAMETER,

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

PS:

1,TYPE:作用于接口、類、枚舉、注解;
2,FIELD:作用于成員變量(字段、枚舉的常量);
3,METHOD:作用于方法;
4,PARAMETER:作用于方法的參數(shù);
5,CONSTRUCTOR:作用于構(gòu)造函數(shù);
6,LOCAL_VARIABLE:作用于局部變量;
7,ANNOTATION_TYPE:作用于Annotation。例如如下代碼:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}

8,PACKAGE:作用于包名;
9,TYPE_PARAMETER:java8新增,但無法訪問到;
10,TYPE_USE:java8新增,但無法訪問到;

下面做個(gè)DEMO,用注解來做以下兩件事:

1,查找控件;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 根據(jù)ID查看View的注解 2017/4/14 08:54
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FindViewById {

    // 使用value命名,則使用的時(shí)候可以忽略,否則使用時(shí)就得把參數(shù)名加上 2017/4/14 08:57
    int value();
}

2,設(shè)置點(diǎn)擊事件;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 設(shè)置點(diǎn)擊事件 2017/4/14 09:20
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SetOnClickListener {

    int id();

    String methodName();

}

PS:

1,變量命名,使用value命名,則使用的時(shí)候可以忽略,否則使用時(shí)就得把參數(shù)名加上,所以我在兩個(gè)注解上分別使用了不同的變量方式,下面使用的時(shí)候,大家記得看清楚。

使用自定義注解

1,在布局文件添加Button,如下:

<Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/btnSay"
        android:text="Hello World!" />

2,在Activity中,申明Button變量以及click方法,如下:

@FindViewById(R.id.btnSay)
@SetOnClickListener(id = R.id.btnSay, methodName = "click")
private Button _btnSay;

/**
 * 點(diǎn)擊事件 2017/4/14 10:00
 */
public void click() {
    Toast.makeText(this, "Hello world", Toast.LENGTH_SHORT).show();
}

3,創(chuàng)建注解解析器,代碼如下:

import android.app.Activity;
import android.view.View;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * Annotation解析 2017/4/14 09:00
 */
public class AnnotationParse {

    /**
     * 解析 2017/4/14 09:01
     * @param target 解析目標(biāo)
     */
    public static void parse(final Activity target) {
        try {
            Class<?> clazz = target.getClass();
            Field[] fields = clazz.getDeclaredFields(); // 獲取所有的字段 2017/4/14 09:34
            FindViewById byId;
            SetOnClickListener clkListener;
            View view;
            String name;
            String methodName;
            int id;
            for (Field field : fields){
                Annotation[] annotations = field.getAnnotations();
                for(Annotation annotation:annotations) {
                    if (annotation instanceof FindViewById) {
                        byId = field.getAnnotation(FindViewById.class); // 獲取FindViewById對(duì)象 2017/4/14 09:10
                        field.setAccessible(true); // 反射訪問私有成員,必須進(jìn)行此操作 2017/4/14 09:13

                        name = field.getName(); // 字段名 2017/4/14 09:18
                        id = byId.value();

                        // 查找對(duì)象 2017/4/14 09:15
                        view = target.findViewById(id);
                        if (view != null)
                            field.set(target, view);
                        else
                            throw new Exception("Cannot find.View name is " + name + ".");

                    } else if (annotation instanceof SetOnClickListener) { // 設(shè)置點(diǎn)擊方法 2017/4/14 09:59
                        clkListener = field.getAnnotation(SetOnClickListener.class);
                        field.setAccessible(true);

                        // 獲取變量 2017/4/14 10:00
                        id = clkListener.id();
                        methodName = clkListener.methodName();
                        name = field.getName();

                        view = (View) field.get(target);
                        if (view == null) { // 如果對(duì)象為空,則重新查找對(duì)象 2017/4/14 09:45
                            view = target.findViewById(id);
                            if (view != null)
                                field.set(target, view);
                            else
                                throw new Exception("Cannot find.View name is " + name + ".");
                        }

                        // 設(shè)置執(zhí)行方法 2017/4/14 09:55
                        Method[] methods = clazz.getDeclaredMethods();
                        boolean isFind = false;
                        for (final Method method:methods) {
                            if (method.getName().equals(methodName)) {
                                isFind = true;
                                view.setOnClickListener(new View.OnClickListener() {
                                    @Override
                                    public void onClick(View v) {
                                        try {
                                            method.invoke(target);
                                        } catch (Exception e) {
                                            e.printStackTrace();
                                        }
                                    }
                                });

                                break;
                            }
                        }

                        // 沒有找到 2017/4/14 09:59
                        if (!isFind) {
                            throw new Exception("Cannot find.Method name is " + methodName + ".");
                        }

                    }
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

PS:

上面的注解解析器注釋比較詳細(xì),仔細(xì)看下,應(yīng)該就可以讀懂上面的解析器作用。它大概做了以下幾個(gè)事情:
a,通過field.getAnnotation(FindViewById.class);反射獲得FindViewById注解對(duì)象,進(jìn)而得到我們?cè)O(shè)置的參數(shù)值R.id.btnSay,再通過Target對(duì)象去查找控件,并設(shè)置;
b,同1,SetOnClickListener也是這樣設(shè)置,只不過多了一個(gè)通過反射獲得的方法,并調(diào)用的過程;
c,field.setAccessible(true);一定要設(shè)置,因?yàn)榉瓷湓L問的是私有成員變量,不設(shè)置會(huì)報(bào)異常;

4,調(diào)用

在OnCreate()方法中,如下調(diào)用:

// 解析注解 2017/4/14 09:22
AnnotationParse.parse(this);

this._btnSay.setText("CCB"); // 只是做下改變

5,演示效果

效果

總結(jié)

總上面的注解解析器中,可以看得出來注解Annotation往往是跟反射配合起來使用的,就連Android現(xiàn)在都很多地方使用到了注解,例如AppcompatActivity類:

AppcompatActivity

就寫框架的人也常說,無反射,不框架,那現(xiàn)在其實(shí)也可以說,無反射,不注解。

所以,要想自己有所進(jìn)階,學(xué)會(huì)創(chuàng)建使用自己的Annotaion就是基礎(chǔ)中的基礎(chǔ)了。

跟著上面的思路走,那么就應(yīng)該能正常理解并使用Annotation庫了.

切記代碼中的注釋一定要看。

希望能對(duì)大家有所幫助,謝謝支持~~~

最后編輯于
?著作權(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)容