Kotlin 扩展函数与 Android KTX

@fython  April 11, 2018

2017 年,Google 官方宣布支持 Kotlin 作为 Android (应用)开发语言,尽管不会替代 Java 成为主要开发语言,但它意味着能够得到更多的支持帮助,学习资源也会更加丰富。Kotlin 仍是运行在 JVM 上的一门语言,但它带来了许多原来 Java 所没有的语言特性和语法糖,“扩展函数” 就是其中之一。

什么是扩展函数?

扩展函数/扩展方法(Extension Functions),可以让你在不修改原有类也不需要继承类就可以为其补充函数。

这个概念在 C# 及其它语言上已经有,享受它为 JVM/Android 平台带来的便利前,我们先简单了解一下它的使用:

声明一个扩展函数

通常来讲,扩展函数主要由下列部分组成:

  1. Receiver type: 接收者的类型,也就是你要为哪个类扩展函数
  2. Function name: 函数名称
  3. Parameters: 参数
  4. Returns type: 返回参数的类型
  5. Function body: 函数体

例如:

fun Int.foo(a: Int): Int {
    return this + a
}

可以看到上面为 Int 定义了一个名为 foo 的扩展函数,接收一个参数并将其和 receiver 相加得到的和作为结果返回。那么使用时所有 Int 对象都可以调用这个扩展函数:

100.foo(200) == 300 // it is true

当要使用带泛型的类作为接收者类型时,由于 Kotlin 不允许在编写时擦除泛型,需要结合泛型函数定义,这里不作例子,请自行实践。

Extensions are resolved statically

扩展函数最终会被编译为静态的方法,不会对原有的类代码造成影响。因此在使用上也会有所限制:

  • 普通的 Java 反射方法会找不到扩展函数。
  • 使用时是静态解析而不是动态解析。例如:假设 B 类继承于 A 类,一个 B 对象被声明为 A 类型并传递给一个变量,当调用这个变量的扩展函数时,被调用的扩展函数是来自于 A 类而不是 B 类。

Android KTX

如果你用过 ButterKnife 这一个开源库,那么你应该知道 Jake Wharton 这个名字,之前这位大神在 Square 公司时开发了许多非常棒的开源库。Kotlin 成为了 Google 所支持的 Android (应用)开发语言不久后,他也跳槽到了 Google 的 Android 部门,为开发者优化 Kotlin 开发体验,先后为 Android Developers 部分文档加入了 Kotlin 语言选项、编写了 Kotlin 代码风格规范(这里我做了一个简单的简体中文翻译版本),再到现在推出了 Android KTX——Android 框架的扩展库。

Android KTX 也许是 "Android KoTlin eXtension" 的缩写,它的目的是扩展原来框架中的类,这看起来就像很多 XXXUtils 类第三方库一样,但由 Google 官方来维护,功能上的设计会比第三方更加谨慎,尽管如此它还是有许多槽点(下一节会提到),此处先简单介绍一些十分有用的扩展函数:

bundleOf(varargs pairs: Pair<String, Any>)

Bundle 是 Android 开发中十分常用的一个可序列化的 Map 类,在 Activity、Fragment、BroadcastReceiver、Service 间传递数据都需要用到它,在 Java 中创建一个 Bundle 并放入参数,通常写法是:

Bundle bundle = new Bundle();
bundle.putString("key0", strValue);
bundle.putInt("key1", 1);
bundle.putBoolean("key2", false);

如果你已经学过 Kotlin 就会了解到 listOf()mapOf() 之类的函数让我们在创建 List 或者 Map 的时候非常方便,实际上大多我们创建 Bundle 时放入的参数量都是确定的,我们就可以使用来自 Android KTX 的 bundleOf() 来快速地完成创建和赋值:

val bundle = bundleOf (
    "key0" to strValue,
    "key1" to 1,
    "key2" to false,
)

View#updatePadding()

为 View 的一个方向单独设置 Padding 的时候,往往都需要先获取其它方向的 Padding,然后在设置时一同传入 View#setPadding 以保持不变,这显得十分蠢。我不确定为什么当时不为每个方向单独制作一个 setPaddingXXX 函数,至少现在通过 Android KTX 扩展出了一个方便的方法:

当我只需要改动顶部的 padding 时,执行下面的语句即可:

view.updatePadding(top = 16f)

如果 View 使用 start/end 代替 left/right,请使用 View#updatePaddingRelative() 方法。

利弊权衡

扩展函数带来的便利从上面的使用介绍中相信读者已经感受到了,这里就直接从它的缺点开始说吧。

在几年前我也曾尝试去使用 Kotlin 但并不打算用它来写任何库,因为绝大部分人还在使用 Java,也许会对引入作用不大的 Kotlin 标准库反感。而去年 Kotlin 再度映入大众眼帘中,不得不承认我为 Kotlin 造了一些轮子,还是一些“无用”的轮子,看起来能够加速我的新 Kotlin App 开发,但缺少系统地整理和谨慎的设计,它反而为我带来了麻烦(当我想从自己的轮子中迁移出来时):我在其中运用了大量的扩展函数,有些看起来十分方便,却对代码的可读性造成了很大的影响。

我反省自己的轮子过渡滥用扩展函数的同时,看到了 Google 官方的 Android KTX 也有欠佳的函数设计,例如 androidx.graphics.drawable 中对 Int 的扩展函数 fun Int.toDrawable(): ColorDrawable
每个开发者都应该在使用前阅读文档没错,然后你会了解到这是一个将整数作为接收者转换为 ColorDrawable 一个纯色图案。但是如果我的整数接收者实际上是一个资源 ID 而不是颜色值,我尝试将 toDrawable 这个名字理解为从资源 ID 获取到一个 Drawable 资源,到了使用时才发现这个函数不是我想要的。

在 Android KTX 之前,还有 Jetbrains 公司同为 Android 开发者推出的 Anko 库,目的基本是一样,它其中有一个十分亮眼的功能是使用 Kotlin 代替 XML 来创建布局,它十分简洁,但扩展性和可读性不如 XML,配套的预览功能也很不完善,从其他使用者的反馈来看都是失望居多,大概也算是鸡肋吧。

一些扩展函数的设计会显得过分简洁,混淆了视听,而原来常用的工具类中设计静态函数也不见得多啰嗦,还可以将同类的功能放到同一个 class / object 中,十分条理。

个人不是一昧地否认,我还是很喜欢一些类似 bundleOf() 这样的函数设计,只是说比起过去在 Java 中常用的设计工具类,在 Kotlin 中设计扩展函数的过程需要更多更多的谨慎。


添加新评论