Original address: medium.com/androiddeve…

Author: Medium.com/@damian.pat…

Published: August 4, 2021-8 minutes reading

By Damian Patel and Mahi K

Two years ago, the Android Open Source Project (AOSP) application team began the process of refactoring AOSP applications from Java to Kotlin. There are two reasons to take this step: to ensure that AOSP applications follow Android best practices, and to provide a good example of developing applications with Kotlin in the first place. Moreover, one of Kotlin’s biggest attractions is its concise syntax. In many cases, most blocks of code written in Kotlin are smaller than the corresponding blocks of functionally identical Java code. In addition, this expressive programming language has a variety of other useful features, such as.

  • Null is safe. This concept has been embedded in Kotlin to help avoid destructive null-pointer exceptions.
  • Concurrency. Structured concurrency, as described by Android in Google I/O 2019, allows common applications to simplify background task management.
  • Java compatibility. Especially in the context of this project, Kotlin’s compatibility with the Java programming language allowed us to go through the conversion process one file at a time.

Last summer, the AOSP team published an article detailing the transformation process for the AOSP DeskClock application. This summer, we converted our AOSP calendar application from Java to Kotlin. Before the conversion, the application had over 18,000 lines of code. After the conversion, we were able to reduce our code base by about 300 lines of code. Following a similar technique to the AOSP DeskClock transformation, we took advantage of Kotlin’s interoperability with the Java programming language to convert the Java files one by one, replacing the Java files with Kotlin’s corresponding files using a separate build target during the transformation. Since we have two people working on this, we also created an exclude_srcs property for each of us in the android.bp file, so that both of us can transform and push changes while reducing merge conflicts. In addition, this allows us to step by step test and determine which files are causing errors.

To convert any given file, we started using the automatic Java-to-Kotlin conversion tool in Android Studio’s Kotlin plugin. While the plug-in succeeded in converting most of the code, there were a few issues that developers might encounter that had to be addressed manually. The manual modifications we must make are described in the following sections.

After converting each file to Kotlin, we manually tested the user interface of the calendar application, ran the unit tests, and ran a subset of the compatibility test Suite (CTS) tests to verify functionality and ensure no regression.

Steps taken after automatic conversion

As mentioned earlier, after running the automatic conversion tool, there are several recurring issues that must be resolved manually. The AOSP DeskClock article details some of these errors and the necessary fixes. Here are a few more problems we encountered in switching AOSP calendars.

Open superclass

One of the problems we encountered was the interaction between the parent and child classes in Kotlin. In Kotlin, to mark a class as inheritable, you must add the open keyword to the class declaration. The same is true for any method in a parent class that is overridden by a child class. In Java inheritance, the open keyword is not required. Due to Kotlin and Java interoperability, this problem does not arise until most of the files are fully converted to Kotlin. For example, the code snippet below shows a declaration class that inherits from SimpleWeeksAdapter.

class MonthByWeekAdapter(context: Context? , params: HashMap<String? .Int? >) : SimpleWeeksAdapter(contextas Context, params) {// body}
Copy the code

Because the conversion is done one file at a time, this leads to an error, because even a fully converted SimpleWeeksAdapter.kt file will not have the open keyword in its class declaration. This needs to be added manually so that the SimpleWeeksAdapter can be inherited. This particular class declaration looks like this.

open class SimpleWeeksAdapter(context: Context, params: HashMap<String? .Int?>?) {// body}
Copy the code

Override modifier

Similarly, methods in a child class that override methods in a parent class must be marked with an override modifier. In Java, this is done with the @Override annotation. However, despite seeing the corresponding Java annotations, the automatic converter automatically adds an override modifier to Kotlin’s method declaration. The solution is to manually add the override modifier to all the appropriate method declarations.

Covering properties

In Kotlin, we encountered another unusual problem with overwriting properties. When a subclass declares a variable with the same name as a non-private variable defined in the parent class, we need to add an override modifier. However, even if a subclass’s variable is of a different type than the parent class, it still seems that modifiers are needed. In some cases, adding an override modifier won’t solve the problem, especially if the subclass is of a completely different type. In fact, if there is a type mismatch, adding an override modifier to a variable in a subclass and an open modifier to a variable in a superclass will result in another error with the content of. The type of the property name * does not match the type of the overridden var-property

Type of *property name* doesn't match the type of the overridden var-propertyCopy the code

This can cause confusion, because in Java, the following code compiles without any problems.

public class Parent {
    int num = 0;
}

class Child extends Parent {
    String num = "num";
}
Copy the code

But in Kotlin, the corresponding code below causes the above error.

class Parent {
    var num: Int = 0
}

class Child : Parent() {
    var num: String = "num"
}
Copy the code

This is an interesting question, and so far we have renamed variables in subclasses to avoid such collisions. The Java snippet above is converted to troublesome Kotlin code with Android Studio’s current converter and is even reported as an error.

Import statements

In each of the files we converted, the automatic converter tool tended to truncate the entire list of import statements in the Java file to the first line in the corresponding Kotlin file. Initially, this led to some frustrating errors, with the compiler complaining about “unknown references” throughout the code. After realizing the error, we started manually copying the import statements from the Java file into the Kotlin file and converting the segment separately.

Exposure field

By default, Kotlin automatically generates getters and setters for instance variables in the class. However, there are times when we want a variable to be a simple Java field. This can be done using the @jVMField annotation.

The @jvmField annotation “instructs the Kotlin compiler not to generate getters/setters for this property and to expose it as a field”. This annotation is particularly useful in the CalendarData class, which contains two static final variables. By using @jVMField annotations on read-only val variables, we ensure that these variables can be accessed by other classes as fields, thus achieving compatibility between Java and Kotlin classes.

Object

Functions defined in Kotlin objects must be marked @jVMStatic so that they can be called in Java code by method name rather than by instantiation. In other words, this annotation performs the Java-like behavior of calling methods by class name. According to the Kotlin documentation, “The compiler generates a static method in the enclosing class of an object and an instance method in the object itself.” We encountered this problem in the Utils file. Once converted, the Java class becomes a Kotlin object. Then, all methods defined in the object must be marked @jvmstatic so that they can be called from other files using the utils.method () syntax. It is worth mentioning that it is used between class names and method names. INSTANCE (utils.instance.method ()) is also an option; However, it violates normal Java syntax and requires all Java static method calls to be changed.

Performance evaluation and analysis

All benchmark tests were performed on a machine with 96 kernels and 176GB of memory. The main metrics analyzed in this project are lines of code abandoned, target APK size, build time, and initial startup screen display time. Along with the analysis of each of these factors, we also list the data collected for each parameter.

Reduced lines of code

After a full conversion from Java to Kotlin, the number of lines of code is reduced from 18,004 to 17,729. This is about 1.5 percent less than the original Java code. While the amount of code reduced is not huge, for a large application like the one mentioned in this article, this conversion may reduce the number of lines of code by much more.

Target APK size

The APK size for the Kotlin application is 2.7MB, while the APK size for the Java application is 2.6MB. This size difference can be said to be negligible, and a marginal increase in size is actually expected due to the inclusion of some small additional Kotlin libraries. This increase in size can be mitigated by using Proguard or R8.

Build time

The build time for Kotlin and Java applications was calculated by taking the average time of 10 clean build trials (excluding outliers), with the average build time for Kotlin applications being 13 minutes and 27 seconds, compared to 12 minutes and 6 seconds for Java applications. Some materials, such as The Difference between Java and Kotlin and the Difference between Kotlin and Java. As discussed in compilation speed, especially for clean builds, Kotlin compile times actually lag behind Java compile times. Some analyses assert that Java compiles at a rate of about 10-15%, while others say the range is 15-20%. Based on clean build times, Java compiled 11.2% faster than Kotlin, although this slight difference, outside the scope of the above, may be due to the fact that the AOSP Calendar is a relatively small application, consisting of only 43 classes. Despite the slow clean build time, Kotlin has advantages that should be considered. For example, as mentioned earlier, using Kotlin generally guarantees less code because its syntax is more concise than Java’s. This makes it easier to maintain the Kotlin code base. Furthermore, as an implied safer and more productive language, it’s fair to argue that slower clean build times are negligible.

Initial display time

Using this method to test the time it took the application to fully display the initial startup screen, we found that the average time for Kotlin applications was about 197.7ms after 10 trials, while the average time for Java applications was about 194.9ms. These separate trials were all conducted on the Pixel 3A XL device. It can be concluded from this test that Java applications may have a slight advantage over Kotlin applications; However, since the average times are very similar, the time differences may be negligible. Therefore, it can be said that the Kotlin transformation of the AOSP calendar did not negatively affect the initial startup time of the application.

conclusion

Converting the AOSP calendar app to Kotlin took about 1.5 months (6 weeks), with 2 interns working on the project. Once we become familiar with the code base and get better at solving recurring compile-time, run-time, and syntax problems, efficiency will definitely improve. Overall, this particular project successfully demonstrates how Kotlin can impact existing Android apps and is proving to be a great next step in the transition to other AOSP apps.


www.deepl.com translation