Two years ago, the Android Open Source Project (AOSP) app team began to refactor AOSP apps to use Kotlin instead of Java. There were two main reasons for the refactoring: one was to ensure that AOSP apps followed Android best practices, and the other was to provide a good example of using Kotlin first for app development. One of the reasons why Kotlin is so attractive is its concise syntax. In many cases, the amount of code written in Kotlin is less than the amount of code in Java for the same function. In addition, Kotlin, an expressive programming language, has various other advantages, such as:
Last summer, the AOSP team published a post detailing the conversion of the AOSP Desktop Clock app. This year, we converted the AOSP Calendar app from Java to Kotlin. Prior to the conversion, the app had over 18,000 lines of code, and after the conversion the code base was reduced by about 300 lines. For this conversion, we followed similar techniques as the AOSP Desktop Clock conversion process, taking advantage of the interoperability of the Kotlin and Java languages, converting the code files one by one, and using independent build targets to replace Java code files with corresponding Kotlin code files in the process. Because there were two people on the team doing this work, we created an exclude_srcs property for each person in the Android.bp file so that both people could refactor and push code at the same time while reducing code merge conflicts. In addition, this allows us to perform incremental testing and quickly locate which files the errors appear in. When converting any given file, we first use the automatic conversion tool from Java to Kotlin provided by the Android Studio Kotlin plugin. Although the plugin successfully helps us convert most of the code, there are still some problems that need to be manually solved by the developer. The parts that need to be manually changed will be listed in the following sections of this article. After converting each file to Kotlin, we manually tested the calendar app UI, ran unit tests, and ran a subset of the Compatibility Test Suite (CTS) for functionality verification to ensure that no regression tests were required. Steps after automatic conversionAs mentioned above, there are some recurring issues that need to be manually located and resolved after using the automatic conversion tool. In the AOSP Desktop Clock article, some of the issues encountered and their solutions are described in detail. The following is a list of some of the issues encountered during the AOSP calendar conversion process. Mark the parent class with the open keywordOne of the issues we encountered was calling Kotlin parent and child classes. In Kotlin, to mark a class as inheritable, you must add the open keyword to the class declaration, and do the same for methods in the parent class that are overridden by the child class. However, inheritance in Java does not require the use of the open keyword. Since Kotlin and Java can call each other, this problem did not appear until most of the code files were converted to Kotlin. For example, in the following code snippet, a class that inherits from SimpleWeeksAdapter is declared:
Since the code file conversion process is done one file at a time, even if you completely convert the SimpleWeeksAdapter.kt file to Kotlin, the open keyword will not appear in its class declaration, which will cause an error. Therefore, you need to manually add the open keyword so that the SimpleWeeksAdapter class can be inherited. This special class declaration is as follows:
The override modifierLikewise, methods in a subclass that override a superclass must also be marked with the override modifier. In Java, this is done with the @Override annotation. However, while there is a corresponding annotation implementation in Java, the automatic conversion process does not add the override modifier to Kotlin method declarations. The solution is to manually add the override modifier in all appropriate places. Override properties in the parent classDuring the refactoring process, we also encountered an unusual problem of property overriding. When a subclass declares a variable and there is a non-private variable with the same name in the parent class, we need to add an override modifier. However, even if the subclass variable is of a different type than the parent class variable, the override modifier must still be added. In some cases, adding override still does not solve the problem, especially when the subclass type is completely different. In fact, if the types do not match, adding the override modifier before the subclass variable and adding the open keyword before the parent class variable will result in an error:
This error is confusing because in Java, the following code compiles fine:
The corresponding code in Kotlin will report the error mentioned above:
This is an interesting question, and we currently circumvent this conflict by renaming the variables in the subclass. The Java code above will be converted to problematic Kotlin code by the code converter currently provided by Android Studio, which has even been reported as a bug. Import StatementIn all the files we converted, the automatic conversion tool tended to truncate all import statements in the Java code to the first line of the Kotlin file. This caused some very frustrating errors at first, with the compiler complaining about "unknown references" throughout the code. After realizing this problem, we started manually pasting import statements from Java into the Kotlin code files and converting them individually. Exposing member variablesBy default, Kotlin automatically generates getter and setter methods for instance variables in a class. However, sometimes we want a variable to be just a simple Java member variable, which can be achieved by using the @JvmField annotation. The @JvmField annotation is used to "instruct the Kotlin compiler not to generate getter and setter methods for this property, and to allow it to be publicly accessible as a member variable." This annotation is particularly useful in the CalendarData class, which contains two static final variables. By using the @JvmField annotation on read-only variables declared with val, we ensure that these variables can be accessed as member variables by other classes, thus achieving compatibility between Java and Kotlin. Static methods in objectsFunctions defined in Kotlin objects must be marked with @JvmStatic to allow them to be called from Java code by method name instead of instantiation. In other words, this annotation makes them behave like Java methods, i.e., they can be called by class name. According to the Kotlin documentation, "The compiler generates a static method for the outer class of the object and an instance method for the object itself." We encountered this problem in the Utils file, and when the conversion is completed, the Java class becomes a Kotlin object. Subsequently, all methods defined in the object must be marked with @JvmStatic, which allows them to be called from other files using syntax such as Utils.method(). It is worth mentioning that using .INSTANCE between the class name and the method name (i.e. Utils.INSTANCE.method()) is also an option, but this does not conform to the common Java syntax and requires changing all calls to Java static methods. Performance evaluation analysisAll benchmarks were performed on a 96-core machine with 176 GiB of RAM. The main metrics analyzed in this project were the number of lines of code reduced, the file size of the target APK, the build time, and the time from launch to first screen display. While analyzing each of the above factors, we also collected data for each parameter and presented it in a table. Reduced lines of codeAfter fully converting from Java to Kotlin, the number of lines of code was reduced from 18,004 to 17,729. This is about 1.5% less than the original Java code. Although the reduction in code volume is not significant, for some large applications, this conversion may have a more significant effect on reducing the number of lines of code, as shown in the example given in the AOSP desktop clock article. Target APK sizeThe APK size of an app written in Kotlin is 2.7 MB, while the APK size of an app written in Java is 2.6 MB. This difference is negligible, and the increase in APK size is actually expected due to the inclusion of some additional Kotlin libraries. This size increase can be optimized by using Proguard or R8. Compile timeKotlin and Java app build times are calculated by taking the average of 10 full builds from scratch (excluding outliers), with an average build time of 13 minutes and 27 seconds for Kotlin apps and 12 minutes and 6 seconds for Java apps. According to some sources (such as "Differences between Java and Kotlin" and "Compile times for Kotlin and Java"), Kotlin compile times are actually longer than Java, especially for builds from scratch. Some analyses assert that Java compiles 10-15% faster, while others claim 15-20%. For our example full build from scratch, Java compiles 11.2% faster than Kotlin, although this small difference is not in the above range, which may be because AOSP Calendar is a relatively small application with only 43 classes. Although full builds from scratch are slower, Kotlin still has advantages in other aspects that should be taken into account. For example, Kotlin's more concise syntax generally results in less code than Java, which makes Kotlin codebases easier to maintain. In addition, since Kotlin is a safer and more efficient programming language, we can assume that the slower full build time is negligible. First screen display timeWe used this method to test the time it takes for an app to fully display the first screen from startup, and after 10 trials, we found that the average time for Kotlin apps was about 197.7 milliseconds, while that for Java was 194.9 milliseconds. These tests were all performed on Pixel 3a XL devices. From this test result, it can be concluded that Java apps may have a slight advantage over Kotlin apps; however, since the average times are very close, the difference is almost negligible. Therefore, it can be said that the conversion of the AOSP Calendar app to Kotlin did not negatively affect the initial startup time of the app. in conclusionConverting the AOSP Calendar app to Kotlin took about 1.5 months (6 weeks) with 2 interns working on the project. It will definitely get more efficient once we get more familiar with the code base and get better at solving recurring compile-time, runtime, and syntax issues. Overall, this particular project successfully demonstrated how Kotlin can impact existing Android apps and was a solid step forward in converting AOSP apps. |
<<: A senior designer from Tencent tells his story: How did I grow in the past 8 years at Tencent?
>>: WeChat launches "One ID Check": quickly check how many phone numbers you have
We all know that the first step in doing informat...
14 lessons on how to monetize your listening manu...
Today is Frost Descent, the eighteenth of the twe...
The new theory can not only give the results that...
This article is an authoritative in-depth market ...
July 28 news, according to foreign media reports,...
Episode 1: Three major trends talk about entry-tr...
Although the new coronavirus is rampant, don’t pa...
Recently, Chinese researchers discovered sessile ...
In the movie "The Angry Sea" released n...
In the era of Industry 4.0, from the perspective ...
After decades of efforts, China's space indus...
Retail: In July 2022, the retail sales of passeng...
The mini program provides convenience for publici...
Before a product design is implemented, the produ...