Support Annotation Library 是从Android Support Library 19.1开始引入的一个全新的函数包,它本身包含一系列有用的元注解,用来帮助开发者在编译期间发生可能存在的Bug。Support Library 本身也使用Annotation Library 提供的注解来完善自身代码质量,Android Studio为Annotation Library 提供了可视化的交互以方便开发者发现问题。
在Android Support Library 22.2中,新增了13种新的 Annotation Library 注解,在实际开发中应该尽量使用最新版本的函数包,以便能够使用更多的元注解来提高代码质量。
Android Support Library 发展到现在,已经有多个jar包,如下表
Android Support Library |
com.android.support:support-annotations:23.1.1 |
com.android.support:support-v4:23.1.1 |
|
com.android.support:support-v13:23.1.1 |
|
com.android.support:appcompat-v7:23.1.1 |
|
com.android.support:design:23.1.1 |
|
com.android.support:gridlayout-v7:23.1.1 |
|
com.android.support:mediarouter-v7:23.1.1 |
|
com.android.support:cardview-v7:23.1.1 |
|
com.android.support:palette-v7:23.1.1 |
|
com.android.support:recyclerview-v7:23.1.1 |
|
com.android.support:leanback-v17:23.1.1 |
点击添加后,在Module 的 build.gradle 文件中会新增Annotation 函数库的依赖代码如下:
support- annotation-23.1.1 函数包中,总共包含39种注解,按照类型分别进行介绍:
(1)资源类型的注解
资源在Android中通常是以整型值表示,并保存在R.java 文件中。这意味着需要传入资源值的函数,如果传入的不是资 源值不会在编译期报错,只会在运行时执行到相应的代码才能发现问题,则使用资源类型注解可以防止这种情况的出现。
资源类型的注解作用于函数参数、返回值及类的变量,在support-annotations-23.1.1 中,每种资源类型对应一个注解。
☯ MenuRes:标记整型值是 android.R.menu 类型。
(2)注解使用
@Nullable 作用于函数参数或者返回值,标记参数或者返回值可以为空。
@NonNull 作用于函数参数或者返回值,标记参数或者返回值不可以为空。
当出现这种违反注解标记的代码时,Android Studio会给出提示,同时使用Android Lint进行静态代码扫描,也会显示出错提示。下面以@NonNull注解为例进行说明,当helloWord函数的参数传入null时,在AndroidStudio中会出现如下图所示警告:
如果使用Android Lint 扫描这个文件的话,在扫描结果中会显示如下图的警告:
(3)、类型定义注解
在Android开发中,整型值不止经常用来代表资源引用值,而且经常用来代替枚举值@IntDet注解用来创建一个整型类型定义的新注解,可以使用这个新注解来标记自己编写的API,下面这段代码从appcompat 函数包中抽取出来的,可以看到@IntDef的用法如下:
import android.support.annotation.IntDef;
public abstract class ActionBar {
@RestrictTo(LIBRARY_GROUP)
@Retention(RetentionPolicy.SOURCE) //告知编译器不要在 .class 文件中存储注解数据
//定义可以接受的常亮列表
@IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
//定义NavigationMode 注解
public @interface NavigationMode {}
//定义常量
@Deprecated
public static final int NAVIGATION_MODE_STANDARD = 0;
@Deprecated
public static final int NAVIGATION_MODE_LIST = 1;
@Deprecated
public static final int NAVIGATION_MODE_TABS = 2;
@Deprecated
@android.support.v7.app.ActionBar.NavigationMode
public abstract int getNavigationMode();
@Deprecated
public abstract void setNavigationMode(@android.support.v7.app.ActionBar.NavigationMode int mode);
@RestrictTo(LIBRARY_GROUP)
@IntDef(flag=true, value={
DISPLAY_USE_LOGO,
DISPLAY_SHOW_HOME,
DISPLAY_HOME_AS_UP,
DISPLAY_SHOW_TITLE,
DISPLAY_SHOW_CUSTOM
})
定义的flag 标识位,来识别函数参数或返回值是否符合某种模式。
(4)线程注解
Android 应用开发过程中,经常会涉及多种线程的使用,界面相关的操作必须在主线程,而耗时操作例如文件下载等则需要放在后台线程中。线程相关的注解有四种。
@UiThread:标记运行在UI线程,一个UI线程Activity运行所在的主窗口,对于一个应用而言,可能存在多个UI线程,每个UI线程对应不同的主窗口。
@MainThread:标记运行在主线程,一个应用只有一个主线程,主线程也是@UIThread线程。通常情况下,我们使用@MainThread来注解声明周期相关的函数,使用@UIThread来注解视图相关函数,一般情况下@MainThread和@UiThread 可以互换使用的。
@WorkerThread:标记运行在后台的注解。
@BinderThread:标记运行在Binder的线程。
一个典型的例子是AsyncTask 的实现,截取部分代码如下:
@MainThread
protected void onPreExecute(){}
@WorkerThread
protected abstract Result doInBackground(Params... params);
@BinderThread
protected void onProgressUpdate(Progress... value)
(5)RGB颜色值注解
在资源类型注解中使用@ColorRes来标记参数类型需要传入颜色类型的资源id,使用@ColorInt 注解则是标记参数类型需要传入RGB或者ARGB 颜色值,在TextView 源码中可以找到@ColorInt的例子:
public void setTextColor(@ColorInt int color){
mTextColor=ColorStateList.valueOf(color);
updateTextColors();
}
(6)值范围注解:
当函数参数的取值是在一定范围内时,可以使用值范围注解来防止调用者传入错误的参数,这中类型的注解有三种:
①、 @Size:对于类似数组、集合和字符串之类的参数,可以使用@Size 注解来表示这些参数的大小。用法如下:
@Size(min=1) // 可以表示集合不可以为空
@Size(max=23) //可以表示字符串最大字符个数是23
@Size(2) //可以表示数组元素个数是2个
@Size(multiple=2) //可以表示数组大小是2的倍数
②、@IntRange:参数类型是int或者Long,用法如下
public void setAlpha(@IntRange(from=0,to=25) int alpha){....}
③、@FloatRange:参数类型是float 或者 double ,用法如下。
public void setAlpha(@FloatRange(from=0,to=1.0) float alpha){....}
(7)权限注解
Android应用在使用某些系统功能时,需要在AndroidManifest.xml中声明权限,否则在运行时会提示缺失对应的权限。为了在编译期及时发现缺失的全权,我们可以使用@RequiresPermission 注解。
如果函数调用需要声明一个权限,语句如下。
@requiresPermission(Mainfest.permission.SET_WALLPAPER)
public abstract void setWallpaper(Bitmap bitmap) throws IOException;
如果函数调用需要声明集合中最少一个权限,语句如下。
@requiresPermission(anyOf= {Mainfest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.ACCESS_FINE_LOCATION})
public abstract Location getLastKnownLocation(String provider);
如果函数调用需要同时声明多个权限,语句如下。
@requiresPermission(allOf={Mainfest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.ACCESS_FINE_LOCATION})
public abstract final void upateVisitedHistory( ContentResolver cr,String url, boolean real)
对于Intent调用所需权限,可以在Intent的ACTION字符串定义处添加注解,语句如下。
@requiresPermission(android.Minfest.permission.BLUETOOTH)
public static final String ACTION_REQUEST_DISCOVERABLE="android.bluetooth.adapter.action.REQUEST_DISCOVERABLE"
对于ContentProvider 相关所需要的权限,可能同时需要读和写这两个操作对应不同的权限声明,语句如下。
@RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS))
@RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS))
pbulic static final Uri BOOKMARKS_URI=Uri.parse("content://browser/bookmarks");
(8)重写函数注解
如果API允许调用者重写某个函数,但同时要求写的函数需要调用被重写的函数,否则代码逻辑可能错误,那么可以使用@CallSuper 注解开提示开发者,语句如下。
@CallSuper
protected void onCreate(@Nullable Bundle savedInstanceState);
(9)返回值注解
如果编写的函数需要调用者对返回值做某些处理,那么可以使用@CheckResult 注解来提示开发者。当然没有必要对每个非空返回值的函数都添加这个注解,该注解的主要目的是让调用者在使用API时不至于怀疑该函数是否会产生副作用。在Android源码中,Context类的checkPermission 函数使用了该注解。
@CheckResult( suggest="#enforcePermission( String ,int , int , String )")
@PackageManager.PermissionResult
public abstract int checkPermission( @NonNull String permission ,int pid ,int uid);
@CheckResult( suggest="#enforceCallingOrSelfPermission( String ,String )")
public abstract int checkCallingOrSelfPermission( @NonNull String permission );
(10)@VisbleForTesiong
单元测试中可能需要访问到一些不可见的类、函数或变量,这时可以使用@VisbleForTesing 注解来使其对测试可见。
(11)@Keep
@keep注解用来标记在Proguard 混淆过程中不需要混淆的类或者方法。如果你曾经在编写混淆文件时使用过,那么@keep 的用法很简单。
-keep class com.foo.bar{ public static
如果有了@keep 注解,则可以在代码编写过程中对不需要混淆的类或者方法直接标记即可。
public class AnnotationDemo{
@Keep
public void doSomething{
//......}
}
最后说明一下,如果函数库中使用 Annotation Library,并使用Gradle 生成aar压缩包,那么在编译时Android Gradle插件会抽取这些注解信息并打包在aar文件中,以便函数库的调用者正常使用注解信息。aar文件中的annoatations.zip 文件就是抽取出来的注解信息