最近在看一些开源项目的源码,发现了Android中的一些很有意思的注解,于是归纳总结了一下,以后在自己的项目中也可以尝试使用。
首先,需要在gradle的dependencies中加入
compile 'com.android.support:support-annotations:25.2.0'
当前的最新版本是25.2.0
Android注解有8种类型,分别是Nullness注解、资源类型注解、线程注解、变量限制注解、权限注解、结果检查注解、CallSuper注解、枚举注解。
Nullness注解
- @NonNull
@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD, ANNOTATION_TYPE, PACKAGE})
public @interface NonNull {
}
- @Nullable
@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD, ANNOTATION_TYPE, PACKAGE})
public @interface Nullable {
}
从名字上就可以很明显的看出,@NonNull表示这个参数、变量等不能为空,而@Nullable则表示可以为空,举个例子:
private void test(@NonNull String test) {
}
如果我们有这样的一个函数,用@NonNull注解表示参数不能为空,如果我们这样调用这个函数
test(null);
我们会得到这样的警告提示,告诉我们这个参数被注解为@NonNull
如果我们将这个函数改为:
private void testNonNull(@Nullable String test) {
}
或者没有任何注解时,就没有提示了。
资源类型注解
所有以“Res”结尾的注解,都是资源类型注解。大概包括:@AnimatorRes、@AnimRes、@AnyRes、@ArrayRes、@AttrRes、@BoolRes、@ColorRes、@DimenRes、@DrawableRes、@FractionRes、@IdRes、@IntRes、@IntegerRes、@InterpolatorRes、@LayoutRes、@MenuRes、@PluralsRes、@RawRes、@StringRes、@StyleableRes、@StyleRes、@TransitionRes、@XmlRes
使用方法也都是类似的,这里举个例子:
@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
public @interface StringRes {
}
private String getStringById(@StringRes int stringId) {
return getResources().getString(stringId);
}
如果我们这样写String name = getStringById(R.string.app_name);
是不会有问题的,但是团队中的其他小伙伴在调用的时候写错了怎么办?比如String name = getStringById(R.layout.activity_main);
如果@StringRes注解,我们看不到任何的提醒,而运行时就会报错,但是如果加上了@StringRes注解,我们就可以看到这样的错误提示:
线程注解
包括@AnyThread、@UiThread和@WorkerThread,表明需要运行在什么线程上。
@Documented
@Retention(CLASS)
@Target({METHOD,CONSTRUCTOR,TYPE})
public @interface WorkerThread {
}
例如有一个函数,需要做一些耗时操作,我们希望它不要运行在主线程上
@WorkerThread
private void testThread() {
// 耗时操作
}
如果我们在主线程中调用这个函数会怎么样呢?
而如果这样调用就不会有问题,这样就保证了我们这个耗时操作不会执行在主线程中。
new Thread(new Runnable() {
public void run() {
testThread();
}
}).start();
变量限制注解
变量限制注解主要包含@IntRange和@FloatRange两种,使用方法类似,都是限制了范围,这里以@IntRange为例。
@Retention(CLASS)
@Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE})
public @interface IntRange {
/** Smallest value, inclusive */
long from() default Long.MIN_VALUE;
/** Largest value, inclusive */
long to() default Long.MAX_VALUE;
}
源码中可以看到,这里包含了from()
和to()
,默认值分别是Long的最小值Long.MIN_VALUE
和Long的最大值Long.MAX_VALUE
。
例如我们有个方法,需要限制输入的范围,我可以这样写:
private void testRange(@IntRange(from = 1, to = 10) int number) {
}
如果调用者输入了一个超出范围的值时,会这样提示他。
权限注解
如果我们有方法需要使用某种权限,可以加上@RequiresPermission这个注解。
@RequiresPermission(Manifest.permission.CALL_PHONE)
private void testPermission() {
}
比如这里需要打电话的权限,但是我并没有在应用中加入该权限。
当我们调用函数时,就会有这样的错误提示。好吧,那我把权限加上,发现还是有错误提示。
没有错,AS就是这么强大,会告诉我们这个权限可能会被用户拒绝,所以我们应该在代码中对这个权限进行检查。
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
testPermission();
这样就没有问题了。
结果检查注解
如果我们写了一个有返回值的函数,并且我们希望调用者对这个返回值进行使用或者检查。这个时候@CheckResult注解就派上用场了。
@Documented
@Retention(CLASS)
@Target({METHOD})
public @interface CheckResult {
/** Defines the name of the suggested method to use instead, if applicable (using
* the same signature format as javadoc.) If there is more than one possibility,
* list them all separated by commas.
*
* For example, ProcessBuilder has a method named {@code redirectErrorStream()}
* which sounds like it might redirect the error stream. It does not. It's just
* a getter which returns whether the process builder will redirect the error stream,
* and to actually set it, you must call {@code redirectErrorStream(boolean)}.
* In that case, the method should be defined like this:
*
* @CheckResult(suggest="#redirectErrorStream(boolean)")
* public boolean redirectErrorStream() { ... }
*
*/
String suggest() default "";
}
比如有这样一个方法,返回了String。
@CheckResult
private boolean testCheckResult() {
return true;
}
如果我们不关心他的返回值。
提示我们结果没有被使用。如果改为
boolean result = testCheckResult();
就不会有问题了。@CheckResult注解保证了我们方法的返回值一定会得到使用。
CallSuper注解
如果我们有一个父类Father,有一个方法display(),有一个子类Child继承了Father,并重写了display()方法,并不会有任何问题。
class Father {
public void display() {
Log.i(TAG, "display: Father");
}
}
class Child extends Father {
@Override
public void display() {
Log.i(TAG, "display: Child");
}
}
但是,如果我想让子类Child在调用display()方式也将父类Father的某些信息一起打印出来怎么办?没错,在子类的display()方法中调用super.display();
即可。那么,我们怎么保证子类就一定会调用super的方法呢?@CallSuper注解登场。
@Documented
@Retention(CLASS)
@Target({METHOD})
public @interface CallSuper {
}
@CallSuper注解表示重写的方法必须调用父类的方法,注意,这里的Target只有METHOD,并没有CONSTRUCTOR,所以构造函数是不能使用这个注解的。
还是刚才的例子,如果我们在父类的方法上加上@CallSuper注解,这个时候子类中重写的方法就会提示这样的错误。
这样就提醒我们需要加上super的方法。
枚举注解
Android官方强烈建议不要在Android程序里面使用到enum,官方的Training课程里面有下面这样一句话:Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.
既然官方都这样说了,那就尽量不要使用枚举了,可是不使用枚举使用什么呢?没错,静态常量。
举个例子,比如我自己写了一个提示框,需要提示用户一些信息,所以这样写:
public class MyTip {
public static void show(String message) {
// 显示提示框
}
}
我希望这个提示框在显示一定时间后自动关掉,所以定义了两个静态常量,一个长时间一个短时间,并且作为show方法的一个参数。
public class MyTip {
public static final int LONG_TIME = 0;
public static final int SHORT_TIME = 1;
public static void show(String message, int type) {
// 显示提示框
}
}
我可以这样让我的提示框显示一个较长的时间。
MyTip.show("message", MyTip.LONG_TIME);
但是有个问题,这里我传入的参数是MyTip.LONG_TIME
,但是实际上不管传入的是1还是0,甚至是MyTip.show("message", 2);
都不会提示错误,因为只要是int就可以,这显示不是我想要的。这里我们就需要用到枚举注解了,@IntDef和@StringDef
@Retention(SOURCE)
@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;
}
这时候我再修改一下代码
public class MyTip {
public static final int LONG_TIME = 0;
public static final int SHORT_TIME = 1;
@IntDef({LONG_TIME, SHORT_TIME})
@Retention(RetentionPolicy.SOURCE)
public @interface Type {}
public static void show(String message, @Type int type) {
// 显示提示框
}
}
这里自己写了一个注解@Type,并且使用了@IntDef注解,value就是两个静态常量。这时候再看调用的地方。
是不是非常熟悉?没错,我们熟悉的Toast就是用@IntDef注解这么实现的。感兴趣的可以去看看源码。
总结
发现注解是一个非常有意思的东西,他可以让我们在很早的时候就发现问题,团队协作起来也更有效率,所以做了一些总结,希望以后在项目中能够多多用到注解。