D8 replaced DX as the default dex compiler in Android Studio (and Android Gradle Plugin) 3.1. It runs faster than DX and produces smaller .dex files, but it’s also capable of some other nice stuff. In this post we’ll look at one of these: desugaring. We’ll explain what that is and what benefits it can bring us.
Desugaring is the removal of syntactic sugar from code. Syntactic sugar is parts of any programming language which is designed specifically to make code more human-friendly. A simple example of this is for accessing arrays. In languages such as Java & Kotlin there are specific classes which represent arrays and these have a getter to retrieve an item from the array which takes a single argument which is the index of the array item that is to be returned:
array.get(1). As well as this there is also an alternate way of doing the same thing using the indexing operator:
array. These two code snippets are functionally identical, yet the second is more terse so requires slightly less cognitive load to read and understand (because it is an established pattern for accessing an array item), and is also quicker to type. The compiler will typically compile these to identical bytecode even though the source code is different. If
array is actually of type
java.util.List then the bytecode for both code snippets will invoke the
get(index) method. So the terser syntax (
array) is syntactic sugar in this case because it is actually transformed to the getter call during compilation to bytecode. This transformation during compilation is the desugaring.
Java 8 support was added to the build toolchain in Android Gradle Plugin 3.0. While Java 8 language features such as lambdas, method references, and type annotations can be achieved using desugaring of the code, some of the Java libraries introduced in Java 8 are are only available from Android 7.0 (Nougat – API 24) onwards and few of us are fortunate enough to be
minSdkVersion = 24. However, from Android Gradle Plugin 4.0 onwards the D8 desugaring has been extended to support some of these libraries as well including
java.util.function, and a subset of
java.time.*. This desugaring of java libraries is referred to as core library desugaring.
This last one is particularly interesting partly because it was not added to Android until API 26 but also because of a previous series of blog posts where we used Jake Wharton’s ThreeTenABP an Android back port of JSR-310 which is what because the Java libraries that we’re interested in. Jake is winding down development of ThreeTenABP and recommends using D8 core library desugaring instead. So we’ll take the example code from that series of articles and port it to use core library desugaring instead of ThreeTenABP to see how easy it is.
The first thing I’ve done is update the dependencies and build tools to the latest versions, and switched to View Binding over Kotlin synthetic imports. The next thing we can do is completely remove the
TimeApplication class which was our
Application instance for which the sole purpose was to initialise ThreeTenABP.
The next thing we need to do is change all of our imports to use the package
java.time instead of
org.threeten.bp. On the face of it this is all we need to do, and this initially appears to work perfectly. In fact it does work perfectly for devices running API 26 and later, however if we try it on devices running earlier versions of Android we’ll get
ClassNotFoundExceptions for the
java.time classes. Moreover we’ll also get lint warnings (and warnings in the Android Studio editor window) along the lines of:
Call requires API level 26 (current min is 21): java.time.Duration#toMillis
This is because core library desugaring is disabled by default. But enabling it is pretty straightforward:
We need to enable core library desugaring by setting a boolean flag under
compileOptions – it is also important to specify source and target compatibility of Java 8. We then need to specify a
coreLibraryDesugaring dependency. This is a library containing pre-compiled version of the Java 8 core libraries which will get included in our APK. While this may seem like it is going to bloat our APK somewhat, it is worth bearing in mind that minify (which should be done on release builds) will strip out any unused code including that from third-party libraries, so will strip out any core Java libraries that are unused. Also, we have removed the dependency on ThreeTenABP so the net change after minification will be minimal. The APK size of the sample app after these changes is 1,871,035 bytes compared to 1,818,380 bytes for the ThreeTenABP version (using the same version of other dependencies, and the same build tools. So using core library desugaring only results in around 53 kilobytes increase in this case, although this will vary from app to app depending on how much or how little of the API surface is being touched, and therefore what minify will leave in the APK.
With those additions to our
build.gradle file desugaring is enabled and we now get backwards compatibility of the
java.time libraries. The sample code works perfectly with just these few small changes.
So for now this is only available to those using Android Studio 4.0 (which at the time of writing is at Beta 3 version), but it should not be too longe before this is in the stable channel and will be useable by all.
The updated source for the previous project code is available here.
© 2020, Mark Allison. All rights reserved.
Copyright © 2020 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.