【注解】巧用Android注解提高代码质量
依赖
dependencies {
implementation 'com.android.support:support-annotations:28.0.0'
}
注解项如下:
- @NonNull
- @Nullable
- @StringRes
- @DrawableRes
- @DimenRes
- @ColorRes
- @InterpolatorRes
- @AnyRes
- @ColorInt
- @MainThread
- @WorkerThread
- @BinderThread
- @UiThread
- @AnyThread
- @IntRange
- @FloatRange
- @Size
- @RequiresPermission
- @CheckResult
- @CallSuper
- @IntDef
- @StringDef
- @Keep
- @VisibleForTesting
- @RestrictTo
- @SdkConstant
- @Override
@Override
覆盖父类方法
@Override是伪代码,表示重写(当然不写也可以),不过写上有如下好处:
1、可以当注释用,方便阅读;
2、编译器可以给你验证@Override下面的方法名是否是你父类中所有的,如果没有则报错。例如,你如果没写@Override,而你下面的方法名又写错了,这时你的编译器是可以编译通过的,因为编译器以为这个方法是你的子类中自己增加的方法。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_banner_view_pager_2);
initViews();
initData();
setSelectedIndicator(0);
}
源码如下:
package java.lang;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
ElementType
package java.lang.annotation;
public enum ElementType {
TYPE,
FIELD,
METHOD,
PARAMETER,
CONSTRUCTOR,
LOCAL_VARIABLE,
ANNOTATION_TYPE,
PACKAGE,
TYPE_PARAMETER,
TYPE_USE;
private ElementType() {
}
}
RetentionPolicy
package java.lang.annotation;
public enum RetentionPolicy {
SOURCE,
CLASS,
RUNTIME;
private RetentionPolicy() {
}
}
@Target
注解的作用目标
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE})
public @interface Target {
ElementType[] value();
}
@Target(ElementType.TYPE) //接口、类、枚举
@Target(ElementType.FIELD) //字段、枚举的常量
@Target(ElementType.METHOD) //方法
@Target(ElementType.PARAMETER) //方法参数
@Target(ElementType.CONSTRUCTOR) //构造函数
@Target(ElementType.LOCAL_VARIABLE)//局部变量
@Target(ElementType.ANNOTATION_TYPE)//注解
@Target(ElementType.PACKAGE) ///包
@Retention
注解的保留位置
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE})
public @interface Retention {
RetentionPolicy value();
}
@Retention(RetentionPolicy.SOURCE) //注解仅存在于源码中,在class字节码文件中不包含
@Retention(RetentionPolicy.CLASS) // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得,
@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Documented
说明该注解将被包含在javadoc中
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE})
public @interface Documented {
}
java中元注解有四个: @Retention @Target @Document @Inherited;
@Inherited:说明子类可以继承父类中的该注解
@NonNull
添加 @Nullable 和 @NonNull 注解,以检查给定变量、参数或返回值的 null 性。@Nullable 注解用于指明可以为 null 的变量、参数或返回值,而 @NonNull 则用于指明不可以为 null 的变量、参数或返回值。
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) {
...
}
...
Android Studio 支持运行 null 性分析,以在代码中自动推断和插入 null 性注解。null 性分析会在代码的整个方法层次结构中扫描协定元素,以检测:
可以返回 null 的调用方法
不应返回 null 的方法
可以为 null 的变量,如字段、局部变量和参数
不能具有 null 值的变量,如字段、局部变量,以及参数
然后,该分析会在检测到上述方法或变量的位置自动插入适当的 null 注解。
要在 Android Studio 中运行 null 性分析,请依次选择 Analyze > Infer Nullity。Android Studio 会在代码中检测到上述方法或变量的位置插入 Android @Nullable 和 @NonNull 注解。运行 null 性分析后,最好验证一下这些注入的注解。
源码如下:
package android.support.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.ANNOTATION_TYPE, ElementType.PACKAGE})
public @interface NonNull {
}
@StringRes
验证资源类型可能非常有用,因为 Android 对资源(如可绘制对象和字符串资源)的引用以整数形式传递。如果代码需要一个参数来引用特定类型的资源(例如可绘制对象),可以为该代码传递预期的引用类型 int,但它实际上会引用其他类型的资源,如 R.string 资源。
例如,添加 @StringRes 注解,以检查资源参数是否包含 R.string 引用,如下所示:
public abstract void setTitle(@StringRes int resId)
@IntRange
@IntRange 注解可以验证整型或长整型参数值是否在指定范围内。以下示例可以确保 alpha 参数包含 0 到 255 之间的整数值:
public void setAlpha(@IntRange(from=0,to=255) int alpha) { ... }
@FloatRange
@FloatRange 注解可以检查浮点型或双精度型参数值是否在指定的浮点值范围内。以下示例可以确保 alpha 参数包含 0.0 到 1.0 之间的浮点值:
public void setAlpha(@FloatRange(from=0.0, to=1.0) float alpha) {...}
@Size
@Size 注解可以检查集合或数组的大小,以及字符串的长度。@Size 注解可用于验证以下特性:
最小大小(例如 @Size(min=2))
最大大小(例如 @Size(max=2))
确切大小(例如 @Size(2))
大小必须是指定数字的倍数(例如 @Size(multiple=2))
例如,@Size(min=1) 可以检查某个集合是否不为空,@Size(3) 可以验证某个数组是否正好包含三个值。以下示例可以确保 location 数组至少包含一个元素:
void getLocation(View button, @Size(min=1) int[] location) {
button.getLocationOnScreen(location);
}
@RequiresPermission
使用 @RequiresPermission 注解可以验证方法调用方的权限。要检查有效权限列表中是否存在某个权限,请使用 anyOf 属性。要检查是否具有某组权限,请使用 allOf 属性。以下示例会为 setWallpaper() 方法添加注解,以确保方法调用方具有 permission.SET_WALLPAPERS 权限。
@RequiresPermission(Manifest.permission.SET_WALLPAPER)
public abstract void setWallpaper(Bitmap bitmap) throws IOException;
以下示例要求 copyImageFile() 方法的调用方具有对外部存储空间的读取权限,以及对复制的映像中的位置元数据的读取权限:
@RequiresPermission(allOf = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.ACCESS_MEDIA_LOCATION})
public static final void copyImageFile(String dest, String source) {
//...
}
如果您需要对内容提供程序拥有单独的读取和写入访问权限,则需要将每个权限要求封装在 @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");
间接权限
如果权限依赖于为某个方法的参数提供的特定值,则应对该参数本身使用 @RequiresPermission,而不必列出具体权限。例如, startActivity(Intent) 方法会对传递给它的 intent 使用间接权限
public abstract void startActivity(@RequiresPermission Intent intent, @Nullable Bundle)
@SdkConstant
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@RequiresPermission(Manifest.permission.CALL_PHONE)
public static final String ACTION_CALL = "android.intent.action.CALL";
@CheckResult
使用 @CheckResult 注解可验证是否实际使用了方法的结果或返回值。不应使用 @CheckResult 为每个非 void 方法添加注解,而应添加注解来阐明可能令人不解的方法的结果。例如,Java 开发新手经常误认为 .trim() 会移除原始字符串中的空白字符。如果使用 @CheckResult 为方法添加注解,系统会在调用方不对方法的返回值进行任何处理时标记使用 .trim() 的情况。
以下示例为 checkPermissions() 方法添加了注解,以确保会实际引用该方法的返回值。此外,这还会将 enforcePermission() 方法指定为要向开发者建议的替代方法:
@CheckResult(suggest="#enforcePermission(String,int,int,String)")
public abstract int checkPermission(@NonNull String permission, int pid, int uid);
@CallSuper
使用 @CallSuper 注解可验证重写方法是否会调用该方法的超类实现。以下示例为 onCreate() 方法添加了注解,以确保所有重写方法实现都会调用 super.onCreate():
@CallSuper
protected void onCreate(Bundle savedInstanceState) {
}
@VisibleForTesting
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
void myMethod() { ... }
Typedef 注解
使用 @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 或 NAVIGATION_MODE_TABS),系统会生成一条警告。
您还可以结合使用 @IntDef 和 @IntRange,以指明某个整数可以是一组给定的常量,也可以是某个范围内的值。
支持使用标记将常量组合起来
如果用户可以使用标记(如 |、& 和 ^ 等)将允许的常量组合起来,您可以通过 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 {}
...
限制 API
@RestrictTo 注解用于指明访问添加了此类注解的 API(软件包、类或方法)会受到限制,具体说明如下。
子类
使用 @RestrictTo(RestrictTo.Scope.SUBCLASSES) 注解形式可以仅允许子类访问 API。
只有对添加了此类注解的类进行扩展的类可以访问此 API。Java protected 修饰符的限制性不够严格,因为它允许从同一软件包中的不相关类进行访问。此外,在有些情况下,您会希望使某个方法保持 public 状态,以便将来灵活使用,因为您永远不能使先前处于 protected 状态且被替换的方法变为 public 状态,但您需要提供一条提示,指明该方法应仅在相应类或子类中使用。
库
使用 @RestrictTo(RestrictTo.Scope.GROUP_ID) 注解形式可以仅允许库访问 API。
只有库代码可以访问添加了此类注解的 API。这样,您不仅可以按所需的任何软件包层次结构组织您的代码,还可以在一组相关库之间共享代码。有些支持库包含大量的实现代码,这些代码不适合外部使用,但必须处于 public 状态,以便在各种互补支持库之间共享,而此选项已经可供此类支持库使用。
注意:Android 支持库类和软件包现在带有 @RestrictTo(GROUP_ID)
注解,这意味着,如果您不小心使用了这些实现类,Lint 会警告您不建议这样做。
测试
使用 @RestrictTo(RestrictTo.Scope.TESTS) 注解形式可以防止其他开发者访问您的测试 API。
只有测试代码可以访问添加了此类注解的 API。这样可以防止其他开发者使用您仅打算作测试之用的 API 进行开发。
自研产品推荐
历时一年半多开发终于smartApi-v1.0.0版本在2023-09-15晚十点正式上线
smartApi是一款对标国外的postman的api调试开发工具,由于开发人力就作者一个所以人力有限,因此v1.0.0版本功能进行精简,大功能项有:
- api参数填写
- api请求响应数据展示
- PDF形式的分享文档
- Mock本地化解决方案
- api列表数据本地化处理
- 再加上UI方面的打磨
为了更好服务大家把之前的公众号和软件激活结合,如有疑问请大家反馈到公众号即可,下个版本30%以上的更新会来自公众号的反馈。
嗯!先解释不上服务端原因,API调试工具的绝大多数时候就是一个数据模型、数据处理、数据模型理解共识的问题解决工具,所以作者结合自己十多年开发使用的一些痛点来打造的,再加上服务端开发一般是面向企业的,作者目前没有精力和时间去打造企业服务。再加上没有资金投入所以服务端开发会滞后,至于什么时候会进行开发,这个要看募资情况和用户反馈综合考虑。虽然目前国内有些比较知名的api工具了,但作者使用后还是觉得和实际使用场景不符。如果有相关吐槽也可以在作者的公众号里反馈蛤!
下面是一段smartApi使用介绍: