Android注解-什么?啥是注解?干啥的?

首先带入问题。
1.什么是注解。
2.注解有什么用,我们为什么要用注解?
3.注解的生命周期,编译时注解和运行时注解区别。

引用别人对注解的解释,注解可以理解成标签。

在代码中我们最常见的注解应该是他 @Override ,用于重载父类的方法。
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

点击 @Override 进到源码,中我们会发现下面这种结构

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

那么,参考源码的实现方式我们这样就可以生成自己的注解

public @interface TestAnnotation {

}

在我们代码中进行调用

@TestAnnotation
private void logOut() {
    Log.i(TAG, "logOut: ");
}

只不过这种注解实现,除了能增加代码量,其他毫无意义 [手动狗头]。

上面@Override 注解中出现的 @Target 和 @Retention

这两个东西,一看感觉就是注解的参数,点进源码会发现官方给他们起了一个专门的名字,meta-annotation(元注解),其只能,表明声明的类型,仅用作复杂注释类型声明中的成员类型。它不能用于直接注释任何内容。(意思对注解进行注解)

/**
* <p>This {@code @Target} meta-annotation indicates that the declared type is
* intended solely for use as a member type in complex annotation type
* declarations.  It cannot be used to annotate anything directly:
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}

如果我们在其他的地方调用,as会弹出类似的报错。


TargetError.png

元注解是注解中的一种,他通过 @Target(ElementType.ANNOTATION_TYPE) 指定其使用场景,只能用在注解上,即对注解进行注解
Target 支持的类型

ElementType 类型范围
TYPE 类、接口(包括注释类型)或枚举声明
FIELD 字段声明(包括枚举常量
METHOD 方法
PARAMETER 参数
CONSTRUCTOR 构造方法
LOCAL_VARIABLE 局部变量
ANNOTATION_TYPE 注解
PACKAGE
TYPE_PARAMETER 类型参数(泛型)
TYPE_USE 使用类型注解

@Retention 指定注解的生命周期

SOURCE CLASS RUNTIME
生命周期 源码阶段 Class文件阶段 运行
解释 .java文件(仅在我们开发过程中存在,
提示错误或警告)
编译后从java变成.class文件
保存在字节码
ClassLoder 加载class字节码到内存

那么对第一个问题的总结 什么是注解,在代码层面上,只要使用@interface 的都是注解。如果我们指定他的Target在annotation上。@Target(ElementType.ANNOTATION_TYPE) 那么也可以称他为元注解。



2 注解的作用
  • @Retention(RetentionPolicy.SOURCE)
    源代码时期的注解,仅存在于.java文件中
    1.用于代码检查例如@NonNull /@Nullable ,资源引用限制例如@DrawableRes,@StringRes,提醒错误,过时例如@Deprecated。
    2.在编译期间处理,.java文件编译生成class文件时期,通过开发者注册的注解处理器(AnnotationProcessor)对注解进行处理,使用JavaPoet生成模板代码,提高开发效率。
  • @Retention(RetentionPolicy.RUNTIME)
    1.注解在class字节码文件中存在,通过反射获取到注解的对象以及参数等信息提供调用。容易实现但反射消耗性能不建议过度使用。

举个栗子
完善一下我们之前的TestAnnotation 添加两个参数,name 和 age

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    String name();
    int age();
}

在创建一个UserBean类

public class UserBean {

    private String name;

    private int age;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    private String getName() {
        return name;
    }

    @FunctionAnnotation
    private void setName(@FunctionType String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{"
                + "name='" + name + '\''
                + ", age=" + age
                + '}';
    }
}

创建用于绑定的Utils

public class AnnotationUtils {

    public static void Inject(Activity activity) {

        //万物皆对象。获取activity的class
        Class<? extends Activity> c = activity.getClass();

        // 获取所有字段
        Field[] fields = c.getDeclaredFields();

        //遍历拿到带有 @TestAnnotation 注解的字段
        for (Field field : fields) {
            if (field.isAnnotationPresent(TestAnnotation.class)) {
                TestAnnotation annotation = field.getAnnotation(TestAnnotation.class);
                if (annotation == null) {
                    break;
                }
                //TestAnnotation name
                String name = annotation.name();
                int old = annotation.age();

                //类型的包名路径 例如:com.example.demo.data.UserBean
                String packName = field.getType().getName();
                //手动定义的对象名称,此例子使用的是mUser
                String fieldName = field.getName();

                try {

                    //创建一个user 这里使用包名路径
                    Class<?> fieldClass = Class.forName(packName);
                    UserBean user = (UserBean) fieldClass.newInstance();

                    //返回此Class对象对应类的、带指定形参列表的方法
                    Method declaredMethod = fieldClass.getDeclaredMethod("setName", String.class);
                    //设置可以访问私有权限
                    declaredMethod.setAccessible(true);
                    //调用方法
                    declaredMethod.invoke(user, name);

                    Method declaredMethodSetOld = fieldClass.getDeclaredMethod("setAge", int.class);
                    declaredMethodSetOld.setAccessible(true);
                    declaredMethodSetOld.invoke(user, old);

                    //设置可以访问私有权限
                    field.setAccessible(true);
                    //set对象
                    field.set(activity,user);

                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                }

            }
        }

    }
}

MainActivity中使用AnnotationUtils注入UserBean 对象

public class MainActivity extends Activity {
    
    @TestAnnotation(name = "xiao ming", old = 18)
    private UserBean mUser;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        AnnotationUtils.Inject(this);
        System.out.println(" main activity user is " + mUser);
    }

}

运行程序我们会得到如下的日志打印

I/System.out:  main activity user is User{name='xiao ming', age=18}

注解的作用,小结
emmm,搞了一圈你就给我看这个?我直接new一个对象set进属性不就完了吗。搞这么一圈四不四有毛病。
对于我们自己写代码的话确实如此。而且代码业务越简单/单一,越不需要反射和注解。如果只要实现一句hello world,那搞别的操作真是画蛇添足。

我理解是这样的

  • 反射 的场景在于,我们需要使用别人的代码或者android源码(依赖库)。且无法直接修改,或者调用,别人写的代码的情况。迫不得已 我们可以尝试使用反射去操作修改别人的对象,或者调用方法。

  • 注解嘛,就厉害了。我们需要给别人提供服务,或者依赖库的时候。使用者通过使用,我们开发者定义的注解,按照我们定义的规则去标注使用者的代码,从而为开发者实现某些功能。方便使用者使用,和理解。
    例如:retrofit ,我们就是使用者,按照retrofit开发者定义好的各种注解,例如@GET,@POST,按照retrofit开发者定义的规则去标注我们的接口方法。开发者通过读取我们的注解,帮我们实现各种逻辑。其他使用注解的框架同理,都是为了使用者便方便调用。
    那么如果我们,需要给别人提供服务,或者我们要写依赖库的时候,就可以考虑是否用注解去实现了。

  • 如果上面说的,理解了。那应该也会理解,为什么上面举的栗子,明明只用反射也可以实习给user 设置 name 和 age,却偏偏要用注解了吧。(先后关系,先有我们定义好注解的TestAnnotation注解的规则,后面才有开发者使用TestAnnotation 注解去标注给User使用)。

注解的优势
  • 上面的栗子运行时期注解,需要用到反射去完成各种操作,好像体现不出注解的优势。
  • 源码时期注解提示安全以及报错
  • 编译时期注解,在编译阶段可以为我们生成代码,实现各种功能例如早期的 JakeWharton 大神Butterknife ,生成代码实现findviewById操作。以及Goodle HIlt 依赖注入。
    那我们如何实现编译时注解呢。Javapoet为此做出了很大贡献。 https://github.com/square/javapoet 具体使用方法请搜索引擎,输入Javapoet教学 [手动狗头]

对于第三个问题,注解的生命周期,编译时注解和运行时注解区别
你都看到这了,应该有个大致答案了。

  • 首先声明周期就不一样,一个在编译期class字节码阶段。一个被ClassLoder 从class字节码到内存。
  • 编译时期注解一般会生成各种代码实现各种操作。运行时期注解,程序运行时期获取到你使用的各种注解,例如retrofit 运行时注解 加 动态代理完成网络请求的各种操作。
  • //todo以后想到了继续补充
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,384评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,845评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,148评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,640评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,731评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,712评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,703评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,473评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,915评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,227评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,384评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,063评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,706评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,302评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,531评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,321评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,248评论 2 352

推荐阅读更多精彩内容