Andrdoid 註解和 meta-data (Kotlin)

第一章 註解

一、基本使用

1.首先聲明註解類

annotation class MyAnnotation(val value: String, val name: String)

2.在Activity中使用反射調用

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val methods = javaClass.declaredMethods
        methods.forEach {
            if (it.isAnnotationPresent(MyAnnotation::class.java)) {
                val annotation = it.getAnnotation(MyAnnotation::class.java)
                Log.d("~~~", "value = ${annotation.value}, name = ${annotation.name}")
            }
        }
    }

    @MyAnnotation("1.0","Cassin")
    fun testAnnotation(){
    }
}

運行程序,Log控制檯顯示如下:

~~~: value = 1.0, name = Cassin

二、元註解

1.@Retention

@Retention標誌了註解存活的時間

@Retention的源碼如下:

/**
 * This meta-annotation determines whether an annotation is stored in binary output and visible for reflection. By default, both are true.
 *
 * @property value necessary annotation retention (RUNTIME, BINARY or SOURCE)
 */
@Target(AnnotationTarget.ANNOTATION_CLASS)
public annotation class Retention(val value: AnnotationRetention = AnnotationRetention.RUNTIME)

其中,AnnotationRentention源碼如下:

/**
 * Contains the list of possible annotation's retentions.
 *
 * Determines how an annotation is stored in binary output.
 */
public enum class AnnotationRetention {
    /** Annotation isn't stored in binary output */
    SOURCE,
    /** Annotation is stored in binary output, but invisible for reflection */
    BINARY,
    /** Annotation is stored in binary output and visible for reflection (default retention) */
    RUNTIME
}

根據源碼的註釋,可知:

  • @Retention設置爲SOURCE時,註解僅存活在源碼中,不會存儲到二進制輸出文件中。
  • 設置爲BINARY時,註解會存儲到二進制輸出文件中,但是對反射不可見。
  • 設置爲RUNTIME時,註解會存儲到二進制輸出文件中,並且對反射可見。

對於同一個註解設置不同的@Retention,筆者測試結果是:

AnnotationRetention.BINARY註解打包出的apk體積最大
AnnotationRetention.RUNTIME註解打包出的apk體積第二大
AnnotationRetention.SOURCE註解打包出的apk體積最小

如果註解是用來配置程序運行必需的參數,則Retention必須是AnnotationRetention.RUNTIME,否則打包出來的apk無法獲取配置的參數。

2.@MustBeDocumented

表示將註解添加到Javadoc中,默認爲不添加。
@MustBeDocumented的源碼如下:

/**
 * This meta-annotation determines that an annotation is a part of public API and therefore should be included in the generated
 * documentation for the element to which the annotation is applied.
 */
@Target(AnnotationTarget.ANNOTATION_CLASS)
public annotation class MustBeDocumented

這裏順便講一下Kotlin怎麼生成Javadoc

2.1.Kotlin使用dokka生成javadoc

dokka庫的github地址爲:https://github.com/Kotlin/dokka

2.2.dokka使用方式

1.在project的build.gradle中添加:

classpath 'org.jetbrains.dokka:dokka-android-gradle-plugin:0.9.18'

2.在app模塊的build.gradle中添加:

apply plugin: 'org.jetbrains.dokka-android'

task javadoc(type: Javadoc) {
    source = android.sourceSets.main.java.srcDirs
    options.encoding = "UTF-8"
    classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
}

task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) {
    outputFormat = "javadoc"
    outputDirectory = javadoc.destinationDir
}

task generateJavadoc(type: Jar, dependsOn: dokkaJavadoc) {
    group = 'jar'
    classifier = 'javadoc'
    from javadoc.destinationDir
}

artifacts {
    archives generateJavadoc
}

sync之後,打開terminal,Windows環境下,輸入:

gradlew generateJavadoc

Mac環境下,輸入:

./gradlew generateJavadoc

Build Success之後,在app模塊下的 build/libs 文件夾下會生成一個app-javadoc.jar,使用解壓工具將其解壓,就可以看到每個類對應的html格式的javadoc了。
上例中,不添加@MustBeDocumented生成的javadoc如下:

添加了@MustBeDocumented生成的javadoc如下:

3.@Target

Target指定此註解能夠對哪些類型進行註解。
@Target源碼如下:

/**
 * This meta-annotation indicates the kinds of code elements which are possible targets of an annotation.
 *
 * If the target meta-annotation is not present on an annotation declaration, the annotation is applicable to the following elements:
 * [CLASS], [PROPERTY], [FIELD], [LOCAL_VARIABLE], [VALUE_PARAMETER], [CONSTRUCTOR], [FUNCTION], [PROPERTY_GETTER], [PROPERTY_SETTER].
 *
 * @property allowedTargets list of allowed annotation targets
 */
@Target(AnnotationTarget.ANNOTATION_CLASS)
@MustBeDocumented
public annotation class Target(vararg val allowedTargets: AnnotationTarget)

其中,AnnotationTarget源碼如下:

/**
 * Contains the list of code elements which are the possible annotation targets
 */
public enum class AnnotationTarget {
    /** Class, interface or object, annotation class is also included */
    CLASS,
    /** Annotation class only */
    ANNOTATION_CLASS,
    /** Generic type parameter (unsupported yet) */
    TYPE_PARAMETER,
    /** Property */
    PROPERTY,
    /** Field, including property's backing field */
    FIELD,
    /** Local variable */
    LOCAL_VARIABLE,
    /** Value parameter of a function or a constructor */
    VALUE_PARAMETER,
    /** Constructor only (primary or secondary) */
    CONSTRUCTOR,
    /** Function (constructors are not included) */
    FUNCTION,
    /** Property getter only */
    PROPERTY_GETTER,
    /** Property setter only */
    PROPERTY_SETTER,
    /** Type usage */
    TYPE,
    /** Any expression */
    EXPRESSION,
    /** File */
    FILE,
    /** Type alias */
    @SinceKotlin("1.1")
    TYPEALIAS
}

根據源碼可知,@Target各屬性的含義是:

  • AnnotationTarget.CLASS 可以給一個類型進行註解 , 類 、 接口 、 對象 、 甚至註解類本身
  • AnnotationTarget.ANNOTATION_CLASS 可以給一個註解類進行註解
  • AnnotationTarget.TYPE_PARAMETER 泛型參數 ( 暫未支持 )
  • AnnotationTarget.VALUE_PARAMETER 方法 、 構造函數的參數
  • AnnotationTarget.PROPERTY (計算)屬性(該註解Java不可見)
  • AnnotationTarget.PROPERTY_GETTER 屬性getter方法
  • AnnotationTarget.PROPERTY_SETTER 屬性setter方法
  • AnnotationTarget.FIELD 字段變量 , 包括PROPERTY的備用字段 ( backing field)
  • AnnotationTarget.LOCAL_VARIABLE 局部變量
  • AnnotationTarget.CONSTRUCTOR 構造函數
  • AnnotationTarget.FUNCTION 方法 、 函數 ( 不包括構造函數 )
  • AnnotationTarget.FILE 文件整體
  • AnnotationTarget.TYPE 泛型支持
  • AnnotationTarget.TYPEALIAS 類型別名
  • AnnotationTarget.EXPRESSION 表達式

4.@Inherited

表示如果某個類註解了此註解,其子類是否繼承此註解,默認不繼承。
@Inherited源碼如下:

/**
 * Indicates that an annotation type is automatically inherited.  If
 * an Inherited meta-annotation is present on an annotation type
 * declaration, and the user queries the annotation type on a class
 * declaration, and the class declaration has no annotation for this type,
 * then the class's superclass will automatically be queried for the
 * annotation type.  This process will be repeated until an annotation for this
 * type is found, or the top of the class hierarchy (Object)
 * is reached.  If no superclass has an annotation for this type, then
 * the query will indicate that the class in question has no such annotation.
 *
 * <p>Note that this meta-annotation type has no effect if the annotated
 * type is used to annotate anything other than a class.  Note also
 * that this meta-annotation only causes annotations to be inherited
 * from superclasses; annotations on implemented interfaces have no
 * effect.
 *
 * @author  Joshua Bloch
 * @since 1.5
 * @jls 9.6.3.3 @Inherited
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

我們做個簡單的測試:
1.先聲明一個不帶@Inherited的註解:

annotation class MyAnnotation(val value: String)

2.在BaseActivity中使用此註解:

// 測試父類上的註解能否被繼承
@MyAnnotation("superClass")
abstract class BaseActivity : AppCompatActivity() {

    // 測試父類方法中的註解能否被繼承
    @MyAnnotation("superFun")
    protected fun superFun() {

    }

    // 測試子類重寫父類方法是能否繼承註解
    @MyAnnotation("superOverrideFun")
    open fun superOverrideFun() {

    }

    // 測試父類抽象方法中的註解能否被繼承
    @MyAnnotation("superAbstractFun")
    protected abstract fun superAbstractFun()
}

3.MainActivity繼承BaseActivity

class MainActivity : BaseActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val annotations = javaClass.annotations
        annotations.forEach {
            if (it is MyAnnotation) {
                Log.d("~~~annotations", "value = ${it.value}")
            }
        }

        val methods = javaClass.methods
        methods.forEach {
            if (it.isAnnotationPresent(MyAnnotation::class.java)) {
                val annotation = it.getAnnotation(MyAnnotation::class.java)
                Log.d("~~~methods", "${it.name}:value = ${annotation.value}")
            }
        }
    }

    override fun superOverrideFun() {
    }

    override fun superAbstractFun() {
    }
}

使用 javaClass.annotations 反射獲取類上的所有註解,使用 javaClass.methods 反射獲取類中所有方法。運行程序,Log控制檯輸出如下:

~~~methods: superFun:value = superFun

4.在註解中加上@Inherited:

@Inherited
annotation class MyAnnotation(val value: String)

運行程序,Log控制檯輸出如下:

~~~annotations: value = superClass
~~~methods: superFun:value = superFun

所以我們可以得出結論:

無@Inherited 有@Inherited
子類能否繼承父類的類上的註解
子類能否繼承到父類抽象方法的註解
子類能否繼承父類方法上的註解
子類重寫父類上的方法,能否繼承到註解

5.@Repeatable

表示此註解是否可重複註解。
@Repeatable源碼如下:

/**
 * This meta-annotation determines that an annotation is applicable twice or more on a single code element
 */
@Target(AnnotationTarget.ANNOTATION_CLASS)
public annotation class Repeatable

設置@Repeatable時,註解的@Retention必須被申明爲僅存活在源碼中,暫不支持其他@Retention。
例如:
聲明一個@Repeatable的註解:

@Repeatable
@Retention(AnnotationRetention.SOURCE)
annotation class MyAnnotation(val value: String)

多次使用註解:

@MyAnnotation("a")
@MyAnnotation("b")
@MyAnnotation("c")
class MainActivity : AppCompatActivity() {
    
    @MyAnnotation("a")
    @MyAnnotation("b")
    @MyAnnotation("c")
    val field = 1

    @MyAnnotation("a")
    @MyAnnotation("b")
    @MyAnnotation("c")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }


    @MyAnnotation("a")
    @MyAnnotation("b")
    @MyAnnotation("c")
    fun testAnnotation() {
    }
}

6.系統自帶註解

6.1.@SupressWarning

@SuppressWarnings註解用來忽略代碼警告,源碼轉換成kotlin如下:

@Target(
    AnnotationTarget.CLASS,
    AnnotationTarget.FILE,
    AnnotationTarget.FIELD,
    AnnotationTarget.FUNCTION,
    AnnotationTarget.PROPERTY_GETTER,
    AnnotationTarget.PROPERTY_SETTER,
    AnnotationTarget.VALUE_PARAMETER,
    AnnotationTarget.CONSTRUCTOR,
    AnnotationTarget.LOCAL_VARIABLE
)
@Retention(AnnotationRetention.SOURCE)
annotation class SuppressWarnings(vararg val value: String)

@Retention是AnnotationRetention.SOURCE,說明只會在源碼中保留註解,打包時會忽略此註解。由@Target可知可以在類、文件、字段、方法、getter方法、setter方法、值參數、構造方法、本地變量上使用此註解。

6.2.@Deprecated

@Deprecated標誌代碼已過時,源碼轉換成Kotlin如下:

@Target(
    AnnotationTarget.CLASS,
    AnnotationTarget.FUNCTION,
    AnnotationTarget.PROPERTY,
    AnnotationTarget.ANNOTATION_CLASS,
    AnnotationTarget.CONSTRUCTOR,
    AnnotationTarget.PROPERTY_SETTER,
    AnnotationTarget.PROPERTY_GETTER,
    AnnotationTarget.TYPEALIAS
)
@MustBeDocumented
public annotation class Deprecated(
    val message: String,
    val replaceWith: ReplaceWith = ReplaceWith(""),
    val level: DeprecationLevel = DeprecationLevel.WARNING
)

@MustBeDocumented標誌着此註解會生成到javadoc中,由@Target可知可以在類、方法、屬性、註解、構造方法、setter方法、getter方法,類型別名上使用此註解。

6.3.@Override

表示重寫方法,源碼轉換成Kotlin如下:

@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(AnnotationRetention.SOURCE)
annotation class Override

@Retention是AnnotationRetention.SOURCE,表示此註解只會在源碼中保留,由@Target可知,可以在方法、getter、setter中使用此註解
還有一些系統自帶註解,如@SafeVarargs、@FunctionalInterface,有興趣的讀者可以自己瞭解

第二章 meta-data

基本使用

1.在application中聲明meta-data

<!--保存值-->
<meta-data android:name="value_key" android:value="value"/>
<!--保存id-->
<meta-data android:name="resource_key" android:resource="@string/app_name"/>
<!--保存id對應的值-->
<meta-data android:name="resource_value" android:value="@string/app_name"/>

2.通過PackageManager調用

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val applicationInfo = packageManager.getApplicationInfo(packageName,PackageManager.GET_META_DATA)
        val metaData = applicationInfo.metaData
        /**獲取meta-data中的參數*/
        Log.d("~~~","value_key:${metaData.get("value_key")}\n" +
                "resource_key:${getString(metaData.get("resource_key") as Int)}\n" +
                "resource_value:${metaData.get("resource_value")}")
    }
}

運行程序,顯示如下:

~~~: value_key:value
resource_key:TestMetaData
resource_value:TestMetaData

參考文章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章