注解,也被称为元数据,为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便地使用这些数据。(Java编程思想)
很多文章都是讲述java注解的,而且很多例子虽然有和Android互通的部分,但是Android开发中也扩展了很多单纯Java中没有的注解应用。所以这里主要介绍Android开发中的注解,当然包括Java注解。
目前很多框架开发或者Android开发中都用到了注解,SDK开发中也有很多可以对接口添加限制以规范用户使用的规则,这些都是值得我们去学习的。
Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。 Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。
Java内置了三种标准注解,其定义在java.lang中。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={
CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
@Target({
TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
那注解的注解又是啥呢,这里就要引出接下来要说的,元注解,专职负责注解其他的注解。
四种元注解
元注解 | 说明 | 取值 |
---|---|---|
@Target | 表示该注解可以用在什么地方 | ElementType.ANNOTATION_TYPE 可以应用于注释类型。 ElementType.CONSTRUCTOR 可以应用于构造函数。 ElementType.FIELD 可以应用于字段或属性。ElementType.LOCAL_VARIABLE 可以应用于局部变量。 ElementType.METHOD 可以应用于方法级注释。 ElementType.PACKAGE 可以应用于包声明。 ElementType.PARAMETER 可以应用于方法的参数。ElementType.TYPE 可以应用于类的任何元素。 |
@Retention | 表示需要在什么级别保存该注解信息 | 1.SOURCE:在源文件中有效(即源文件保留) 2.CLASS:在class文件中有效(即class保留) 3.RUNTIME:在运行时有效(即运行时保留) |
@Documented | 表示将此注解包含在Javadoc中 | 无 |
@Inherited | 表示允许子类继承父类中的注解 | 无 |
经过对元注解的表格进行理解,可以回头看我们平常使用的标准注解Override 、Deprecated 和SuppressWarnings 。可以看到他们分别都由元注解进行了注释,并且我们能够明白,为啥@Override只能用在方法上(@Target),而不能用在类、构造函数、变量上,而@Deprecated就可以用在构造函数、变量、方法、包、参数等,因为其代表的意义就可以表示它能够标注的所有类型都是废弃的元素。
另外,我们需要对Java程序或者Android程序的三个阶段进行解释。我们在as或者idea等IDE开发时的Java源码时期,即SOURCE实际。然后经过gradle或其他构件工具编译,变成了.Class文件,在Android apk中的dex文件中都是.class文件,此时即Retention的Class时期。之后便是程序运行阶段,变成Jvm运行的RUNTIME时期,此时应用是执行状态,如果定义注解为RUNTIME,则此时的注解是保留的。
java的三个标准注解的@Retention,有两个是RetentionPolicy.SOURCE,一个是RetentionPolicy.RUNTIME。从上述表格可以了解,source即源文件保留,即是.java文件中保留。Runtime即运行时有效,即在Android应用在运行时还会存在,当然如果是java程序运行时也保留。可以理解为@Override 注释的方法,只在代码开发时有用即编码时可以覆盖父类方法,编译后和运行中这个注解代表的意义就没有了。
大多数时候,程序员主要是用元注解定义自己的注解,并编写自己的处理器来处理它们,文章最后会简单的写个自定义注解并应用以加深理解。
扩展
上边是经典的Java注解介绍,由于我们的Java开发都是基于JDK的,所以上述源码定义都在JDK的源码中。而由于新的JDK对旧版进行了扩充。
如@Repeatable 即是@since 1.8。 从JDK1.8引入的,可能使用比较少见,笔者也没见过这个的应用目前。但是提这个的目的即任何语法都是供加速开发或者其他目的添加的,只不过我们自定义注解是为了我们自己的逻辑、或者做框架的是为了使用框架的人方便, JDK的开发人员是为了所有Java开发和他们自己方便。所以虽然我们不是定义JDK语言的,但是我们任何人开发都可以使用注解这种方式,为我们的代码和框架服务。注解是不断变化并发展的,当然我们Java或Android开发也是。
package java.lang.annotation;
/**
* The annotation type {@code java.lang.annotation.Repeatable} is
* used to indicate that the annotation type whose declaration it
* (meta-)annotates is repeatable. The value of
* {@code @Repeatable} indicates the containing annotation
* type for the repeatable annotation type.
*
* @since 1.8
* @jls 9.6 Annotation Types
* @jls 9.7 Annotations
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
/**
* Indicates the containing annotation type for the
* repeatable annotation type.
* @return the containing annotation type
*/
Class<? extends Annotation> value();
}
本来在Android系统源码中/frameworks/base/core/java/android/annotation中是有很多的注解的,但是呢,他们都是系统源码用的注解,不是给我们这种开发人员用的,因为所有的注解的注释上都有@hide注释。
* {
@hide}
*/
@Documented
@Retention(SOURCE)
@Target({
METHOD, PARAMETER, FIELD})
public @interface AnyRes {
}
然后呢,Android系统给我们提供的SDK中,只有两个原生注解。位于android.annotation包中
@TargetApi 使高版本API的代码在低版本SDK不报错。
@SuppressLint 使用此标注让Lint忽略指定的警告。
举例:
@SuppressLint(“NewApi”)屏蔽一切新api中才能使用的方法报的android lint错误。 @SuppressLint(“HandlerLeak”) 在主线程用Handler处理消息出现时会有警告,提示你,这块有内存泄露的危险,handler最好声明为static的
然后系统以另外的形式即Android Support方式提供给我们其他的注解。这些注解不是默认加载的,它们被包装为一个单独的库。Support Library现在是由一些更小的库组成的,包括:v4-support、appcompat、gridlayout、mediarouter等等。当然这里的注解是没有@hide注释的,只是需要开发者去加载对应的dependence。然后Android Studio实在基于这些注解来检查代码中的潜在问题了。如果升级到androidx,也有对应的dependence。
dependencies {
compile 'com.android.support:support-annotations:XXX'
}
作用举例:资源里的R.layout.和R.string.,二者都是int值,在源码阶段,如果在setContentView(值)的值设为R.string.* 理论上是不是也能编译通过?但是实际上不能,如果你这样做了,就会显示红色警告,为什么呢。
原因就在源码里,我们可以点进去看源码。在setContentView的参数里有**@LayoutRes**的注解,告诉用户这个int值必须是代表layout的。其他的资源类注解同理。
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
Thread annotations 线程执行限制类:用于限制方法或者类必须在指定的线程执行。如果方法代码运行线程和标注的线程不一致,则会导致警告。
@AnyThread
@BinderThread
@MainThread
@UiThread
@WorkerThread
Value Constraint Annotations 类型范围限制类:用于限制标注值的值范围
@FloatRang
@IntRange
如下为@FloatRange的注释上的示例, 其代表返回值为从0.0-1.0直接的float值。
/**
* Denotes that the annotated element should be a float or double in the given range
*
* Example:
* {@code
* @FloatRange(from=0.0,to=1.0)
* public float getAlpha() {
* ...
* }
类型定义类:用于限制定义的注解的取值集合
@IntDef int型除了被作为资源引用传递之外,还经常被作为一种“枚举”类型来使用。在创建一个注解的同时列出所有可用的有效值,然后再使用这个注解。也就是说@IntDef是用来修饰注解的注解(元注解),用它所修饰的注解在使用时就限定了被修饰对象的可取值范围。
@StringDef 类似IntDef,列举出所有String的取值范围。
如下为Intdef的源码,可以看出,其@Target为ANNOTATION_TYPE,和前边java的元注解一致,所以这两个也是元注解。
@Retention(CLASS)
@Target({
ANNOTATION_TYPE})
public @interface IntDef {
/** Defines the allowed constants for this element */
long[] value() default {
};
/** Defines whether the constants can be used as a flag, or just as an enum (the default) */
boolean flag() default false;
}
这里继续举例说明,自定义了一个Weather注解,而且定义了取值范围,即IntDef中包含的选型。
public static final int SUNNY = 1;
public static final int CLOUDY = 2;
public static final int RAIN = 3;
public static final int SNOW = 4;
@Target(ElementType.PARAMETER)
@IntDef({
SUNNY,
CLOUDY,
RAIN,
SNOW
})
@Retention(RetentionPolicy.SOURCE)
public @interface Weather {
}
public void setWeather(@Weather int weather){
}
使用时,如果传入IntDef中定义的值,则显示正常,否则编译器会出发红色警告。虽然,2值是包含在IntDef取值范围的int值,但是仍不允许,必须写对应常量。
@RequiresPermission( Manifest.permission.READ_EXTERNAL_STORAGE)
public void setWeather(@Weather int weather) {
}
JUnit:
是一个Java语言的单元测试框架,可以大大缩短你的测试时间和准确度。
@Test:进行测试的方法 。
@BeforeClass : 该方法表示启动测试类对象测试之前启动的方法, 所以该方法必须是static 修饰的(可以通过类名直接访问).一般用来打开配置文件,初始化资源等
@AfterClass :该方法表示测试类对象测试完成之后启动的方法, 所以该方法必须是static 修饰的(可以通过类名直接访问).一般用来关闭数据库,结束资源等
@Before :该方法表示调用每个测试方法前都会被调用一次
@After :该方法表示调用每个测试方法后都会被调用一次
@Ignore :已经被忽略的测试方法 ,我们测试的话,会自动过滤掉
……
ButterKnife :
Android 开发中 IOC 框架,它减少了大量重复的代码。专注于Android系统的View注入框架,以前总是要写很多findViewById来找到View对象,有了ButterKnife可以很轻松的省去这些步骤。并且使用ButterKnife对性能基本没有损失,因为ButterKnife用到的注解并不是在运行时反射的,而是在编译的时候生成新的class。
ButterKnife项目地址:https://github.com/JakeWharton/butterknife有需要研究的同学可以通过阅读源码深入学习。
涉及注解有:
@BindView
@OnClick
@BindString等。
Dagger2
著名的依赖注入框架,依赖注入,顾名思义,就是说当代码执行过程中需要某个服务对象的时候,不是通过当前代码自己去构造或者去查找获取服务对象,而是通过外部将这个服务对象传给当前代码。
其自定义的注解有:
@Inject
@Moudle
@Provider
@Component
@Singleton
@Scope
@Qualifier
@Named
对相关知识需要进一步学习的可以参考项目地址: https://github.com/google/dagger
Retrofit
Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装,网络请求的工作本质上是 OkHttp 完成,而 Retrofit 仅负责 网络请求接口的封装。Retrofit通过注解的方式,进行网络请求描述。
其常用注解有:
@GET
@POST
@PUT
@DELETE
@PATCH
@HEAD
@OPTIONS
@HTTP
需要继续深入了解的同学可以参考项目地址:https://github.com/square/retrofit
EventBus
EventBus是一种用于Android的事件发布-订阅总线,由GreenRobot开发,它简化了应用程序内各个组件之间进行通信的复杂度,尤其是碎片之间进行通信的问题,可以避免由于使用广播通信而带来的诸多不便。
其主要的注解为
@Subscribe
项目地址为:https://github.com/greenrobot/EventBus
Arouter
ARouter 是阿里开源的组件化框架,或者叫做路由框架。可以用来映射页面关系,实现跳转相关的功能。在 Android 中,常被用来进行组件化通讯。
常用注解有:
@Route
@Interceptor
@Autowired等。
项目地址为:https://github.com/alibaba/ARouter
为了更好的了解注解的作用和框架如何使用注解打到自己的目的,方便用户提高编码效率。这里模仿ButterKnife的功能实现一个注解。
首先自定义注解InjectDIYLayout , 这里为了简单示例用了运行时注解,并采用反射方法调用执行。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectDIYLayout {
int value();
}
然后写工具类,在BaseActivity中的onCreate中初始化。InjectDIYUtils .inject(this);
public class InjectDIYUtils {
private static final String TAG = "InjectDIYUtils";
public static void inject(Object context){
injectDIYLayout (context);
}
}
之后就是注解的解释了,就是injectLayout的方法体。这里通过拿到参数activity的Context,并获取Activity的类,之后通过类getAnnotation得到此类注释的注解,经打印可以判断是否此注解。然后通过反射获取到setContentView,并用反射方法的invoke调用,传入注解中的layout值。
private static void injectDIYLayout (Object context) {
Class<?> aClass = context.getClass();
InjectDIYLayout annotation = aClass.getAnnotation(InjectDIYLayout.class);
Log.e(TAG, "injectLayout: annotation.value()="+annotation.value());
try {
Method contentView = aClass.getMethod("setContentView", int.class);
try {
Object invoke = contentView.invoke(context, annotation.value());
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
使用时的代码
@InjectDIYLayout(R.layout.activity_main)
public class MainActivity extends BaseActivity {
然后在Activity的onCreate方法中注释setContentView。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView();
编译看看,是不是能够正常显示。有兴趣的同学可以自己创建工程测试下。
首先,由于注解分了三个阶段,有源码阶段、编译阶段、运行阶段,不同阶段的注解有不同作用。
还等什么呢,赶紧拿起电话订购吧! 哦,不,是收藏这篇文章吧。