共计 4612 个字符,预计需要花费 12 分钟才能阅读完成。
两年前,Android 开源我的项目 (AOSP) 利用 团队开始应用 Kotlin 代替 Java 重构 AOSP 利用。之所以重构次要有两个起因: 一是确保 AOSP 利用可能遵循 Android 最佳实际,另外则是提供优先应用 Kotlin 进行利用开发的良好范例。Kotlin 之所以具备弱小的吸引力,起因之一是其简洁的语法,很多状况下用 Kotlin 编写的代码块的代码数量相比于性能雷同的 Java 代码块要更少一些。此外,Kotlin 这种具备丰盛表现力的编程语言还具备其余各种长处,例如:
- 空平安: 这一概念能够说是根植于 Kotlin 之中,从而帮忙防止破坏性的空指针异样;
- 并发: 正如 Google I/O 2019 中对于 Android 的形容,结构化并发 (structured concurrency) 可能容许应用协程简化后盾的工作治理;
- 兼容 Java: 尤其是在这次的重构我的项目中,Kotlin 与 Java 语言的兼容性可能让咱们一个文件一个文件地进行 Kotlin 转换。
AOSP 团队在去年夏天发表了一篇文章,具体介绍了 AOSP 桌面时钟利用的转换过程。而往年,咱们将 AOSP 日历利用从 Java 转换成了 Kotlin。在这次转换之前,利用的代码行数超过 18,000 行,在转换后代码库缩小了约 300 行。在这次的转换中,咱们因循了同 AOSP 桌面时钟转换过程中相似的技术,充分利用了 Kotlin 与 Java 语言的互操作性,对代码文件一一进行了转换,并在过程中应用独立的构建指标将 Java 代码文件替换为对应的 Kotlin 代码文件。因为团队中有两个人在进行此项工作,所以咱们在 Android.bp 文件中为每个人创立了一个 exclude_srcs 属性,这样两个人就能够在缩小代码合并抵触的前提下,都可能同时进行重构并推送代码。此外,这样还能容许咱们进行增量测试,疾速定位谬误呈现在哪些文件。
在转换任意给定的文件时,咱们一开始先应用 Android Studio Kotlin 插件中提供的 从 Java 到 Kotlin 的主动转换工具。尽管该插件胜利帮忙咱们转换了大部份的代码,然而还是会遇到一些问题,须要开发者手动解决。须要手动更改的局部,咱们将会在本文接下来的章节中列出。
在将每个文件转换为 Kotlin 之后,咱们手动测试了日历利用的 UI 界面,运行了单元测试,并运行了 Compatibility Test Suite (CTS) 的子集来进行性能验证,以确保不须要再进行任何的回归测试。
主动转换之后的步骤
下面提到,在应用主动转换工具之后,有一些重复呈现的问题须要手动定位解决。在 AOSP 桌面时钟文章中,具体介绍了其中遇到的一些问题以及解决办法。如下列出了一些在进行 AOSP 日历转换过程中遇到的问题。
用 open 关键词标记父类
咱们遇到的问题之一是 Kotlin 父类和子类之间的互相调用。在 Kotlin 中,要将一个类标记为可继承,必须得在类的申明中增加 open
关键字,对于父类中被子类笼罩的办法也要这样做。然而在 Java 中的继承是不须要应用到 open
关键字的。因为 Kotlin 和 Java 可能互相调用,这个问题直到大部分代码文件转换到了 Kotlin 才呈现。
例如,在上面的代码片段中,申明了一个继承于 SimpleWeeksAdapter
的类:
class MonthByWeekAdapter(context: Context?, params:
HashMap<String?, Int?>) : SimpleWeeksAdapter(context as Context, params) {// 办法体}
因为代码文件的转换过程是一次一个文件进行的,即便是齐全将 SimpleWeeksAdapter.kt
文件转换成 Kotlin,也不会在其类的申明中呈现 open
关键词,这样就会导致一个谬误。所以之后须要手动进行 open 关键词的增加,以便让 SimpleWeeksAdapter
类能够被继承。这个非凡的类申明如下所示:
open class SimpleWeeksAdapter(context: Context, params: HashMap<String?, Int?>?) {// 办法体}
override 修饰符
同样地,子类中笼罩父类的办法也必须应用 override
修饰符来进行标记。在 Java 中,这是通过 @Override
注解来实现的。然而,尽管在 Java 中有相应的注解实现版本,然而主动转换过程中并没有为 Kotlin 办法申明中增加 override
修饰符。解决的方法是在所有适当的中央手动增加 override
修饰符。
覆写父类中的属性
在重构过程中,咱们还遇到了一个属性覆写的异样问题,当一个子类申明了一个变量,而在父类中存在一个非公有的同名变量时,咱们须要增加一个 override
修饰符。然而,即便子类的变量同父类变量的类型不同,也依然要增加 override
修饰符。在某些状况下,增加 override
仍不能解决问题,尤其是当子类的类型齐全不同的时候。事实上,如果类型不匹配,在子类的变量前增加 override
修饰符,并在父类的变量前增加 open
关键字,会导致一个谬误:
type of *property name* doesn’t match the type of the overridden var-property
这个报错很让人纳闷,因为在 Java 中,以下代码能够失常编译:
public class Parent {int num = 0;}
class Child extends Parent {String num = "num";}
而在 Kotlin 中相应的代码就会报下面提到的谬误:
class Parent {var num: Int = 0}
class Child : Parent() {var num: String = "num"}
这个问题很有意思,目前咱们通过在子类中对变量重命名来躲避了这个抵触。下面的 Java 代码会被 Android Studio 目前提供的代码转换器转换为有问题的 Kotlin 代码,这甚至 被报告为是一个 bug 了。
import 语句
在咱们转换的所有文件中,主动转换工具都偏向于将 Java 代码中的所有 import 语句截断为 Kotlin 文件中的第一行。最开始这导致了一些很让人抓狂的谬误,编译器会在整个代码中报 “unknown references” 的谬误。在意识到这个问题后,咱们开始手动地将 Java 中的 import 语句粘贴到 Kotlin 代码文件中,并独自对其进行转换。
裸露成员变量
默认状况下,Kotlin 会主动地为类中的实例变量生成 getter 和 setter 办法。然而,有些时候咱们心愿一个变量仅仅只是一个简略的 Java 成员变量,这能够通过应用 @JvmField
注解来实现。
@JvmField 注解
的作用是 “ 批示 Kotlin 编译器不要为这个属性生成 getter 和 setter 办法,并将其作为一个成员变量容许其被公开拜访 ”。这个注解在 CalendarData 类 中特地有用,它蕴含了两个 static final 变量。通过对应用 val 申明的只读变量应用 @JvmField 注解,咱们确保了这些变量能够作为成员变量被其余类拜访,从而实现了 Java 和 Kotlin 之间的兼容性。
对象中的静态方法
在 Kotlin 对象中定义的函数必须应用 @JvmStatic
进行标记,以容许在 Java 代码中通过办法名,而非实例化来对它们进行调用。也就是说,这个注解使其具备了相似 Java 的办法行为,即可能通过类名调用办法。依据 Kotlin 的文档,” 编译器会为对象的外部类生成一个静态方法,而对于对象自身会生成一个实例办法。” 咱们在 Utils 文件 中遇到了这个问题,当实现转换后,Java 类就变成了 Kotlin 对象。随后,所有在对象中定义的办法都必须应用 @JvmStatic 标记,这样就容许在其余文件中应用 Utils.method() 这样的语法来进行调用。值得一提的是,在类名和办法名之间应用 .INSTANCE (即 Utils.INSTANCE.method()) 也是一种抉择,然而这不太合乎常见的 Java 语法,须要扭转所有对 Java 静态方法的调用。
性能评估剖析
所有的基准测试都是在一台 96 核、176 GiB 内存的机器上进行的。本我的项目中剖析用到的次要指标有所缩小的代码行数、指标 APK 的文件大小、构建工夫和首屏从启动到显示的工夫。在对上述每个因素进行剖析的同时,咱们还收集了每个参数的数据并以表格的形式进行了展现。
缩小的代码行数
从 Java 齐全转换到 Kotlin 后,代码行数从 18,004 缩小到了 17,729。这比原来的 Java 代码量 缩小了大概 1.5%。尽管缩小的代码量并不可观,但对于一些大型利用来说,这种转换对于缩小代码行数的成果可能更为显著,可参阅 AOSP 桌面时钟 文中所举的例子。
指标 APK 大小
应用 Kotlin 编写的利用 APK 大小是 2.7 MB,而应用 Java 编写的利用 APK 大小是 2.6 MB。能够说这个差别根本能够忽略不计了,因为蕴含了一些额定的 Kotlin 库,所以 APK 体积上的减少,实际上是能够预期的。这种大小的减少能够通过应用 Proguard 或 R8 来进行优化。
编译工夫
Kotlin 和 Java 利用的构建工夫是通过取 10 次从零进行残缺构建的工夫的平均值来计算的 (不蕴含异样值),Kotlin 利用 的均匀构建工夫为 13 分 27 秒 ,而 Java 利用 的均匀构建工夫为 12 分 6 秒。据一些材料 (如 “Java 和 Kotlin 的区别 ” 以及 “Kotlin 和 Java 在编译工夫上的比照 ”) 显示,Kotlin 的编译工夫事实上比 Java 要更耗时,特地是对于从零开始的构建。一些剖析断言,Java 的编译速度会快 10-15%,又有一些剖析称这一数据为 15-20%。拿咱们的例子进行从零开始残缺构建所破费的工夫来说,Java 的编译速度比 Kotlin 快 11.2%,只管这个渺小的差别并不在上述范畴内,但这有可能是因为 AOSP 日历是一个绝对较小的利用,仅有 43 个类。只管从零开始的残缺构建比较慢,然而 Kotlin 依然在其余方面占有优势,这些劣势更应该被思考到。例如,Kotlin 绝对于 Java,更简洁的语法通常能够保障较少的代码量,这使得 Kotlin 代码库更易保护。此外,因为 Kotlin 是一种更为平安无效的编程语言,咱们能够认为残缺构建工夫较慢的问题能够忽略不计。
首屏显示的工夫
咱们应用了这种 办法 来测试利用从启动到齐全显示首屏所须要的工夫,通过 10 次试验后咱们发现,应用 Kotlin 利用 的均匀工夫约为 197.7 毫秒,而 Java 的则为 194.9 毫秒。这些测试都是在 Pixel 3a XL 设施上进行的。从这个测试后果能够得出结论,与 Kotlin 利用相比,Java 利用可能具备渺小的劣势;然而,因为均匀工夫十分靠近,这个差别简直能够忽略不计。因而,能够说 AOSP 日历利用转换到 Kotlin,并没有对利用的初始启动工夫产生负面影响。
论断
将 AOSP 日历利用转换为 Kotlin 大概花了 1.5 个月 (6 周) 的工夫,由 2 名实习生负责该项目标施行。一旦咱们对代码库更加相熟并更加长于解决重复呈现的编译时、运行时和语法问题时,效率必定会变得更高。总的来说,这个非凡的我的项目胜利地展现了 Kotlin 如何影响现有的 Android 利用,并在对 AOSP 利用进行转换的道路中迈出了松软的一步。
欢迎您 点击这里 向咱们提交反馈,或分享您喜爱的内容、发现的问题。您的反馈对咱们十分重要,感谢您的反对!