Android进阶之Annotation(注解)的使用

注解支持的版本

Android support library从19.1版本开始就引入了一个新的注解库。

添加支持注解库依赖项

要在您的项目中启用注解,请向您的库或应用添加 support-annotations 依赖项。
支持注解库是Android 支持库的一部分。要向您的项目添加注解,您必须下载支持存储库并向 build.gradle文件中添加 support-annotations依赖项。

添加支持注解库依赖项

1,打开 SDK 管理器,方法是点击工具栏中的 SDK Manager或者选择 Tools > Android > SDK Manager

SDK Manager

2,点击 SDK Tools 标签;

SDK Tools

3,展开 Support Repository 并选中 Android Support Repository 复选框;
4,点击 OK;
5,将以下代码行添加到 build.gradle 文件的 dependencies 块中,向您的项目添加 support-annotations 依赖项:

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

6,Sync Now;

PS:

如果您使用appcompat库,则无需添加support-annotations依赖项。因为 appcompat库已经依赖注解库。

那有关注解的环境配置就已经构建好了,那接下来就是创建自己的注解的时候了。

介绍注解

在实现自己的注解时,我们需要来了解其中两个注解。

@Retention(RetentionPolicy.*)

Retention,保存策略。传入的RetentionPolicy参数有如下三种:

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源文件的时候有效;
2,CLASS:只在*.java或者*.class中的文件有效,但是在运行环境无效;
3,RUNTIME:包含以上两种,并且运行时也会有效果,一般我们都会选用该参数。

@Target(ElementType.*)

Target,注解的目标类型。传入的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:作用于方法的参数;
5,CONSTRUCTOR:作用于构造函数;
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新增,但无法访问到;

下面做个DEMO,用注解来做以下两件事:

1,查找控件;

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

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

    // 使用value命名,则使用的时候可以忽略,否则使用时就得把参数名加上 2017/4/14 08:57
    int value();
}

2,设置点击事件;

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

/**
 * 设置点击事件 2017/4/14 09:20
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SetOnClickListener {

    int id();

    String methodName();

}

PS:

1,变量命名,使用value命名,则使用的时候可以忽略,否则使用时就得把参数名加上,所以我在两个注解上分别使用了不同的变量方式,下面使用的时候,大家记得看清楚。

使用自定义注解

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;

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

3,创建注解解析器,代码如下:

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 解析目标
     */
    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对象 2017/4/14 09:10
                        field.setAccessible(true); // 反射访问私有成员,必须进行此操作 2017/4/14 09:13

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

                        // 查找对象 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) { // 设置点击方法 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) { // 如果对象为空,则重新查找对象 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 + ".");
                        }

                        // 设置执行方法 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:

上面的注解解析器注释比较详细,仔细看下,应该就可以读懂上面的解析器作用。它大概做了以下几个事情:
a,通过field.getAnnotation(FindViewById.class);反射获得FindViewById注解对象,进而得到我们设置的参数值R.id.btnSay,再通过Target对象去查找控件,并设置;
b,同1,SetOnClickListener也是这样设置,只不过多了一个通过反射获得的方法,并调用的过程;
c,field.setAccessible(true);一定要设置,因为反射访问的是私有成员变量,不设置会报异常;

4,调用

在OnCreate()方法中,如下调用:

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

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

5,演示效果

效果

总结

总上面的注解解析器中,可以看得出来注解Annotation往往是跟反射配合起来使用的,就连Android现在都很多地方使用到了注解,例如AppcompatActivity类:

AppcompatActivity

就写框架的人也常说,无反射,不框架,那现在其实也可以说,无反射,不注解。

所以,要想自己有所进阶,学会创建使用自己的Annotaion就是基础中的基础了。

跟着上面的思路走,那么就应该能正常理解并使用Annotation库了.

切记代码中的注释一定要看。

希望能对大家有所帮助,谢谢支持~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容