自定义Android注解系列教程之注解变量

前言

对于Android注解,或多或少都有一点接触,但相信大多数人都是在使用其它依赖库的时候接触的。因为有些库如果你想使用它就必须使用它所提供的注解。例如:ButterKnife、Dagger2、Room等等。

至于为何使用注解?使用过的应该都知道,最明显的就是方便、简洁。通过使用注解可以在项目编译阶段,帮助我们自动生成一些重复的代码,减轻我们的负担。典型的ButterKnife本质就是使用Android注解,通过注解来减少我们对view.findViewById的编写,提高我们的开发效率。上一个系列(AAC)的Room也是一样,我们可以简单的回顾一下:

@Entity(tableName = "contacts")
data class ContactsModel(
 @PrimaryKey
 @ColumnInfo(name = "contacts_id")
 val id: Int,
 @ColumnInfo(name = "name")
 val name: String,
 @ColumnInfo(name = "phone")
 val phone: String
)

通过使用注解来定义一个实体表,也就10行左右的代码。如果要我们全部自己写那绝对要两三百行代码了,而且其中还可能出错,又要改bug等等。效率就严重降低。对于依赖库如果都这么麻烦也就不会有人用了。

那么如何判断一个依赖库是否需要使用注解呢?其实很简单,只要记住以下两点即可:

  • 需要生成的代码不能与项目逻辑有关
  • Android注解只能生成代码,并不能修改代码

这里透露一下,Android注解的本质是使用Java的反射机制,后续会详细说明

项目架构

相信ButterKnife应该有接触过吧,没有的也没关系,现在正是时候。下面我们会自己实现BindView与OnClick注解,实现ButterKnife中的对应注解功能。那么我先来看下整体的项目架构

通过项目图,我们可以清晰的看到,主要分为三个部分

  • butterknife-annotations:注解库,包含BindView与OnClick等自定义的注解
  • butterknife-bind:绑定库,自定义的注解与声明的类绑定
  • butterknife-compiler: 解析编译生成库,解析声明类中的注解,在编译时自动生成相应的代码。

为了帮助大家能够更轻松的理解Android注解,今天主要分析的就是butterknife-annotations这个注解库。带大家一起来声明注解变量。

BindView

为了要实现开源库butterknife类似的绑定id效果,这里我们先定义一个BindView注解,具体如下:

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface BindView {
 @IdRes int[] value();
}

嗯,还是很简单的对吧。也就5行代码解决BindView注解的定义。

那么再来详细剖析这5行代码。

Retention

首先是第一行代码的Retention,看它的使用方式就能知道,它也是一个声明了的注解。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
 /**
 * Returns the retention policy.
 * @return the retention policy
 */
 RetentionPolicy value();
}

通过源码我们可以看出该注解只接收一个参数,该参数为RetentionPolicy类型。那么我们在进一步深入RetentionPolicy:

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、CLASS与RUNTIME。它们的区别主要是作用的周期范围,下面我再对这三个的作用进行翻译一遍:

  • SOURCE: 使用该标明的注解将在编译阶段就被抛弃掉。
  • CLASS:使用该标明的注解将在编译阶段记录到生成的class文件中,但在运行阶段时又会被VM抛弃。默认是该模式。
  • RUNTIME:使用该标明的注解将在编译阶段被保存在生成的class文件中,同时在运行阶段时会保存到VM中。所以它该注解将一直存在,自然能够通过java的反射机制进行读取。

所以它们的存在的生命时长为SOURCE < CLASS < RUNTIME。知道了它的作用范围之后,我们在自定义注解时就要尽量较小注解的作用范围,提高项目的编译与运行速度。

因为我们的BindView注解只是为了进行Viwe的绑定,所以在编译之后就无需存在,所以这里就使用了CLASS来进行标明。

Target

下面我们在来看第二行代码,这里使用到了另一个注解Target,我们还是来看下它的源码:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@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();
}

可以看到注解的源码都非常简单,这里接收了一个ElementType数组参数,ElementType不难猜出它的类型也是一个枚举:

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
}

ElementType中虽然有10常量,但我们实际真正常用的也就是前面8种。它们代表自定义的注解能够作用的对象。分别为:

  • TYPE: 作用于类、接口或者枚举
  • FIELD:作用于类中声明的字段或者枚举中的常量
  • METHOD:作用于方法的声明语句中
  • PARAMETER:作用于参数声明语句中
  • CONSTRUCTOR:作用于构造函数的声明语句中
  • LOCAL_VARIABLE:作用于局部变量的声明语句中
  • ANNOTATION_TYPE:作用于注解的声明语句中
  • PACKAGE:作用于包的声明语句中
  • TYPE_PARAMETER:java 1.8之后,作用于类型声明的语句中
  • TYPE_USE:java 1.8之后,作用于使用类型的任意语句中

结合我们的BindView的作用是对View进行id绑定,自然是作用与声明的字段上。所以在BindView中使用了FIELD。

再来看第四行代码

@IdRes int[] value()

有了上面的注解接触,不难理解这是标明BindView将接收一个int类型的数组参数。对于开源库butterknife中的BindView是接收需要绑定的View的id,这里我们做一个改版,再接收一个String的id,用来为绑定的View设置默认值。这样我们自定义了的BindView注释就完成了。

OnClick

下面我们再自定义一个OnClick点击的注解,经过上面的分析,可以在脑海中想想Retention与Target分别什么值?

想好了之后我们在来过一遍

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface OnClick {
 @IdRes int value();
}

Retention的作用范围与BindView一样首页SOURCE,在编译之后就无需存在;Target的作用对象与BindView不同,既然是点击事件的点击操作,自然是作用在操作逻辑的方法上,所以这里使用METHOD。

keep

文章开头有提及到本质是通过注解来自动生成代码,为我们创建所需的类,那么在实际开发中一旦我们的项目混淆了,这将会导致自动创建的类失效,从而导致我们自定义的注解失效。所以为了防止其失效,我们在这里再定义一个注解keep:

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface Keep {
}

Retention的作用范围是在class文件中还要能够被其它class调用,所以这里使用CLASS;Target作用对象是自动生成的类,所以使用TYPE。至于参数则不必要,它只是为了标明类,防止其被混淆。

总结

butterknife-annotations库中的自定义注解就完成了。通过上面的分析,我们注意点主要归结于以下三点:

  • Retention: 明确注解作用的生命时长,尽早的消除
  • Target: 明确注解作用的对象
  • Keep: 防止后续自动生成的类被混淆

注解变量的定义就到这结束了,同时文章中的代码都可以在Github (本地下载)中获取到。使用时请将分支切换到feat_annotation_processing

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Android注解基础介绍快速入门与解读

    首先什么是注解?@Override就是注解,它的作用是: 1.检查是否正确的重写了父类中的方法. 2.标明代码,这是一个重写的方法. 1.体现在于:检查子类重写的方法名与参数类型是否正确:检查方法private/final/static等不能被重写.实际上@Override对于应用程序并没有实际影响,从它的源码中可以出来. 2.主要是表现出代码的可读性. 作为Android开发中熟知的注解,Override只是注解的一种体现,更多时候,注解还有以下作用: 降低项目的耦合度. 自动完成一些规律性的

  • Android基于注解的6.0权限动态请求框架详解

    前言 安卓6.0之后,一些敏感权限需要进行动态请求,虽说编写请求授权代码并不难,但是每次一需要权限就需要在视图中添加一段代码,严重影响代码美观,同时也增加了一点点工作量. 于是,小盆友闲暇之余基于AOP封装了一个基于注解的权限请求框架.如果有幸加入您的项目,使用过程中有问题或是有哪些不便,请留言区或github上与我交流,共同进步.如果喜欢这个框架请给个star和❤️. github地址:https://github.com/zincPower/JPermission 先上图,看看效果...第一

  • Android 中的注解详细介绍

    注解是我们经常接触的技术,Java有注解,Android也有注解,本文将试图介绍Android中的注解,以及ButterKnife和Otto这些基于注解的库的一些工作原理. 归纳而言,Android中的注解大概有以下好处 提高我们的开发效率 更早的发现程序的问题或者错误 更好的增加代码的描述能力 更加利于我们的一些规范约束 提供解决问题的更优解 准备工作 默认情况下,Android中的注解包并没有包括在framework中,它独立成一个单独的包,通常我们需要引入这个包. dependencies

  • Android 中的注解深入探究

    本文系GDG Android Meetup分享内容总结文章 注解是我们经常接触的技术,Java有注解,Android也有注解,本文将试图介绍Android中的注解,以及ButterKnife和Otto这些基于注解的库的一些工作原理. 归纳而言,Android中的注解大概有以下好处 提高我们的开发效率 更早的发现程序的问题或者错误 更好的增加代码的描述能力 更加利于我们的一些规范约束 提供解决问题的更优解 准备工作 默认情况下,Android中的注解包并没有包括在framework中,它独立成一个

  • Android注解使用之ButterKnife 8.0详解

    前言: App项目开发大部分时候还是以UI页面为主,这时我们需要调用大量的findViewById以及setOnClickListener等代码,控件的少的时候我们还能接受,控件多起来有时候就会有一种想砸键盘的冲动.所以这个时候我们想着可以借助注解的方式让我们从这种繁重的工作中脱离出来,也让代码变得更加简洁,便于维护,今天主要学习一下只专注View.Resource.Action注解框架ButterKnife. ButterKnife介绍 ButterKnife是一个专注于Android系统的V

  • Android AOP 注解详解及简单使用实例(三)

    Android  注解 相关文章: Android AOP注解Annotation详解(一) Android AOP之注解处理解释器详解(二) Android AOP 注解详解及简单使用实例(三) 一.简介 在Android 里面 注解主要用来干这么几件事: 和编译器一起给你一些提示警告信息. 配合一些ide 可以更加方便快捷 安全有效的编写Java代码.谷歌出的support-annotations这个库 就是主要干这个的. 和反射一起 提供一些类似于spring 可配置的功能,方便简洁. 二

  • Android注解ButterKnife的基本使用

    ButterKnife的最新版本是8.4.0. 首先,需要导入ButterKnife的jar包. 在AndroidStudio中,File->Project Structure->Dependencies->Library dependency 搜索butterknife即可,第一个就是. 另外一种就是直接在build:grade(app)dependencies里添加: compile 'com.jakewharton:butterknife:8.4.0' annotationProc

  • 深入分析安卓(Android)中的注解

    归纳而言,Android中的注解大概有以下好处 1.提高我们的开发效率 2.更早的发现程序的问题或者错误 3.更好的增加代码的描述能力 4.更加利于我们的一些规范约束 5.提供解决问题的更优解 准备工作 默认情况下,Android中的注解包并没有包括在framework中,它独立成一个单独的包,通常我们需要引入这个包. dependencies { compile 'com.android.support:support-annotations:22.2.0' } 但是如果我们已经引入了 app

  • Android 反射注解与动态代理综合使用详解

    前言 本章内容主要研究一下java高级特性-反射.android注解.和动态代理的使用,通过了解这些技术,可以为了以后实现组件化或者Api hook相关的做一些技术储备. 反射 主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义. 反射是java中一种强大的工具,能够使我们很方便的创建灵活的代码,这些代码可以再运行时装配,无需在组件之间进行源代码链接.但是反射使用不当会成本很高 比较常用的方法 getDeclare

  • Android AOP注解Annotation详解(一)

    Android 注解Annotation 相关文章: Android AOP注解Annotation详解(一) Android AOP之注解处理解释器详解(二) Android AOP 注解详解及简单使用实例(三) Android AOP 等在Android上应用越来越广泛,例如框架ButterKnife,Dagger2,EventBus3等等,这里我自己总结了一个学习路程. - Java的注解Annotation - 注解处理解析器APT(Annotation Processing Tool)

  • Android AOP之注解处理解释器详解(二)

    Android APO 注解处理解释器 相关文章: Android AOP注解Annotation详解(一) Android AOP之注解处理解释器详解(二) Android AOP 注解详解及简单使用实例(三) 一.提取Annotation信息 当开发者使用了Annotation修饰了类.方法.Field等成员之后,这些Annotation不会自己生效,必须由开发者提供相应的代码来提取并处理Annotation信息.这些处理提取和处理Annotation的代码统称为APT(Annotation

  • Android注解框架对比分析

    Java的注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标记,标记可以加在包,类,属性,方法,本地变量上.然后你可以写一个注解处理器去解析处理这些注解(人称编译时注解),也可以在程序运行时利用反射得到注解做出相应的处理(人称运行时注解). 开发Android程序时,没完没了的findViewById, setOnClickListener等等方法,已经让大多数开发者头疼不已.好在市面上有所谓的注解框架可以帮助开发者简化一些过程.比较流行的有butterknife

  • Android中封装SDK时常用的注解总结

    前言 在工作中我们经常需要将功能模块封装成库供合作厂商调用, 如何写好一个健壮的Android Library有很多讲究,使用注解可以对SDK暴露给开发者的接口做出一些限制,从而尽可能地避免开发者错误地使用API. 下面我们介绍几种封装SDK时常用到的注解,需要的朋友们可以参考学习. 一.IntDef与StringDef 我们有时候会使用int常量或者String常量来代替枚举, 特别在你编写SDK的时候,你可以通过IntDef或者StringDef来限制接口可接受的参数. 比如,有一个 dis

随机推荐