使用代码检查工具(例如 lint)可以帮助您查找问题并改进代码,但检查工具只能推断出有限的信息。例如,Android 资源 ID 使用 int
来标识字符串、图形、颜色和其他资源类型,因此检查工具无法判断何时指定了应指定颜色的字符串资源。这种情况意味着,即使使用代码检查,您的应用也可能会渲染不正确或完全无法运行。
注释让您可以为代码检查工具(例如 lint)提供提示,以帮助检测这些更细微的代码问题。注释作为元数据标签添加,您将它们附加到变量、参数和返回值,以检查方法返回值、传递的参数、局部变量和字段。与代码检查工具一起使用时,注释可以帮助您检测诸如空指针异常和资源类型冲突之类的问题。
Android 通过 Jetpack 注解库 支持多种注解。您可以通过 androidx.annotation
包访问该库。
注意: 如果一个模块依赖于注解处理器,则必须使用 Kotlin 的 kapt
或 ksp
依赖配置,或者使用 Java 的 annotationProcessor
依赖配置来添加该依赖项。
在您的项目中添加注解
要在您的项目中启用注解,请将 androidx.annotation:annotation
依赖项添加到您的库或应用中。当您运行代码检查或 lint
任务时,会检查您添加的所有注解。
添加 Jetpack 注解库依赖项
Jetpack 注解库发布在 Google 的 Maven 仓库 上。要将 Jetpack 注解库添加到您的项目,请在您的 build.gradle
或 build.gradle.kts
文件的 dependencies
块中包含以下行
Kotlin
dependencies { implementation("androidx.annotation:annotation:1.8.2") }
Groovy
dependencies { implementation 'androidx.annotation:annotation:1.8.2' }
如果您在自己的库模块中使用注解,则这些注解将作为 Android 归档 (AAR) 工件的一部分以 XML 格式包含在 annotations.zip
文件中。添加 androidx.annotation
依赖项不会为您的库的任何下游用户引入依赖项。
注意: 如果您正在使用其他 Jetpack 库,则可能不需要添加 androidx.annotation
依赖项。由于许多其他 Jetpack 库都依赖于注解库,因此您可能已经可以使用这些注解。
要查看 Jetpack 存储库中包含的注解的完整列表,请参阅 Jetpack 注解库参考,或使用自动完成功能显示 import androidx.annotation.
语句的可用选项。
运行代码检查
要从 Android Studio 启动代码检查(包括验证注解和自动 lint 检查),请从菜单中选择分析 > 检查代码。Android Studio 将显示冲突消息,以标记您的代码与注解冲突的潜在问题,并建议可能的解决方案。
您还可以通过 使用命令行运行 lint
任务 来强制执行注解。虽然这可能有助于标记持续集成服务器的问题,但 lint
任务不会强制执行空值注解(在下一节中描述);只有 Android Studio 会执行此操作。有关启用和运行 lint 检查的更多信息,请参阅 使用 lint 检查改进您的代码.
虽然注解冲突会生成警告,但这些警告不会阻止您的应用编译。
空值注解
空值注解在 Java 代码中很有用,可以强制执行值是否可以为空。它们在 Kotlin 代码中不太有用,因为 Kotlin 有内置的空值规则,这些规则在编译时强制执行。添加 @Nullable
和 @NonNull
注解以检查给定变量、参数或返回值的空值。 @Nullable
注解表示可以为空的变量、参数或返回值。 @NonNull
表示不可为空的变量、参数或返回值。
例如,如果包含空值的局部变量作为参数传递给附加了 @NonNull
注解的该参数的方法,则构建代码会生成警告,指示非空冲突。此外,尝试在不首先检查结果是否为空的情况下引用带有 @Nullable
标记的方法的结果会生成空值警告。仅当方法的每次使用都必须明确进行空值检查时,才在方法的返回值上使用 @Nullable
。
以下示例演示了空值的使用。Kotlin 示例代码没有利用 @NonNull
注解,因为在指定不可为空类型时,它会自动添加到生成的字节码中。Java 示例利用 @NonNull
注解在 context
和 attrs
参数上检查传递的参数值不为空。它还检查 onCreateView()
方法本身不返回 null
Kotlin
... /** Annotation not used because of the safe-call operator(?)**/ override fun onCreateView( name: String?, context: Context, attrs: AttributeSet ): View? { ... } ...
Java
import androidx.annotation.NonNull; ... /** Add support for inflating the <fragment> tag. **/ @NonNull @Override public View onCreateView(String name, @NonNull Context context, @NonNull AttributeSet attrs) { ... } ...
空值分析
Android Studio 支持运行空值分析,以自动推断并在您的代码中插入空值注解。空值分析会扫描代码中整个方法层次结构中的契约,以检测
- 调用可能返回 null 的方法。
- 不应返回 null 的方法。
- 变量(如字段、局部变量和参数),这些变量可能为 null。
- 变量(如字段、局部变量和参数),这些变量不能保存 null 值。
然后,分析会自动在检测到的位置插入相应的空值注解。
要在 Android Studio 中运行空值分析,请选择分析 > 推断空值。Android Studio 会在您的代码中检测到的位置插入 Android @Nullable
和 @NonNull
注解。运行空值分析后,建议您验证注入的注解。
注意: 添加空值注解时,自动完成可能会建议 IntelliJ @Nullable
和 @NotNull
注解,而不是 Android 空值注解,并且可能会自动导入相应的库。但是,Android Studio lint 检查器只查找 Android 空值注解。验证注解时,请确认您的项目使用的是 Android 空值注解,以便 lint 检查器能够在代码检查期间正确通知您。
资源注解
验证资源类型很有用,因为对资源(如 可绘制 和 字符串 资源)的 Android 引用作为整数传递。
期望参数引用特定类型资源的代码(如 String
)可以传递给预期引用类型 int
,但实际上引用的是不同类型的资源,例如 R.string
资源。
例如,添加 @StringRes
注解以检查资源参数是否包含 R.string
引用,如下所示
Kotlin
abstract fun setTitle(@StringRes resId: Int)
Java
public abstract void setTitle(@StringRes int resId)
在代码检查期间,如果参数中没有传递 R.string
引用,则注解会生成警告。
可以使用相同的注解格式添加其他资源类型的注解,例如 @DrawableRes
、@DimenRes
、@ColorRes
和 @InterpolatorRes
,并在代码检查期间运行。
如果您的参数支持多种资源类型,可以在给定参数上放置多个资源类型注解。使用 @AnyRes
表示带注解的参数可以是任何类型的 R
资源。
虽然您可以使用 @ColorRes
指定参数应该是颜色资源,但颜色整数(RRGGBB
或 AARRGGBB
格式)不被识别为颜色资源。相反,使用 @ColorInt
注解表示参数必须是颜色整数。构建工具会标记将颜色资源 ID(如 android.R.color.black
)而不是颜色整数传递给带注解方法的错误代码。
线程注解
线程注解会检查方法是否从特定类型的 线程 调用。支持以下线程注解
构建工具将 @MainThread
和 @UiThread
注解视为可互换的,因此您可以从 @MainThread
方法调用 @UiThread
方法,反之亦然。但是,在具有不同线程上的多个视图的系统应用的情况下,UI 线程可能与主线程不同。因此,您应该使用 @UiThread
注解与应用视图层次结构关联的方法,并且仅使用 @MainThread
注解与应用生命周期关联的方法。
如果类中的所有方法都共享相同的线程要求,则可以向类添加单个线程注解,以验证类中的所有方法是否都从相同类型的线程调用。
线程注解的常见用法是验证使用 @WorkerThread
注解的方法或类是否仅从适当的后台线程调用。
值约束注解
使用 @IntRange
、@FloatRange
和 @Size
注解以验证传递的参数的值。当应用于用户很可能弄错范围的参数时,@IntRange
和 @FloatRange
最有用。
@IntRange
注解会验证整数或长整型参数值是否在指定范围内。以下示例表示 alpha
参数必须包含 0 到 255 之间的整数。
Kotlin
fun setAlpha(@IntRange(from = 0, to = 255) alpha: Int) { ... }
Java
public void setAlpha(@IntRange(from=0,to=255) int alpha) { ... }
@FloatRange
注解会检查浮点型或双精度浮点型参数值是否在指定的浮点值范围内。以下示例表示 alpha
参数必须包含 0.0 到 1.0 之间的浮点值。
Kotlin
fun setAlpha(@FloatRange(from = 0.0, to = 1.0) alpha: Float) {...}
Java
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)
会验证数组是否正好包含三个值。
以下示例表示 location
数组必须至少包含一个元素。
Kotlin
fun getLocation(button: View, @Size(min=1) location: IntArray) { button.getLocationOnScreen(location) }
Java
void getLocation(View button, @Size(min=1) int[] location) { button.getLocationOnScreen(location); }
权限注解
使用 @RequiresPermission
注解以验证方法调用的权限。要检查有效权限列表中的单个权限,请使用 anyOf
属性。要检查一组权限,请使用 allOf
属性。以下示例使用注解标记 setWallpaper()
方法,以指示该方法的调用者必须具有 permission.SET_WALLPAPERS
权限。
Kotlin
@RequiresPermission(Manifest.permission.SET_WALLPAPER) @Throws(IOException::class) abstract fun setWallpaper(bitmap: Bitmap)
Java
@RequiresPermission(Manifest.permission.SET_WALLPAPER) public abstract void setWallpaper(Bitmap bitmap) throws IOException;
以下示例要求 copyImageFile()
方法的调用者同时具有对外部存储的读取访问权限和对复制图像中位置元数据的读取访问权限。
Kotlin
@RequiresPermission(allOf = [ Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.ACCESS_MEDIA_LOCATION ]) fun copyImageFile(dest: String, source: String) { ... }
Java
@RequiresPermission(allOf = { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.ACCESS_MEDIA_LOCATION}) public static final void copyImageFile(String dest, String source) { //... }
要为意图添加权限,请将权限要求放在定义意图操作名称的字符串字段上。
Kotlin
@RequiresPermission(android.Manifest.permission.BLUETOOTH) const val ACTION_REQUEST_DISCOVERABLE = "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE"
Java
@RequiresPermission(android.Manifest.permission.BLUETOOTH) public static final String ACTION_REQUEST_DISCOVERABLE = "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE";
对于需要对读写访问权限进行单独设置的内容提供者的权限,请将每个权限要求用 @RequiresPermission.Read
或 @RequiresPermission.Write
注释包装。
Kotlin
@RequiresPermission.Read(RequiresPermission(READ_HISTORY_BOOKMARKS)) @RequiresPermission.Write(RequiresPermission(WRITE_HISTORY_BOOKMARKS)) val BOOKMARKS_URI = Uri.parse("content://browser/bookmarks")
Java
@RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS)) @RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS)) public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks");
间接权限
如果权限依赖于传递给方法参数的特定值,请在参数本身使用 @RequiresPermission
注释,而无需列出特定权限。例如,startActivity(Intent)
方法对传递给该方法的意图使用间接权限。
Kotlin
abstract fun startActivity(@RequiresPermission intent: Intent, bundle: Bundle?)
Java
public abstract void startActivity(@RequiresPermission Intent intent, @Nullable Bundle)
当使用间接权限时,构建工具会执行数据流分析以检查传递给方法的参数是否具有任何 @RequiresPermission
注释。然后,它们会在方法本身强制执行从参数中存在的任何注释。在 startActivity(Intent)
示例中,Intent
类中的注释会导致对 startActivity(Intent)
的无效使用产生警告,当传递给该方法的意图没有适当的权限时,如图 1 所示。
构建工具从 Intent
类中相应的意图操作名称上的注释生成 startActivity(Intent)
上的警告。
Kotlin
@RequiresPermission(Manifest.permission.CALL_PHONE) const val ACTION_CALL = "android.intent.action.CALL"
Java
@RequiresPermission(Manifest.permission.CALL_PHONE) public static final String ACTION_CALL = "android.intent.action.CALL";
如果需要,可以在注释方法参数时用 @RequiresPermission
替换 @RequiresPermission.Read
或 @RequiresPermission.Write
。但是,对于间接权限,@RequiresPermission
不应与读或写权限注释一起使用。
返回值注释
使用 @CheckResult
注释来验证方法的结果或返回值是否实际使用。不必用 @CheckResult
注释每个非 void 方法,而是添加该注释以阐明可能令人困惑的方法的结果。
例如,Java 新手经常错误地认为 <String>.trim()
会从原始字符串中删除空格。用 @CheckResult
注释该方法会标记 <String>.trim()
的使用情况,其中调用者对该方法的返回值不做任何处理。
以下示例注释了 checkPermissions()
方法以检查该方法的返回值是否实际引用。它还将 enforcePermission()
方法命名为建议开发者使用的替代方法。
Kotlin
@CheckResult(suggest = "#enforcePermission(String,int,int,String)") abstract fun checkPermission(permission: String, pid: Int, uid: Int): Int
Java
@CheckResult(suggest="#enforcePermission(String,int,int,String)") public abstract int checkPermission(@NonNull String permission, int pid, int uid);
CallSuper 注释
使用 @CallSuper
注释来验证重写方法是否调用了该方法的超类实现。
以下示例注释了 onCreate()
方法以确保任何重写方法实现都调用了 super.onCreate()
。
Kotlin
@CallSuper override fun onCreate(savedInstanceState: Bundle?) { }
Java
@CallSuper protected void onCreate(Bundle savedInstanceState) { }
Typedef 注释
Typedef 注释检查特定参数、返回值或字段是否引用特定常量集。它们还允许代码完成自动提供允许的常量。
使用 @IntDef
和 @StringDef
注释来创建整数和字符串集的枚举注释以验证其他类型的代码引用。
Typedef 注释使用 @interface
声明新的枚举注释类型。@IntDef
和 @StringDef
注释以及 @Retention
会注释新注释,对于定义枚举类型是必要的。 @Retention(RetentionPolicy.SOURCE)
注释告诉编译器不要将枚举注释数据存储在 .class
文件中。
以下示例显示了创建注释的步骤,该注释检查作为方法参数传递的值是否引用了定义的常量之一。
Kotlin
import androidx.annotation.IntDef //... // Define the list of accepted constants and declare the NavigationMode annotation. @Retention(AnnotationRetention.SOURCE) @IntDef(NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS) annotation class NavigationMode // Declare the constants. const val NAVIGATION_MODE_STANDARD = 0 const val NAVIGATION_MODE_LIST = 1 const val NAVIGATION_MODE_TABS = 2 abstract class ActionBar { // Decorate the target methods with the annotation. // Attach the annotation. @get:NavigationMode @setparam:NavigationMode abstract var navigationMode: Int }
Java
import androidx.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
或 NAVIGATION_MODE_TABS
),则会生成警告。
将 @IntDef
和 @IntRange
组合起来以指示整数可以是给定的常量集或某个范围内的值。
启用将常量与标志组合
如果用户可以使用标志(例如 |
、&
、^
等)组合允许的常量,则可以使用带有 flag
属性的注释来检查参数或返回值是否引用了有效模式。
以下示例创建了带有有效 DISPLAY_
常量列表的 DisplayOptions
注释。
Kotlin
import androidx.annotation.IntDef ... @IntDef(flag = true, value = [ DISPLAY_USE_LOGO, DISPLAY_SHOW_HOME, DISPLAY_HOME_AS_UP, DISPLAY_SHOW_TITLE, DISPLAY_SHOW_CUSTOM ]) @Retention(AnnotationRetention.SOURCE) annotation class DisplayOptions ...
Java
import androidx.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 {} ...
在用带有注释标志的代码构建代码时,如果装饰的参数或返回值没有引用有效模式,则会生成警告。
保留注释
@Keep
注释可确保在构建时代码缩小时不会删除已注释的类或方法。此注释通常添加到通过反射访问的方法和类,以防止编译器将代码视为未使用。
注意:使用 @Keep
注释的类和方法始终出现在应用程序的 APK 中,即使在应用程序逻辑中从未引用这些类和方法。
要使应用程序的大小保持较小,请考虑是否需要在应用程序中保留每个 @Keep
注释。如果使用反射来访问已注释的类或方法,请在 ProGuard 规则中使用 -if
条件,指定进行反射调用的类。
有关如何缩小代码并指定不应删除哪些代码的更多信息,请参阅 缩小、混淆和优化应用程序。
代码可见性注释
使用以下注释来表示代码特定部分的可见性,例如方法、类、字段或包。
使代码对测试可见
@VisibleForTesting
注释表示已注释的方法比通常需要的可见性更高,以便使该方法可测试。此注释有一个可选的 otherwise
参数,该参数允许您指定如果不需要使其对测试可见,该方法的可见性是什么。Lint 使用 otherwise
参数来强制执行预期可见性。
在以下示例中,myMethod()
通常是 private
,但它对测试是 package-private
。使用 VisibleForTesting.PRIVATE
指定,如果从 private
访问允许的上下文之外(例如,从不同的编译单元)调用此方法,Lint 会显示一条消息。
Kotlin
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) fun myMethod() { ... }
Java
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) void myMethod() { ... }
您还可以指定 @VisibleForTesting(otherwise = VisibleForTesting.NONE)
来表示方法仅存在于测试中。此形式与使用 @RestrictTo(TESTS)
相同。它们都执行相同的 Lint 检查。
限制 API
@RestrictTo
注释表示对已注释 API(包、类或方法)的访问有限制,如下所示。
子类
使用注释形式 @RestrictTo(RestrictTo.Scope.SUBCLASSES)
将 API 访问限制为仅限子类。
只有扩展已注释类的类才能访问此 API。Java protected
修饰符不够严格,因为它允许从同一包中不相关的类访问。此外,在某些情况下,您可能希望将方法保留为 public
以便将来灵活使用,因为您永远无法将以前是 protected
并被重写的方法变为 public
,但您想提供一个提示,即该类仅供在类内部或从子类中使用。
库
使用注释形式 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
将 API 访问限制为仅限您的库。
只有您的库代码才能访问已注释的 API。这使您不仅可以按照所需的任何包层次结构来组织代码,还可以在一组相关的库之间共享代码。Jetpack 库已经可以使用此选项,这些库具有大量不供外部使用的实现代码,但必须是 public
才能在各种补充 Jetpack 库之间共享。
测试
使用注释形式 @RestrictTo(RestrictTo.Scope.TESTS)
来防止其他开发者访问您的测试 API。
只有测试代码才能访问已注释的 API。这将防止其他开发者使用您专门用于测试目的的 API 进行开发。