从 Kotlin 的角度看待 Android 注解

我们知道,通过反射机制,我们可以在运行时动态地获取和操作类的信息。有些时候,我们需要对一些信息提前描述,在使用反射的时候区别对待,这就要用到注解了。关于反射和注解,现在的文章基本上都是针对 Java 的,难道 Kotlin 就不配拥有吗?当然不是,下面就以 Kotlin 的角度去看待 Android 注解吧。

反射

引入依赖

implementation "org.jetbrains.kotlin:kotlin-reflect:1.8.20"

反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。在 Kotlin 中,字节码对应的类是 kotlin.reflect.KClass。

要想使用反射,需先获取 kotlin 的 KClass 对象,kotlin 主要通过下面两种方式获取。

// 第一种:通过 ::class 的方式获取
val clazz1: KClass<User> = User::class

//第二种:通过 实例对象.javaClass.kotlin 的方式获取
val user = User()
val clazz2: KClass<User> = user.javaClass.kotlin

有 KClass 对象后,就可以调用方法了。

比如现在有个类 Tool

class Tool {
    private var name = ""
    var id: Int = 0

    constructor() {}

    constructor(name: String, id: Int) {
        this.name = name
        this.id = id
    }

    private fun test() {
        Log.i(tag, "test")
    }

    fun study(text: String) {
        Log.i(tag, "study $text")
    }
}

在 Kotlin 中,默认的无参构造函数行为与 Java 不同。当你在 Kotlin 类中定义了一个明确的构造函数时,编译器不会自动为你生成默认的无参构造函数。

可以调用如下方法去获取属性和方法

val kClass = Tool::class

kClass.declaredMemberProperties.forEach {
    // 获取该类声明的非扩展属性
}
kClass.memberProperties.forEach {
    // 获取该类和所有超类中声明的非扩展属性
}

kClass.declaredMemberFunctions.forEach {
    // 获取该类中声明的非扩展非静态函数
}
kClass.memberFunctions.forEach {
    // 获取该类和所有超类中声明的非扩展非静态函数
}

kClass.members.forEach {
    // 获取该类中可访问的所有函数和属性,但不包括构造函数。
}

需要注意的是,这里说的静态,是指 Java 类的静态。

也可以获取相关的类信息,比如

// 返回类名
val simpleName = kClass.simpleName
// 返回类的全包名
val qualifiedName = kClass.qualifiedName
// 返回类的可见性,包括 PUBLIC,PROTECTED,INTERNAL,PRIVATE
val visibility = kClass.visibility
// 返回该类是否为 open
val isOpen = kClass.isOpen
// 该类的直接超类的列表
kClass.supertypes.forEach {
    
}

从上面的 Tool 类中,有两个构造函数,可以用来创建对象。

val clazz = Tool::class
// 创建对象,该方法调用的是无参构造创建实例,需要先确保该类有无参构造函数
val instance = clazz.createInstance()

// 通过第二个构造函数创建
val secondInstance =
    clazz.constructors.find { it.parameters.size == 2 }?.call("Android", 888)

有了对象之后就可以访问属性和方法了

val clazz = Tool::class
val instance = clazz.createInstance()
// 调用方法
clazz.declaredFunctions.forEach {
    when (it.name) {
        "test" -> {
            // 设置可访问性,调用私有方法需要置为 true
            it.isAccessible = true
            it.call(instance)
        }

        "study" -> {
            it.isAccessible = true
            it.call(instance, "Android")
        }
    }
}
// 访问属性
clazz.declaredMemberProperties.forEach {
    if (it.name == "name") {
        it as KMutableProperty1<Tool, String>
        it.isAccessible = true
        // 设置私有属性 name 的值
        it.set(instance, "AJianJun")
        // 获取私有属性 name 的值
        val name = it.get(instance)
    } else if (it.name == "id") {
        it as KMutableProperty1<Tool, Int>
        it.isAccessible = true
        // 设置属性 id 的值
        it.set(instance, 100)
        // 获取属性 id 的值
        val id = it.get(instance)
    }
}

从中我们可以看到,name 为私有属性,test 为私有方法,我们照样可以通过反射来访问他们,只需设置 isAccessible 为 true 即可,这就是反射的特权,但是这些都需要小心操作,否则会影响代码安全。

注解

注解是一种代码标签,可以给特定的注解代码标注一些额外的信息,这些信息可以保留在源码期,编译期和运行期,可以通过反射获取标签的信息来处理实际的代码逻辑,注解是不直接影响代码执行的。

元注解

Kotlin 注解类自己本身也可以被注解,可以给注解类加注解,这种注解被称为元注解,Kotlin 的元注解主要有四种:@Target,@Retention,@Repeatable,@MustBeDocumented。

Target

Target 就是目标对象,可以同时指定一个或多个目标对象,目标对象如下所示:

public enum class AnnotationTarget {
    /** Class, interface or object, annotation class is also included */
    CLASS,
    /** Annotation class only */
    ANNOTATION_CLASS,
    /** Generic type parameter */
    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
}

Retention

Retention 表示注解保留的时间,有三种时期:

public enum class AnnotationRetention {
    /** 源码期 */
    SOURCE,
    /** 编译期 */
    BINARY,
    /** 运行期 */
    RUNTIME
}

MustBeDocumented

MustBeDocumented 是用于指示某个注解应该被包含在生成的文档中,它对注解本身的行为没有任何影响,只是起到一个提示作用。

Repeatable

Repeatable 的作用是允许将同一种注解多次应用于同一个元素上,下面举个例子:

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@Repeatable
annotation class MyAnnotation(val value: Int)

@MyAnnotation(100)
@MyAnnotation(200)
fun work() {

}

使用自定义注解

在 Android 开发中,需要使用危险权限时,就要动态的检查和申请权限,可以通过注解来处理这个逻辑,这里要实现的,就是检查和申请一个 Activity 中的所有需要的权限。

先定义一个注解

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class CheckPermission(val permission: Array<String>)

增加 Activity 的扩展方法

fun Activity.initPermissions() {
    val kClass = this::class
    kClass.findAnnotation<CheckPermission>()?.let { checkPermission ->
        val permissions = arrayListOf<String>()
        // 拿到注解上的值
        checkPermission.permission.forEach {
            // 检查是否有这个权限
            if (ContextCompat.checkSelfPermission(this, it) !=
                PackageManager.PERMISSION_GRANTED
            ) {
                permissions.add(it)
            }
        }
        if (permissions.isNotEmpty()) {
            // 申请权限
            requestPermissions(this, permissions)
        }
    }
}

在需要权限的 Activity 中添加注解即可

@CheckPermission([Manifest.permission.CALL_PHONE, Manifest.permission.CAMERA])
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initPermissions()
    }
}

<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.CALL_PHONE"/>

这里只是为了讲解 Android 注解而举的一个简单的例子,实际开发中,权限处理有很多成熟稳重的库,处理得更加完善,比如郭神的 PermissionX,就很不错!

需要注意的是,反射和注解虽然强大,但由于其特性和使用方式,可能会对性能产生一些负面影响。因为使用反射需要在运行时动态地获取类的信息,这涉及到额外的计算和内存开销,相比于直接调用方法或访问属性,反射操作通常会更慢,造成的性能开销较高。其次,反射可以绕过访问修饰符的限制,使得可以访问并修改本来不应该被公开的方法和属性,这可能导致安全漏洞和意外的行为。所以,请谨慎使用!

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

全套视频资料:

一、面试合集

在这里插入图片描述
二、源码解析合集
在这里插入图片描述

三、开源框架合集
在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取↓↓↓

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值