Android Annotation注解详解
轉載請標明出處:http://blog.csdn.net/zhaoyanjun6/article/details/119874435
本文出自【趙彥軍的博客】
文章目錄
- Java注解
- 元注解說明
- @Retention
- @Target
- 通過反射獲取注解信息
- 方法使用注解
- 方法的參數使用注解
- Android 自帶的注解
- @LayoutRes
- @MainThread
- @IntDef
- @StringRes
- @ColorInt
- @IdRes
- @DrawableRes
- @NotNull
- @Nullable
- @Keep
- @RequiresPermission
- @Deprecated
- CallSuper
- @IntRange
- @FloatRange
- @CheckResult
- @size
- 總結:
Java注解
Java 注解(Annotation)又稱 Java 標注,是 JDK5.0 引入的一種注釋機制。
Java 語言中的類、方法、變量、參數和包等都可以被標注。和 Javadoc 不同,Java 標注可以通過反射獲取標注內容。在編譯器生成類文件時,標注可以被嵌入到字節碼中。Java 虛擬機可以保留標注內容,在運行時可以獲取到標注內容 。 當然它也支持自定義 Java 標注。
Java內置了多種標準注解,其定義在java.lang中。
-
Override 表示當前的方法定義將覆蓋父類中的方法
-
Deprecated 被此注解標記的元素表示被廢棄
-
SuppressWarnings 關閉不當的編譯器警告信息
在上面我們看到了 @Target 、@Retention , 這些也是注解,我們暫且可以稱之為注解的注解。
元注解說明
@Retention
表示需要在什么級別保留該注解信息
- RetentionPolicy.SOURCE:只在源代碼中保留 一般都是用來增加代碼的理解性或者幫助代碼檢查之類的,比如我們的Override
- RetentionPolicy.CLASS : 默認的選擇,能把注解保留到編譯后的字節碼class文件中,僅僅到字節碼文件中,運行時是無法得到的
- RetentionPolicy.RUNTIME :注解不僅能保留到class字節碼文件中,還能在運行通過反射獲取到,這也是我們最常用的
@Target
表示該注解可以用在什么地方
- ElementType.FIELD : 能修飾成員變量
- ElementType.METHOD:能修飾方法
- ElementType.CONSTRUCTOR : 能修飾構造器
- ElementType.PACKAGE : 能修飾包
- ElementType.PARAMETER : 能修飾方法參數
- ElementType.TYPE : 能修飾類、接口或枚舉類型
- ElementType.ANNOTATION_TYPE : 能修飾注解
- ElementType.LOCAL_VARIABLE:能修飾局部變量
- ElementType.MODULE :
通過反射獲取注解信息
Annotation是被動的元數據,永遠不會有主動行為,所以我們需要通過使用反射,才能讓我們的注解產生意義。
首先我們定義一個端口注解:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Port {String value() default "8080"; }RetentionPolicy.RUNTIME 在運行時保留注解,作用于是字段。
反射獲取字段
object BindPort {/*** 綁定的目的* 1、通過反射獲取注解的值* 2、通過反射給目標設置值*/fun bind(activity: Activity) {//獲取所有字段val fields = activity.javaClass.declaredFieldsfields.forEach { field ->//獲取所有注解val ans = field.annotationsans.forEach {if (it is Port) {//獲取注解值var port = it.value//通過屬性反射給屬性注入值field.isAccessible = truefield.set(activity, port)}}}}}上面代碼的邏輯很簡單:
首先遍歷循環所有的屬性,如果當前屬性被指定的注解所修飾,那么就將當前屬性的值修改為注解中成員變量的值。
這里setAccessible(true)的使用時因為,我們在聲明port變量時,其類型為private,為了確保可以訪問這個變量,防止程序出現異常。
注解的使用:
class MainActivity : AppCompatActivity() {@Portvar port: String? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)Log.d("yy--", "反射前:$port")BindPort.bind(this)Log.d("yy--", "反射后:$port")} }運行結果:
com.example.myapplication D/yy--: 反射前:null com.example.myapplication D/yy--: 反射后:8080當然,我們也可以在注解時,自定義我們的屬性值,比如:
class MainActivity : AppCompatActivity() {@Port("8090")var port: String? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)Log.d("yy--", "反射前:$port")BindPort.bind(this)Log.d("yy--", "反射后:$port")} }運行結果:
com.example.myapplication D/yy--: 反射前:null com.example.myapplication D/yy--: 反射后:8090方法使用注解
我們來模擬一個http請求, 定義一個 Request 注解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Request {Method value();enum Method {GET, POST} }注解的方式簡單,@Retention(RetentionPolicy.RUNTIME) 在運行時保留,@Target(ElementType.METHOD) 作用域在方法上。
下面我們編寫,反射的方法,定義 HttpBind
object HttpBind {/*** 綁定的目的* 1、通過反射獲取注解的值* 2、通過反射給目標設置值*/fun bind(activity: Activity) {//獲取所有方法val methods = activity.javaClass.methodsmethods.forEach { method ->//獲取所有注解val ans = method.annotationsans.forEach {if (it is Request) {//獲取注解值var requestMethod = it.valueif (requestMethod == Request.Method.GET) {//發起get請求method.invoke(activity, requestMethod.name)} else if (requestMethod == Request.Method.POST) {//發起post請求method.invoke(activity, requestMethod.name)}}}}}}注解使用
class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)//使用反射,解析注解HttpBind.bind(this)}@Request(Request.Method.GET)fun http(method: String) {Log.d("yy--", "網絡請求:$method")} }我們運行一下,看看效果
D/yy--: 網絡請求:GET方法的參數使用注解
先定義一個參數注解 Path
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) public @interface Path {String value() default ""; }這個注解也很簡單,在運行時保留,作用域在參數上
下面我們使用反射來獲取參數注解的值
object HttpBind {/*** 綁定的目的* 1、通過反射獲取注解的值* 2、通過反射給目標設置值/反射調用方法*/fun bind(activity: Activity) {//獲取所有方法val methods = activity.javaClass.methodsmethods.forEach { method ->//獲取所有參數注解,一個方法有多個參數,一個參數有多個注解,所以類型是二維數組val ans = method.parameterAnnotationsans.forEach { annotationArray ->annotationArray.forEach { parameterAnnotation ->if (parameterAnnotation is Path) {var parameter = parameterAnnotation.valuemethod.invoke(activity, parameter)}}}}} }使用如下:
class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)HttpBind.bind(this)}fun http(@Path("zhaoyanjun") user: String) {Log.d("yy--", "參數注解:$user")} }運行結果如下:
D/yy--: 參數注解:zhaoyanjunAndroid 自帶的注解
Android 系統已經幫我內置了很多有用的注解,在我們的開發過程中可以很方便的使用。
implementation 'androidx.annotation:annotation:1.2.0'示例如下
我們舉幾個常見的例子:
資源限制類
- @AnimatorRes :animator資源類型
- @AnimRes:anim資源類型
- @AnyRes:任意資源類型
- @ArrayRes:array資源類型
- @AttrRes:attr資源類型
- @BoolRes:boolean資源類型
- @ColorRes:color資源類型
- @DimenRes:dimen資源類型。
- @DrawableRes:drawable資源類型。
- @FractionRes:fraction資源類型
- @IdRes:id資源類型
- @IntegerRes:integer資源類型
- @InterpolatorRes:interpolator資源類型
- @LayoutRes:layout資源類型
- @MenuRes:menu資源類型
- @PluralsRes:plurals資源類型
- @RawRes:raw資源類型
- @StringRes:string資源類型
- @StyleableRes:styleable資源類型
- @StyleRes:style資源類型
- @TransitionRes:transition資源類型
- @XmlRes:xml資源類型
線程限制類
Thread annotations 線程執行限制類:用于限制方法或者類必須在指定的線程執行。如果方法代碼運行線程和標注的線程不一致,則會導致警告。
- @AnyThread
- @BinderThread
- @MainThread
- @UiThread
- @WorkerThread
數值限制類
Value Constraint Annotations 類型范圍限制類:用于限制標注值的值范圍
- @FloatRang
- @IntRange
@LayoutRes
這個是layout 資源類型,我們看一下 Activity 的 setContentView 源碼:
@Overridepublic void setContentView(@LayoutRes int layoutResID) {getDelegate().setContentView(layoutResID);}本質上,layoutResID 是一個 int 類型,如果不做限定的話,可以傳入任意整形,但是有 @LayoutRes 注解的限制,值只能傳入 R.layou.xx , 如果傳入其他的類型就會報錯。舉例如下:
需要注意的是,報錯只是編譯器的檢查出錯,提醒開發者改正錯誤用法,提前規避風險,并不影響編譯運行
@MainThread
限定方法執行的線程,如果方法代碼運行線程和標注的線程不一致,不會報錯,更多是起一個提醒作用
@MainThreadfun run() {}@IntDef
IntDef 的源碼如下:
@Retention(SOURCE) @Target({ANNOTATION_TYPE}) public @interface IntDef {/** Defines the allowed constants for this element */int[] value() default {};/** Defines whether the constants can be used as a flag, or just as an enum (the default) */boolean flag() default false;/*** Whether any other values are allowed. Normally this is* not the case, but this allows you to specify a set of* expected constants, which helps code completion in the IDE* and documentation generation and so on, but without* flagging compilation warnings if other values are specified.*/boolean open() default false; }可以看到 Target 是 ANNOTATION_TYPE 說明 IntDef 是作用在注解上的。
還有一個 value 是 int數組。
下面我們定義一個注解 MOBILE_TYPE , 并且用 IntDef 修飾,如下:
import androidx.annotation.IntDef; import kotlin.annotation.AnnotationRetention; import kotlin.annotation.Retention;/*** @author : zhaoyanjun* @time : 2021/7/29* @desc :*/ public class Util {public static final int TYPE_MI = 1;public static final int TYPE_MEIZU = 2;public static final int TYPE_HUAWEI = 3;@Retention(AnnotationRetention.SOURCE)@IntDef({TYPE_MI, TYPE_MEIZU, TYPE_HUAWEI})public @interface MOBILE_TYPE {}//使用public void doSomething(@MOBILE_TYPE int mobile){} }使用方法很簡單,首先定義你需要的常量,然后用 @IntDef 包住這些常量,這樣別人在使用你的方法時如果輸入的值不在枚舉的范圍內,編譯器就會給出提示了。
同理,@StringDef 也是同樣的用法
import androidx.annotation.StringDef;import kotlin.annotation.AnnotationRetention; import kotlin.annotation.Retention;/*** @author : zhaoyanjun* @time : 2021/7/29* @desc :*/ public class Util {public static final String TYPE_HD = "720p";public static final String TYPE_SHD = "1080p";public static final String TYPE_FHD = "4k";@Retention(AnnotationRetention.SOURCE.SOURCE)@StringDef({TYPE_HD, TYPE_SHD, TYPE_FHD})public @interface DISPLAY_TYPE {}public void doSomething(@DISPLAY_TYPE String display) {} }還有一個 @LongDef也是同樣的用法,這里就不舉例了。
總結 :IntDef @StringDef @LongDef 可以限制變量的類型,可以代替枚舉類型
我們來看一個系統例子,Toast 的源碼:
public static Toast makeText(Context context, CharSequence text, @Duration int duration) {return makeText(context, null, text, duration);}@Duration 是一個自定義的注解:
/** @hide */@IntDef(prefix = { "LENGTH_" }, value = {LENGTH_SHORT,LENGTH_LONG})@Retention(RetentionPolicy.SOURCE)public @interface Duration {}看到這里,我們已經很熟悉了,也是用的 @IntDef 注解,除此之外,我們還發現了一個細節 ,在 android 的注解包里,@IntDef 帶有 prefix 屬性,但是在 androidx 的注解包里卻沒有。
下面貼一下兩個的源碼,大家看看:
//android 的源碼,包名:android.annotation @Retention(SOURCE) @Target({ANNOTATION_TYPE}) public @interface IntDef {/** Defines the constant prefix for this element */String[] prefix() default {};/** Defines the constant suffix for this element */String[] suffix() default {};/** Defines the allowed constants for this element */int[] value() default {};/** Defines whether the constants can be used as a flag, or just as an enum (the default) */boolean flag() default false; }//androidx 的源碼,包名:androidx.annotation @Retention(SOURCE) @Target({ANNOTATION_TYPE}) public @interface IntDef {/** Defines the allowed constants for this element */int[] value() default {};/** Defines whether the constants can be used as a flag, or just as an enum (the default) */boolean flag() default false;/*** Whether any other values are allowed. Normally this is* not the case, but this allows you to specify a set of* expected constants, which helps code completion in the IDE* and documentation generation and so on, but without* flagging compilation warnings if other values are specified.*/boolean open() default false; }這是兩個包下面的 IntDef 的差異,我想知道的是 prefix 有什么用?
其實也很簡單,規范 value 數組元素的命名前綴。
@StringRes
這個其實很好理解,限制字符的來源,必須是 R.string.xx , StringRes 源碼如下:
/*** Denotes that an integer parameter, field or method return value is expected* to be a String resource reference (e.g. {@code android.R.string.ok}).*/ @Documented @Retention(CLASS) @Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) public @interface StringRes { }舉個系統的例子:
public static Toast makeText(Context context, @StringRes int resId, @Duration int duration)throws Resources.NotFoundException {return makeText(context, context.getResources().getText(resId), duration);}@ColorInt
限定顏色的取值范圍 R.color.xx , 源碼如下:
/*** Denotes that the annotated element represents a packed color* int, {@code AARRGGBB}. If applied to an int array, every element* in the array represents a color integer.* <p>* Example:* <pre>{@code* public abstract void setTextColor(@ColorInt int color);* }</pre>*/ @Documented @Retention(CLASS) @Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD}) public @interface ColorInt { }舉個系統的例子:
public void setTextColor(@ColorInt int color) {mTextColor = ColorStateList.valueOf(color);updateTextColors(); }@IdRes
限制id 的取值范圍:R.id.xx , 源碼碼如下:
/*** Denotes that an integer parameter, field or method return value is expected* to be an id resource reference (e.g. {@code android.R.id.copy}).*/ @Documented @Retention(CLASS) @Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) public @interface IdRes { }舉個系統的例子:
@Overridepublic <T extends View> T findViewById(@IdRes int id) {return getDelegate().findViewById(id);}@DrawableRes
限定資源的取值類型是一個 drawable 類型:android.R.attr.alertDialogIcon
@Documented @Retention(CLASS) @Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) public @interface DrawableRes { }舉個系統的例子:
public void setImageResource(@DrawableRes int resId) {... }@NotNull
定義個變量不能為空, 如果真的為空,不會影響編譯,只是編譯器會報錯,提醒開發者注意。
public class Util {//參數不能為nullpublic void run(@NotNull String name) {} }測試:
可以看到編譯器會自動檢查
@Nullable
限定一個參數,一個方法的返回值可以為null
public class Util {@Nullablepublic String aa() {return null;} }使用:
編譯器會自動提示
@Keep
哪里不想被混淆就注解哪里。
@Keep public class Test { }public class TestA { }開始混淆打包,查看混淆后的結果:
我們發現TestA不見了而Test保留了下來,說明我們的配置起作用了,下面我們在Test 類中增加點內容看看混淆后會變成什么樣子,修改后的類內容如下:
查看混淆后的結果:
不幸的是雖然類名保留下來了,但是里面的內容卻被混淆了,如果我們想把name變量不被混淆怎么辦呢?
我們繼續修改Test類,這次我們多加了點東西,會在后面用到,內容如下:
@Keep public class Test {int age = 20;@Keepprotected String sex = "m";@Keeppublic String name = "CodingMaster";public int getAge() {return age;}public void setAge(int age) {this.age = age;}private void cry(){} }重新混淆查看結果:
我們的name變量被成功的保留了,同理如何保留被sex變量呢?這里就不買關子了,直接給出答案,為sex添加@Keep注解就可以了,持懷疑態度的同學👨?🎓可以自己去驗證。
細心的同學可能已經發現,Test類里面的方法都被混淆了,怎樣指定某個方法不被混淆呢?
然后為cry()方法添加@Keep注解,重新混淆查看結果:
有沒有很簡單的感覺呢?哪里不混淆@Keep哪里,再也不用為混淆頭疼了!
@RequiresPermission
限定字段,方法需要某個權限,如果沒有,編譯器會提醒
public class Util1 {@RequiresPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) public String run() {return null; }使用:
看到編譯器報錯,我們點擊一下 Add permission check , 編譯器會自動幫我們補全代碼
- @RequiresPermission(permision)
- @RequiresPermission(allOf={permision1,perminsion2})
- @RequiresPermission(anyOf={permision1,perminsion2})
@Deprecated
標記某個字段或者方法過時,舉例:
CallSuper
子類重寫某個方法時,要求調用super,可以使用該注解
@IntRange
//限定只能傳1-4fun run(@IntRange(from = 1, to = 4) num: Int) {}使用:
@FloatRange
用法上和 IntRange 一樣,
//限定只能傳1-4fun run(@FloatRange(from = 1.0, to = 4.0) num: Float) {}源碼如下:
其中,fromInclusive 是否包含 from ,toInclusive 是否包含 to , 其實就是左包含,右包含的意思。
@CheckResult
假設你定義了一個方法返回一個值,你期望調用者用這個值做些事情,那么你可以使用@CheckResult注解標注這個方法,強制用戶定義一個相應的返回值,使用它!
首先定義 CallSuperT ,定義一個retrunI方法返回一個int類型
public class CallSuperT {@CheckResultpublic int retrunI(){return 1;} }正確調用:
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);CallSuperT callSuperT = new CallSuperT();int returns = callSuperT.retrunI();} }如果這里去掉返回類型的定義對象:int returns則會拋出異常
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);CallSuperT callSuperT = new CallSuperT();callSuperT.retrunI();} }錯誤提示結果:
@size
定義長度大小,可選擇最小和最大長度使用
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);testDo("");testDo("111");testDo("1");}private void testDo(@Size(min = 1,max = 2)String s){Log.e("tag","-------->"+s);} }錯誤提示結果:
這里size定了一個最小和最大長度,所以只有testDo(“1”)符合條件,其他調用都拋出了異常
總結:
注解的作用:
- 提高我們的開發效率
- 更早的發現程序的問題或者錯誤
- 更好的增加代碼的描述能力
- 更加利于我們的一些規范約束
- 提供解決問題的更優解
總結
以上是生活随笔為你收集整理的Android Annotation注解详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android Kotlin Corou
- 下一篇: Kotlin by属性委托