kotlin选择表达_为什么选择Kotlin?

kotlin选择表达

One of the main goals of Android Open Source Project (AOSP) applications is to serve as an example to developers on how to build Android applications. As a part of this commitment, the AOSP apps are developed with Android best practices in mind, including the use of Kotlin as their development language. Recently, in pursuit of this goal, we began refactoring the AOSP DeskClock from Java to Kotlin. Through this process, much was learned about what developers could expect when converting their own apps. This article touches on some hurdles that were encountered during this process and provides tips that you can use to reduce the workload for a similar conversion. You’ll also learn about the benefits of converting from Java to Kotlin, focusing on improvements that were seen as a result of the DeskClock conversion.

Android开放源代码项目(AOSP)应用程序的主要目标之一是为开发人员提供有关如何构建Android应用程序的示例。 作为此承诺的一部分,AOSP应用程序的开发考虑了Android最佳实践,包括使用Kotlin作为其开发语言。 最近,为了实现这一目标,我们开始将AOSP DeskClock从Java重构为Kotlin。 通过此过程,我们学到了很多有关开发人员在转换自己的应用程序时可能期望的内容。 本文介绍了此过程中遇到的一些障碍,并提供了可用于减少类似转换工作量的技巧。 您还将了解从Java转换为Kotlin的好处,重点是DeskClock转换带来的改进。

为什么选择Kotlin? (Why Kotlin?)

We chose to convert the AOSP DeskClock app in part due to Kotlin’s many benefits over Java, including the following:

我们之所以选择转换AOSP DeskClock应用程序,部分原因是Kotlin与Java相比具有许多优势,其中包括:

  • Built-in null safety, which can substantially reduce null pointer exceptions in your app.

    内置的null安全性,可以大大减少应用程序中的null指针异常。
  • Conciseness, or writing less code to do more work.

    简洁,或编写更少的代码来完成更多工作。
  • Google’s “Kotlin-first” support for Android development means that new Android development tools and content such as Jetpack libraries and online training will be built with Kotlin users first in mind.

    Google对Android开发的“Kotlin优先”支持意味着新的Android开发工具和内容(如Jetpack库和在线培训)将首先考虑Kotlin用户的需求。

Additionally, many developer surveys have ranked Kotlin as one of the most loved languages to work with in terms of developer satisfaction. All of these benefits together can lead to a much smoother process when maintaining your codebase in Kotlin rather than Java.

此外,就开发人员的满意度而言, 许多开发人员调查都将Kotlin列为最受欢迎的语言之一。 当使用Kotlin而不是Java维护代码库时,所有这些优点加在一起可以使流程更加流畅。

转换过程 (The Conversion Process)

The DeskClock app is quite large, containing 160 Java files with a total of 31,914 lines of code before conversion began. Because of its size, the code had to be converted incrementally. In AOSP, this was done by creating a separate Soong build target (DeskClockKotlin), excluding any Java files with Kotlin equivalents. Kotlin’s interoperability with Java made this process straightforward by allowing the DeskClockKotlin target to continually include more Kotlin files as the corresponding Java files were converted.

DeskClock应用程序非常大,转换开始前包含160个Java文件,总共31,914行代码。 由于其大小,必须对代码进行增量转换。 在AOSP中,这是通过创建一个单独的Soong构建目标(DeskClockKotlin)来完成的,该目标不包括任何与Kotlin等效的Java文件。 Kotlin与Java的互操作性通过允许DeskClockKotlin目标在转换相应的Java文件时连续包含更多Kotlin文件,从而使此过程变得简单。

The first conversion step was to run the automatic Kotlin conversion tool from the Kotlin plugin in Android Studio. This plugin automatically converts code from Java to Kotlin and generally works well, though you might see some common issues that must be manually corrected to make the Kotlin code more idiomatic and robust. The incremental conversion helped to ensure the correctness of the converted code by targeting only a small portion of app functionality at a time. In addition to manual testing, we also ran the Compatibility Test Suite (CTS) for the DeskClock app to check for possible feature regressions.

第一步是从Android Studio中的Kotlin插件运行自动Kotlin转换工具 。 该插件会自动将Java代码转换为Kotlin,并且通常可以正常运行,尽管您可能会看到一些常见问题,必须手动纠正这些问题才能使Kotlin代码更加惯用和强大。 一次仅针对一小部分应用程序功能,增量转换有助于确保转换后代码的正确性。 除了手动测试,我们还为DeskClock应用程序运行了兼容性测试套件(CTS) ,以检查可能的功能回归。

要做的手工工作 (Manual Work To Be Done)

During the conversion process, we ran into some issues that required more manual work after running the auto-conversion tool. This section discusses a few of the more complex issues in depth, followed by short summaries of additional minor issues.

在转换过程中,我们遇到了一些问题,这些问题在运行自动转换工具后需要更多的手动工作。 本节深入讨论一些较复杂的问题,然后简要概述其他较小的问题。

无法在某些文件上运行自动转换 (Unable to Run Auto-Conversion on Some Files)

One of the most time-consuming issues we encountered was the inability to run the Kotlin plugin’s auto-conversion tool on certain Java files. This was not a common issue, occurring only twice in the first ~75 files converted. However, we did need to either rewrite the Java code completely in Kotlin or spend time investigating the issue.

我们遇到的最耗时的问题之一是无法在某些Java文件上运行Kotlin插件的自动转换工具。 这不是一个常见的问题,在转换后的前75个文件中仅发生了两次。 但是,我们确实需要在Kotlin中完全重写Java代码,或者花时间研究此问题。

We first encountered this problem with the ClockDatabaseHelper.java file. In that file, a try-with-resources block was causing the issue.

我们首先在ClockDatabaseHelper.java文件中遇到此问题。 在该文件中, try-with-resources块导致了该问题。

try (Cursor cursor = db.query(…)) {
// some code in here
}

This try-with-resources statement does not exist within Kotlin, though it does have an equivalent in the use function. The try-with-resources statement in Java handles the releasing of the given resource once it is no longer in use (i.e. after the try block). Similarly, the Kotlin use function takes a lambda expression and disposes of the resources that use was called on once the lambda has been executed. One way to write the code from above in Kotlin would be as follows:

尽管try-with-resources语句在use 函数中具有等效功能 ,但它在Kotlin中并不存在。 Java中的try-with-resources语句在不再使用给定资源时(即在try块之后)处理释放给定资源。 同样,Kotlin的使用 函数采用Lambda表达式并处理使用的资源 一旦执行了lambda,就会被调用。 从上面用Kotlin编写代码的一种方法如下:

val cursor = db.query(…)
cursor.use {
// some code in here
}

The converter could not automatically make this change. In this case, we manually updated the Java code to declare the Cursor before the try block, instead of in the try-with-resources statement, which allowed the conversion tool to run successfully.

转换器无法自动进行此更改。 在这种情况下,我们手动更新了Java代码以在try块之前声明Cursor ,而不是在try-with-resources语句中声明,这使转换工具能够成功运行。

The second file that was unable to be converted automatically was the LogEventTracker.java file. This file contained a function defined as follows:

无法自动转换的第二个文件是LogEventTracker.java文件。 该文件包含定义如下的功能:

private String safeGetString(@StringRes int resId) {
return resId == 0 ? null : mContext.getString(resId);
}

The conversion tool was unable to handle the ternary expression being returned from this function. It seems that the tool is unable to deduce the correct return type of the corresponding Kotlin function (String? in this case). We manually rewrote this function as follows:

转换工具无法处理从此函数返回的三元表达式 。 看来该工具无法推断出相应Kotlin函数的正确返回类型(在这种情况下为String? )。 我们手动重写了此功能,如下所示:

private fun safeGetString(@StringRes resId: Int): String? {
return if (resId == 0) null else context.getString(resId)
}

In both of these cases, the manual work to rewrite the Java code is minimal. However, the converter skips the entire file when encountering issues like these. This means that for large files that contain minor issues, without knowing the root cause beforehand, manual investigation into the source of the issue is required.

在这两种情况下,重写Java代码的手动工作都是最少的。 但是,当遇到此类问题时,转换器将跳过整个文件。 这意味着对于包含小问题的大文件,如果事先不知道根本原因,则需要对问题的来源进行手动调查。

One method that we used to discover issues was to comment out the majority of the code in the file and then incrementally un-comment code until encountering the issue. This can be a time-effective way to figure out what code snippet is responsible.

我们用来发现问题的一种方法是注释掉文件中的大多数代码,然后逐步取消注释代码,直到遇到问题为止。 这是找出哪个代码段负责的省时方法。

静态常量继承 (Static Constant Inheritance)

An additional complex issue we encountered was the difference in inheritance abilities between Java and Kotlin. This issue occurred in the ClockContract.java file, where static constant values are defined within interfaces that inherit from each other. This is not a problem in itself. However, many places in the codebase attempted to reference the constants through children of these interfaces, which is not possible in Kotlin.

我们遇到的另一个复杂问题是Java和Kotlin之间的继承能力不同。 此问题发生在ClockContract.java文件中,该文件在彼此继承的接口中定义了静态常量值。 这本身不是问题。 但是,代码库中的许多地方都试图通过这些接口的子级引用常量,这在Kotlin中不可能的

A common way to define static constants in Kotlin to be referenced in Java code is within companion objects, in this case within the interfaces the constants are to be defined in. Due to the incremental conversion, the ClockContract interfaces were referenced from Java code, so this companion object approach was used. The difference between Java and Kotlin here is that companion objects are not inherited from the parents of an interface. For example, a static constant defined in the companion object of an interface A, where interface B inherits from A, would not be able to be accessed by calling B.CONSTANT_NAME. This method of access would work fine in Java.

在Java对象中定义要在Java语言中引用的Kotlin中的静态常量的一种常用方法是在伴随对象内,在这种情况下,将在常量中定义接口。由于增量转换, ClockContract接口是从Java代码中引用的,因此使用了这种伴随对象方法。 Java和Kotlin之间的区别在于,伴随对象不是从接口的父对象继承的。 例如, B.CONSTANT_NAME调用B.CONSTANT_NAME来访问在接口A的伴随对象中定义的静态常量,接口B从接口A继承来。 这种访问方法在Java中可以正常工作。

To solve this problem, we chose to replace the affected usages of these interfaces with calls to the constants in the interfaces where they are defined. Continuing the example from the last paragraph, we would instead refer to A.CONSTANT_NAME, instead of trying to refer to B.CONSTANT_NAME, since CONSTANT_NAME is defined in interface A. This problem could perhaps be solved differently by changing the overall structure of the inheritance of these constants, likely not using interfaces in Kotlin. However, the approach taken was straightforward to implement.

为了解决此问题,我们选择用对定义它们的接口中的常量的调用来替换这些接口的受影响用法。 继续上一段的示例,我们将改为引用A.CONSTANT_NAME ,而不是尝试引用B.CONSTANT_NAME ,因为CONSTANT_NAME是在接口A中定义的。可以通过更改继承的总体结构来解决此问题。这些常量中的一个,很可能不在Kotlin中使用接口。 但是,采用的方法很容易实现。

手动可空性修复 (Manual Nullability Fixes)

One other common issue that came up during the conversion process was the occasional inaccuracy of nullable types in the converted Kotlin code. For example, converted code incorrectly specified that a function accepts a String instead of a String? parameter, which could result in runtime errors. These issues can be avoided if the converter tool is able to deduce from the Java code whether or not a parameter or return type is able to be null. You can annotate Java code with @Nullable and @NonNull to give the IDE the information needed to make this decision correctly. Otherwise, it’s possible to manually fix these nullability issues in the resulting Kotlin by tracing through the code and determining whether it is possible to receive or return a null value.

转换过程中出现的另一个常见问题是,转换后的Kotlin代码中的可空类型偶尔不准确 。 例如,转换后的代码错误地指定了函数接受String而不是String? 参数,这可能会导致运行时错误。 如果转换器工具能够从Java代码推断出参数或返回类型是否可以为null,则可以避免这些问题。 您可以使用@Nullable@NonNull注释Java代码,以向IDE提供正确做出此决定所需的信息。 否则,可以通过跟踪代码并确定是否可以接收或返回null值来手动修复生成的Kotlin中的这些可空性问题。

其他问题 (Additional Issues)

In addition to these more complex issues, we encountered multiple smaller issues during the conversion process. Each issue had a quick fix that didn’t take much time to address.

除了这些更复杂的问题之外,我们在转换过程中还遇到了多个较小的问题。 每个问题都有一个快速解决方法,不需要花费很多时间来解决。

The generated Kotlin code sometimes included the deprecated uses of “===” and “!==” while comparing two Int values. Using “==” or “!=” suffices for comparing Int values.

在比较两个Int值时,生成的Kotlin代码有时包括不赞成使用的“ ===”和“!==”。 使用“ ==”或“!=” 足以比较Int

Some @NonNull annotations present in the Java code are not removed in the resulting Kotlin code. These annotations are not needed in Kotlin due the nullability safety the language provides, so they were removed.

Java代码中存在的某些@NonNull注释不会在生成的Kotlin代码中删除。 由于语言提供的可空性安全性,因此Kotlin不需要这些注释,因此将其删除。

Occasionally, single line comments within Java functions appeared in two different places in the resulting Kotlin code. These comments appeared both in their proper place and also in general class scope outside of the function they are meant to be in. We removed these second instances.

有时,Java函数中的单行注释会在结果Kotlin代码的两个不同位置出现。 这些注释不仅出现在其应有的位置,而且出现在它们应包含的功能之外的常规类范围内。我们删除了第二个实例。

The generated Kotlin code excluded blank lines that were present in the equivalent Java code. This can result in some functions that were spaced out to separate portions of logic to have all their lines together in the resulting Kotlin code.

生成的Kotlin代码排除了等效Java代码中存在的空白行。 这可能会导致某些功能被分隔开来分隔逻辑的各个部分,以使所有行在生成的Kotlin代码中在一起。

When functions that accept Long parameters are instead passed an Int value, the generated Kotlin code sometimes adds “as Long” to attempt to cast the Int to a Long. However the correct approach is to call toLong() on the Int. This also applies when used with other number types (e.g. Float, etc.).

当将接受Long参数的函数传递给Int值时,生成的Kotlin代码有时会添加“ as Long ”以尝试强制转换Int 到了长 但是正确的方法是在Int上调用toLong() 。 与其他类型的数目(例如,使用时,这也适用于Float )。

In Java, you can reassign function parameters. In Kotlin, however, function parameters are defined as val behind the scenes and thus cannot be reassigned. In the generated Kotlin code for this scenario, a local var is defined within the function having the same name as the function parameter. To avoid shadowing of a parameter’s name by the local var, it could be beneficial to rename the local var so as to increase the function’s readability by not sharing the same name between a parameter and local variable.

在Java中,您可以重新分配函数参数。 但是,在Kotlin中,功能参数被定义为幕后的val ,因此无法重新分配。 在为此场景生成的Kotlin代码中,在函数内定义了一个与函数参数同名的局部变量。 为避免局部变量对参数名称的影响 ,重命名局部变量可能会有所帮助 通过在参数和局部变量之间不共享相同的名称来提高函数的可读性。

When a private Java property with a private getter method is converted to Kotlin, the private keyword sometimes remains on the get() accessor function for the property. This is unnecessary, however, since the property being declared private is enough.

当具有private getter方法的private Java属性转换为Kotlin时, private关键字有时会保留在该属性的get()访问器函数上。 但是,这是不必要的,因为将属性声明为private就足够了。

快速转换技巧 (Quick Conversion Tips)

The following list aims to quickly summarize the more detailed explanations of the issues encountered above into actionable items to help speed up the conversion process:

以下列表旨在将对上面遇到的问题的更详细的解释快速总结为可操作的项目,以帮助加快转换过程:

  • Ensure usages of “===” and “!==” are used for checking referential equality

    确保使用“ ===”和“!==”来检查引用相等性

  • Remove any @NonNull or @Nullable annotations in the Kotlin code

    删除Kotlin代码中的所有@NonNull@Nullable批注

  • Check that single line comments are located in their desired places and not duplicated

    检查单行注释是否位于所需位置,并且没有重复
  • Add back removed blank lines within functions to match the style of the Java code

    在函数中添加回去的空白行以匹配Java代码的样式
  • Fix any incorrect casting attempts between number values

    修正数值之间的任何不正确的转换尝试
  • Improve code readability by renaming local variables that shadow names of function parameters

    通过重命名遮盖函数参数名称的局部变量来提高代码的可读性
  • Remove unnecessary private keywords on get() accessors on private properties

    删除私有属性上get()访问器上不必要的private关键字

结果 (Outcomes)

Right now, approximately half of the DeskClock app’s Java files have been converted to Kotlin. We’ve replaced 15,886 lines of Java code with an equivalent 15,240 lines of Kotlin code. The conversion of these Java files took roughly one month of engineering to complete. Through this Kotlin conversion process, as well as this article, the AOSP DeskClock app is better able to fulfill its goal of being an example for Android development best practices.

目前,DeskClock应用程序的Java文件中大约有一半已转换为Kotlin。 我们已经用等效的15,240行Kotlin代码替换了15,886行Java代码。 这些Java文件的转换大约花费了一个月的工程时间。 通过此Kotlin转换过程以及本文,AOSP DeskClock应用程序能够更好地实现其目标,成为Android开发最佳实践的典范。

After converting roughly half of the DeskClock app to Kotlin, we’re already seeing benefits in the number of lines of code to do equivalent work. The total number of lines of code has dropped from 15,886 to 15,240, representing a ~5% decrease. While this is not a tremendous decrease so far, it is expected that as more Java files are converted to Kotlin, some additional boilerplate code can be removed, as the Kotlin files have less interaction with Java files.

将DeskClock应用程序的大约一半转换为Kotlin之后,我们已经发现执行等效工作的代码行数有所增加。 代码的总行数从15,886减少至15,240,减少了5%。 尽管到目前为止这并不是一个巨大的减少,但是随着Kotlin文件与Java文件的交互作用减少,可以预期随着更多Java文件转换为Kotlin,可以删除一些附加的样板代码。

We also examined the differences in build times and APK size between the original Java app and the half-converted app. The APK size of the partially converted Kotlin app was 6237217 bytes, compared to 6117353 bytes for the original Java app’s APK size. This very slight size increase, approximately 2%, shows that the introduction of Kotlin to the app did not dramatically affect the APK size. When looking at the change in build times between these two versions of the app, the Java app had an average build time of ~33 seconds, whereas the partially-converted Kotlin app had an average build time of ~57 seconds. These build times were both recorded while performing clean builds with no previously compiled class files on a 48 core machine with 128 GB of RAM.

我们还检查了原始Java应用程序和半转换应用程序之间构建时间和APK大小的差异。 部分转换的Kotlin应用的APK大小为6237217字节,而原始Java应用的APK大小为6117353字节。 这种很小的增加(大约2%)表明,将Kotlin引入应用程序并没有显着影响APK的大小。 当查看这两个版本的应用程序之间的构建时间变化时,Java应用程序的平均构建时间为〜33秒,而部分转换的Kotlin应用程序的平均构建时间为〜57秒。 在具有128 GB RAM的48核计算机上执行以前没有编译过的类文件的干净构建时,都记录了这些构建时间。

In addition to the results found from the conversion of the DeskClock app, other developers have similarly seen benefits from converting to Kotlin. Duolingo completed a full Kotlin migration of their app and reduced their line count by 30%. As well, the Google Home app began incorporating Kotlin into their codebase, reducing their NullPointerExceptions by 33%. 70% of the top 1k apps include Kotlin code, as well, and 60% of professional Android developers use Kotlin.

除了从DeskClock应用程序转换中获得的结果之外,其他开发人员也同样看到了转换为Kotlin的好处。 Duolingo完成了他们应用程序的完整Kotlin迁移 ,并将行数减少了30%。 同样,Google Home应用程序也开始将Kotlin纳入其代码库, 从而将其NullPointerExceptions减少了33% 。 排名前1k的应用程序中有70%也包含Kotlin代码,而60%的专业Android开发人员也使用Kotlin。

下一步 (Next Steps)

To learn more about the Java to Kotlin conversion process for Android, check out the Get Started with Kotlin on Android documentation or the Kotlin for Java Developers Pathway. If you’d like to look at the codebase, you can check out the AOSP source code by following the Downloading the Source documentation.

要了解有关Android到Java到Kotlin转换过程的更多信息,请查看Android文档上的Kotlin 入门Java开发者之路Kotlin 。 如果您想查看代码库,可以按照“ 下载源代码”文档中的说明来检查AOSP源代码。

翻译自: https://medium.com/androiddevelopers/re-writing-the-aosp-deskclock-app-in-kotlin-76c836370cb

kotlin选择表达

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值