来自谷歌官方文档的翻译。原文地址
使用Lint等工具进行代码检查可以帮助你查找问题改善代码质量,但是这些工具只能进行推断并不能真正进行检查。因为Android使用一个int
类型作为ID,表示字符串,图片,颜色和其他类型的资源,这时如果你在一个需要使用颜色资源ID的地方使用了一个字符串资源ID,代码检查工具是不会报错的,但是你的应用仍然会发生绘制错误或者是不能运行。
注解(Annotations)就是让你给代码检查工具Lint等给一个提示,让它们注意检查这些微妙的代码问题。这些注解就好像元数据标签(mentadata tags)一样和变量,参数,返回值绑定在一起,检查它们是否合法。当运行代码检查工具的时候,注解就会帮助你检查比如空指针,资源类型冲突等问题。
Android通过Annotations Support Library支持多种注解,你可以通过android.support.annotation包获取这些注解。
在工程中添加注解
在工程中打开注解功能,添加support-annotations
依赖。你添加的任何注解都会代码检查工具运行时或者lint task
时进行检查。
添加注解功能的工程依赖
Support Annotations library是Android Support Repository的一部分。所以你必须先下载support repository
,然后在build.gradle
中添加support-annotations
依赖。
-
在工具栏中点击 SDK Manager
点击 SDK Tools 标签.
展开 Support Repository, 并选中 Android Support Repository。
点击 OK.
安装向导中点击 Continue 直到完成安装.
-
将以下代码添加到
build.gradle
文件中,完成添加support-annotations
依赖:dependencies { compile 'com.android.support:support-annotations:24.2.0' }
这里使用的版本可能低于你下载的版本,所以这里指定的版本号必须和你在第三步中下载的版本号保持一致。
最后点击工具栏或者通知中的 Sync Now。
如果你自己的库模块中使用了annotations,那么annotations就已经以XML的方式存在于AAR(Android Archive (AAR) artifact)文件的annotations.zip
中了。使用了你的库的用户就没有必要再以这种添加依赖的方式添加这个模块了。
如果你想用Gradle Java plugin这种方式代替默认的Android plugin for Gradle (com.android.application 或 com.android.library)
,那你必须明确指定SDK库的位置,因为Android支持库并不支持JCenter
repositories {
jcenter()
maven { url '<your-SDK-path>/extras/android/m2repository' }
}
注意:如果你使用了appcompat库,那你同样不需要添加 support-annotations
依赖,因为 appcompat
库已经添加过这个依赖了,你可以直接使用annotations。
Android支持库中完整的注解列表你可以通过Support Annotations library reference查询,或者利用代码补全功能,在输入了import android.support.annotation.
语句后出现的可用项中查看。
运行代码检查功能
在 Android Studio 的工具栏中选择 Analyze > Inspect Code ,启动代码检查功能,包括确认注解的有效性和Lint自动检查两部分。Android Studio会显示冲突信息,标记出在代码中潜藏的问题,并且给出相应的解决解决建议。
你也可以用命令行启动lint
任务,这对持续集成服务器发现问题很有帮助,但是要注意,这样启动的lint
任务并不能检查nullness
注解,只有Android Studio才能具备这个共轭能。关于Lint检查的的问题,请看使用Lint改善你的代码。
注意,即使注解冲突产生了警告,但是这些警告并不会组织代码的编译。
空值注解(Nullness Annotations)
@Nullable 注解表示变量,参数,返回值可以为null
, @NonNull 注解表示变量,参数,返回值不能为null
。
如果一个值为null
的变量,被传递到了一个参数被标记为@NonNull
的方法中,这时编译就会产生一个non-null
的警告;另外一方面,如果引用一个返回值被标记为@Nullable
的方法,并且你没有对返回结果进行是否为空的检查,那么就会受到一个nullness
的警告。只有当你想提醒方法的使用者,在每次使用方法前都要明确地进行非空检查时,才能使用@Nullable
注解标记方法的返回值。
下面的例子使用了@NonNull
注解标记了context
和parameters
两个参数,表示要检查传入的这两个参数的值是不能为空,同时还要检查onCreateView()
方法自身的返回值不能为空
import android.support.annotation.NonNull;
...
/** Add support for inflating the <fragment> tag. **/
@NonNull
@Override
public View onCreateView(String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
...
}
...
空值分析(Nullability Analysis)
Android Studio支持空值分析(nullability analysis)去自动推断并且在代买中插入空值注解(nullness annotations)。空值分析会扫描所有方法层次结构中的调用关系,去检查:
- 调用的方法可以返回空
- 调用的方法不能返回空
- 变量,字段,局部变量,参数等可以为空
- 变量,字段,局部变量,参数等不能为空
分析完后会在检查的位置自动插入适当的空值注解。
在Android Studio中选择Analyze > Infer Nullity
开启空值分析。Android Studio会在检测的地方插入Android版本的 @Nullable 和 @NonNull 注解。下面是一些很好的实践经验:
注意:当加入空值注解的时候,代码补全功能会建议我们使用 IntelliJ版本的 @Nullable and @NotNull 注解来代替Android版本的注解,同时也会自动引入相应的包。但是 Android Studio Lint 功能只会检查Android版本的注解。当确认你的注解的时候,一定要检查你的工程使用的是Android版本的注解,这样Lint功能才能正常运行。(PS:Android版本的是
@NonNull
,IntelliJ的是@NotNull
;@Nullable
写法是一样的)
资源注解(Resource Annotations)
确认资源类型非常有用,因为Android对于资源的引用,比如drawable,string,都是用整数类型进行传递的。如果代码期待接收特定的资源类型,比如Drawable,就可以把该资源引用的int值传递过去。但是实际上也有可能错传了一个R.string
资源的int值过去。所以确认资源的类型很有用。
我们可以添加了一个@StringRes注解,去检查参数的是不是一个R.string
类型的资源:
public abstract void setTitle(@StringRes int resId) { … }
如果参数不是一个R.string
类型的资源,那么在代码检查期间就会产生一个警告。
其他[@DrawableRes][drawableres],[@DimenRes][dimenres],[@ColorRes][colorres],[@InterpolatorRes][interpolatorres]等资源注解都可以按这种格式使用。如果你的参数支持多个资源格式,你可以对其添加多个资源注解。[@AnyRes][anyres]注解表示该菜蔬可以是任意一种R
资源格式。
即使你使用了[@ColorRes][colorres]指定了一个资源类型的参数,但是用RRGGBB
或 AARRGGBB
表示的颜色整数值却并不会被认可。同样用[@ColorInt][colorint]指定的资源也只认可能被解析的颜色整数值。编译工具会标记出这些不正确的代码。
[stringres]:
[drawableres]: https://developer.android.com/reference/android/support/annotation/DrawableRes.html
[dimenres]:https://developer.android.com/reference/android/support/annotation/DimenRes.html
[colorres]:https://developer.android.com/reference/android/support/annotation/ColorRes.html
[interpolatorres]:https://developer.android.com/reference/android/support/annotation/InterpolatorRes.html
[anyres]:https://developer.android.com/reference/android/support/annotation/AnyRes.html
[colorint]:https://developer.android.com/reference/android/support/annotation/ColorInt.html
线程注解(Thread Annotations)
线程注解用来检查方法是不是在一个特定的线程中被调用。支持以下注解
注意:编译工具将
@MainThread
和@UiThread
看成是可互换的,所以你可以在标注为@MainThread
的方法中调用@UiThread
的方法,反之亦然。但是,在不同线程上有多个视图的系统应用程序的情况下,UI线程可能不等同于主线程。因此,您应该使用@UiThread
注解与应用程序视图层次结构相关的方法,并使用@MainThread
注解仅与应用程序生命周期相关联的方法。
如果类中的所有方法都共享相同的线程,则可以向类添加单个线程注解,以验证类中的所有方法是否都从同一类型的线程中被调用。
线程注解的一个常见用法是验证AsyncTask类中被覆盖的方法,因为此类在后台执行,并仅在UI线程上发布结果。
值约束注解(Value Constraint Annotations)
使用@IntRange,@FloatRange和@Size注解验证传进来的参数的值。当用户可能输入不在范围内的参数时,@IntRange
和@FloatRange
非常有用。
@IntRange
注解验证整数或长整数参数的值是否在指定范围内。以下示例确保alpha
参数包含从0到255的整数值:
public void setAlpha(@IntRange(from=0,to=255) int alpha) { … }
@FloatRange
注解检查float
或double
类型参数值是否在浮点值的指定范围内。以下示例确保alpha
参数包含从0.0到1.0的浮点值:
public void setAlpha(@FloatRange(from=0.0, to=1.0) float alpha) {...}
@Size
注解检查集合或数组的大小,以及字符串的长度。 @Size
注解可用于验证以下数量:
- 最大尺寸 (如 @Size(min=2))
- 最小尺寸 (如 @Size(max=2))
- 精确尺寸 (如 @Size(2))
- 尺寸的倍数(如 @Size(multiple=2))
例如,@Size(min=1)
检查集合是否为空,@Size(3)
验证数组是否包含有三个值。以下示例确保局部数组变量至少包含一个元素:
int[] location = new int[3];
button.getLocationOnScreen(@Size(min=1) location);
权限注解(Permission Annotations)
使用@RequiresPermission注解来验证方法调用者的权限。要从列表中检查单个权限的有效权限,请使用anyOf
属性。 要检查一组权限,请使用allOf
属性。以下示例注解setWallpaper()
方法,以确保方法的调用者具有permission.SET_WALLPAPERS
权限:
@RequiresPermission(Manifest.permission.SET_WALLPAPER)
public abstract void setWallpaper(Bitmap bitmap) throws IOException;
此示例要求copyFile()
方法的调用者拥有读写外部存储的权限:
@RequiresPermission(allOf = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE})
public static final void copyFile(String dest, String source) {
...
}
对于意图(intents)的权限,将权限要求放置在定义意图操作名称的字符串字段上:
@RequiresPermission(android.Manifest.permission.BLUETOOTH)
public static final String ACTION_REQUEST_DISCOVERABLE =
"android.bluetooth.adapter.action.REQUEST_DISCOVERABLE";
对于需要对读写访问具有单独权限的内容提供程序(content providers)的权限,请在@RequiresPermission.Read 或 @RequiresPermission.Write注解中包含每个权限要求:
@RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS))
@RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS))
public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks");
间接权限(Indirect Permissions)
当权限取决于提供给方法参数的特定值时,只对该参数使用@RequiresPermission
注解,而不用列出特定的权限。 例如,startActivity(Intent) 方法对传递给方法的intent
参数就使用了间接权限:
public abstract void startActivity(@RequiresPermission Intent intent, @Nullable Bundle) {...}
当你使用间接权限时,构建工具会执行数据流分析,以检查传递给方法的参数是否有@RequiresPermission
注解。 然后他们从方法本身的参数强制执行任何现有注解。 在startActivity(Intent)
示例中,当没有适当权限的意图传递给方法时,Intent类中的注解导致对startActivity(Intent)
的无效使用的结果警告,如图1所示。
构建工具从Intent类中相应意图操作名称上的注解在startActivity(Intent)
上生成警告:
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@RequiresPermission(Manifest.permission.CALL_PHONE)
public static final String ACTION_CALL = "android.intent.action.CALL";
有时,您可以在注解方法的参数时,用@RequiresPermission
替换@RequiresPermission.Read
和@RequiresPermission.Write
。 但是,对于间接权限,@RequiresPermission
不应与读取或写入权限注解混合使用。
返回值注解(Return Value Annotations)
使用@CheckResult注解来验证方法的结果或返回值实际上是否被使用。在容易混淆的方法结果上添加@CheckResult
注解用以区分,而不是对每个非void方法都进行注解。在容易混淆的方法结果上添加@CheckResult
注解用以区分,而不是对每个非void方法都进行注解。 例如,Java开发�新手经常错误地认为<String>.trim()
是从原始字符串中删除所有空格。对方法使用包含<String>.trim()
的@CheckResult
注解,�这样调用者就不用对方法的返回值�进行任何操作了。
以下示例中,@CheckResult
注解了[checkPermissions()](https://developer.android.com/reference/android/content/pm/PackageManager.html#checkPermission(java.lang.String, java.lang.String))方法,以确保方法的返回值实际被引用。 它还建议开发人员将[enforcePermission()](https://developer.android.com/reference/android/content/ContextWrapper.html#enforcePermission(java.lang.String, int, int, java.lang.String))方法作为一种替代方案:
@CheckResult(suggest="#enforcePermission(String,int,int,String)")
public abstract int checkPermission(@NonNull String permission, int pid, int uid);
调用父类方法注解(CallSuper Annotations)
使用@CallSuper注解来验证该方法是否调用了被覆盖的父类�方法。以下示例中,onCreate()
方法使用了该注解,以确保任何覆盖了这个方法的实现都必须调用super.onCreate)
:
@CallSuper
protected void onCreate(Bundle savedInstanceState) {
}
类型定义注解(Typedef Annotations)
你可以使用@IntDef和@StringDef注解,来创建一个整数或者字符串集合的枚举类型。Typedef注解确保特定参数,返回值或字段只能使用特定的一组常量。它们还可以使代码拥有自动补全功能。
Typedef注解使用@interface
声明新的枚举注解类型。@IntDef
和@StringDef
以及@Retention
注解一起标注新的注解它,并且它们三个是定义一个枚举类型所必需的。@Retention(RetentionPolicy.SOURCE)
注解告诉编译器不要将被标记了的枚举类型数据存储在.class
文件中。
以下示例说明了创建这种注解的步骤,以确保作为方法参数传递的值是一组已经定义好的常量集合中的一个:
import android.support.annotation.IntDef;
...
public abstract class ActionBar {
...
// Define the list of accepted constants and declare the NavigationMode annotation
@Retention(RetentionPolicy.SOURCE)
@IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
public @interface NavigationMode {}
// Declare the constants
public static final int NAVIGATION_MODE_STANDARD = 0;
public static final int NAVIGATION_MODE_LIST = 1;
public static final int NAVIGATION_MODE_TABS = 2;
// Decorate the target methods with the annotation
@NavigationMode
public abstract int getNavigationMode();
// Attach the annotation
public abstract void setNavigationMode(@NavigationMode int mode);
如果mode
参数的值不是已经定义的(NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, or NAVIGATION_MODE_TABS)
其中的一个,编译时就会受到警告。
你还可以将 @IntDef 和 @IntRange 一起使用,用来限定一个整数值既是给定的一组常量中的一个,同时也是一个给定范围内的值。
将常量和标志位一起使用(Enable combining constants with flags)
如果用户想将有效的常量与标志位(例如|
, &
, ^
等)组合使用,你可以结合flag
属性定义这个注解,以检查参数或返回值是否是合法的样式。以下示例用一系列DISPLAY_
常数创建了DisplayOptions
注解:
import android.support.annotation.IntDef;
...
@IntDef(flag=true, value={
DISPLAY_USE_LOGO,
DISPLAY_SHOW_HOME,
DISPLAY_HOME_AS_UP,
DISPLAY_SHOW_TITLE,
DISPLAY_SHOW_CUSTOM
})
@Retention(RetentionPolicy.SOURCE)
public @interface DisplayOptions {}
...
当你使用注解标志构建代码时,如果修饰的参数或返回值不是有效的样式时,则会生成警告。
代码访问权限注解(Code Accessibility Annotations)
使用@VisibleForTesting和@Keep注解来表示方法,类或字段的可访问性。
@VisibleForTesting
注解表示,代码测试时候,被注解了的这段代码比其所声明的,有更大的可见性。(比如声明为private
,测试时就变成了public
)。
@Keep
注解确保被标注的元素在编译代码中压缩代码资源的时候不会被删除。这个标签的典型应用就是添加在要被反射调用(reflection)的类或者方法上面,确保编译器不会把这些方法或者类当做是无用的资源而被优化掉(删除)。