合理使用Android提供的Annotation来提高代码的质量
概述
Java语言提供了Annotation的机制,让描述性的元数据能够和代码共存。通常我们可以利用Annotation,来做一些标志性的说明。然而Annotation必须和相应的解析工具一起才能工作。合理的运用Annotation,会带来一些额外的效果。
本文不讨论Annotation的基础语法以及基础使用方法。
Android用Annotation干了什么?
Java这种强类型的语言,在编写代码的时候,编辑器就会通过语法检查,来阻止一些错误的发生。相比于一些弱类型的语言,更安全。Annotation作为元数据而存在,结合上运行在编辑期的解析工具,可以做到更细致的代码检查,可以说是一种对语法检查的扩充吧。
Android就充分利用了这一点,Android在提供了大量的Annotation的同时,也在ADT中提供了操作这些Annotation的工具,主要体现在代码检查阶段。下面我们会详细看一下Android提供的Annotation。这些Annotation不仅可以让代码在编写的时候,少出一些问题,而且可以对一个类、方法等做详细的说明。这些Annotation也可以指导使用API的开发人员,正确的使用API。
Android是如何运用Annotation来进行代码检查的?
Android提供了几大类的Annotation,用来配合其相应的解析工具,来对代码做更严格的检查。
1、强制方法调用类型。
作用:指示所有Override该方法的方法,都需要调用调用该方法。
Annotations:@CallSuper
使用场景:你设计了一个框架,里面有一些基础方法,各子类需要覆写该方法,但是一定要调用父类的方法。这时候可以使用这个Annotation来强制子类调用父类的方法。
示例代码:
//父类 @CallSuper protected void fun() { System.out.println("parent fun()"); } //子类 @Override protected void fun() { super.fun(); }
如果不调用 super.fun(),则会出现提示:Overriding method should call super.fun。
2、指定固定常量型。
作用:指示一个逻辑类型,并且这个逻辑类型的值只能取固定的常量。
Annotations:@IntDef
@StringDef
使用场景:作为一个特定类型的有限个取值,可以使用Enum或者使用常量,但是Enum可以有类型检查,而常量没有。众所周知,因为内存占用的问题,Android不推荐使用Enum,那就只能使用常量,但是常量不能做类型检查,所以Android就提供了这样一种方法,来解决此问题,让指定的属性值只能取定义好的常量。
示例代码:
//定义常量 public static final int INT_1 = 1; public static final int INT_2 = 2; //指定该Annotation描述的对象,只能使用这两个常量 @IntDef({INT_1, INT_2}) public @interface MyType { } //如果一个方法只想接收这两个常量(常量名称,而不是常量的值)作为参数,那么可以这样使用 private void f (@MyType int time) { }
如果调用f()时,不传递这两个常量,比如f(2)。那么就会报错:Must be one of: INT_1, INT_2。
3、资源对象的值类型指定型。
作用:指示所描述的参数、字段、返回值必须是指定的资源类型的引用。
Annotations:@AnimatorRes
@AnimRes
@AnyRes
@ArrayRes
@AttrRes
@BoolRes
@ColorRes
@DimenRes
@DrawableRes
@FractionRes
@IdRes
@IntegerRes
@InterpolatorRes
@LayoutRes
@MenuRes
@PluralsRes
@RawRes
@StringRes
@StyleableRes
@StyleRes
@TransitionRes
@XmlRes
使用场景:如果你有一个方法,需要一个 R.id 的值作为参数,或者一个方法,希望返回值是 R.anim 类型的引用,那么这个时候就可以使用这些Annotation。
示例代码:
//希望接收一个id作为参数 private void f (@IdRes int id) { } private void call() { //但是实际上不小心传递了一个layout进去。 f(R.layout.day_view); }
那么这个时候就会报错:Expected resource of type id。
4、取值范围型。
作用:指示一个int、float或者double类型的值,合理的取值范围。
Annotations:@FloatRange
@IntRange
使用场景:比如有一个方法,接收一个int型的透明度参数,那么该方法就会希望传进来的值应该是0~255,这时就可以使用该Annotation。
示例代码:
//希望接收一个[0, 255]的值作为透明度 private void setAlpha (@IntRange(from = 0, to = 255) int alpha) { } private void call() { //但是实际上不小心传递了一个300进去。 setAlpha(300); }
那么这个时候就会报错:Value must be ≥ 0 and ≤ 255 (was 300)。
5、线程标识型。
作用:指示某个对象、构造器或者方法,应该在指定的类型的线程运行。
Annotations:@BinderThread
@MainThread
@UiThread
@WorkerThread
使用场景:如果有一个方法,比较耗时,所以它应该放在工作线程执行。这时候可以用@WorkerThread来标注该方法,当在主线程调用该方法的时候,就会报错。
示例代码:
//该方法比较耗时,所以放在子线程执行 @WorkerThread private void shouldRunOnWorkerThread() { } @MainThread private void runOnMainThread() { //在主线程调用该方法,会报错。 shouldRunOnWorkerThread(); }
那么这个时候就会报错:Method shouldRunOnWorkerThread must be called from the worker thread, currently inferred thread is main。
像 Activity 的onCreate方法,都是被标注为主线程执行的。
@MainThread @CallSuper protected void onCreate(@Nullable Bundle savedInstanceState) { .......................; }
6、空指针指示型。
作用:指示某个参数、返回值等是否可以为空。
Annotations:@NonNull
@Nullable
使用场景:比如一个方法,可能返回一个null,可以使用@Nullable来标注,那么使用该方法的地方,一般都要做null判断。
7、混淆指示类型。
作用:指示被描述的对象,在编译的时候不要被混淆。
Annotations:@Keep
使用场景:当一个方法、属性、对象等,需要被反射使用时,比如通过反射来实现工厂。这时候可以使用该Annotation来标记,这样在编译的时候,就不会被混淆(如果开了混淆的话)。
示例代码:
//抽象产品,避免被混淆 @Keep class Product {} //产品A,避免被混淆 @Keep class ProductA extends Product{} //产品B,避免被混淆 @Keep class ProductB extends Product{} private Product getProduct(String productName) { //使用反射创建具体的Product return ...; }
8、参数长度限定类型。
作用:指示被描述的对象应该有明确的大小。
Annotations:@Size
使用场景:当一个参数,需要最小的大小时,使用该Annotation,比如,一个数组最少有2个元素、一个String最低长度为3等。
示例代码:
//要求param的长度最小为2 private void minSize(@Size(min = 2) String param) { } private void callMinSize() { //传递长度只有1的"s"时,会报错 minSize("s"); }
那么这个时候就会报错:Length must be at least 2 (was 1)。
9、其他类型。
作用:指示一个int值是颜色类型的值(AARRGGBB)。
Annotations:@ColorInt
示例代码:
//标明该常量代表颜色值 @ColorInt public static final int BLACK = 0xFF000000; //标明该方法希望接收一个代表颜色值的参数 public setColor(@ColorInt int color) { }