Introduction
R8 is the in-built application shrinker present in Google Android Studio. As advent goes on, R8 is now backed by Google for maintaining and rewriting it’s metadata so that it can be fully compatible with the shrinking of its libraries and the applications of Kotlin reflection.
The prime function of R8 is to reduce the size of Android applications by implementing pruning strategies. Pruning means to eliminate any branch of the code according to the laid constraints. R8 prunes the source code by eliminating the unused part of the code, such as functions that are never invoked, unused variables, classes with no objects. Apart from code elimination R8 also optimises the code that is retained.
On top of this, R8 is also programmed for shrinking Android libraries. This reduces the space complexity of the libraries and eventually our Android project. Library shrinking can also be used for abstraction. For instance, if any of the new features of your library is in the testing phase, or you are not ready to launch it publically, you can use Library shrinking for abstracting it.
The Android Gradle Plugin version 4.1.0-beta03 is the minimum requirement for implementing R8 libraries. It has been a boon for the Kotlin Developers, as they realised the need for metadata and the optimisation of networking strategies using them.
It is one of the most efficient programming languages for writing Android applications and libraries. Still, there are a lot of challenges while shrinking Kotlin libraries or applications that use Kotlin reflection. This is because to identify Kotlin language constructs, it uses metadata in Java class files. If your application shrinker is inefficient in maintaining and updating its metadata, your library or application will not compile successfully and will generate bug reports.
Kotlin Metadata
The supplementary information stored in annotations in Java class files which are produced by it’s JVM compiler is known as Kotlin metadata. The most important function of metadata is to specify which Kotlin language constructs a particular method or class present in the corresponding class file. For instance, the Kotlin compiler is allowed to recognise whether a method present in a class file is actually a Kotlin extension function with the help of Kotlin metadata only.
Go through the simple example depicted below with the help of a code snippet from GitHub. The depicted library code is used for defining a hypothetical base command builder for initiating compiler commands.
| package com.example.mylibrary | |
| /** CommandBuilderBase contains options common for D8 and R8. */ | |
| abstract class CommandBuilderBase { | |
| internal var minApi: Int = 0 | |
| internal var inputs: MutableList<String> = mutableListOf() | |
| abstract fun getCommandName(): String | |
| abstract fun getExtraArgs(): String | |
| fun build(): String { | |
| val inputArgs = inputs.joinToString(separator = ” “) | |
| return “${getCommandName()} –min-api=$minApi $inputArgs ${getExtraArgs()}” | |
| } | |
| } | |
| fun <T : CommandBuilderBase> T.setMinApi(api: Int): T { | |
| minApi = api | |
| return this | |
| } | |
| fun <T : CommandBuilderBase> T.addInput(input: String): T { | |
| inputs.add(input) | |
| return this | |
| } Code Courtesy : CommandBuilder.kt hosted by GitHub R8 allows us to define a hypothetical yet stable D8CommandBuilder on top of CommandBuilderBase so that the D8 command is simplified. package com.example.mylibrary /** D8CommandBuilder to build a D8 command. */ class D8CommandBuilder: CommandBuilderBase() { internal var intermediateOutput: Boolean = false override fun getCommandName() = “d8” override fun getExtraArgs() = “–intermediate=$intermediateOutput” } fun D8CommandBuilder.setIntermediateOutput(intermediate: Boolean) : D8CommandBuilder { intermediateOutput = intermediate return this } Code Courtesy : DBCommandBuilder.kt hosted by GitHub |
The above mentioned example depicts the use of extension functions for ensuring that in case you the setMinApi method is invoked by a D8CommandBuilder, the return type of the object will be altered to D8CommandBuilder and will not be CommandBuilderBase.
The extension function used in the above code snippet is placed on the file class CommandBuilderKt and they are on the top most layer of the hierarchy. Consider the following class file from the Git repository which uses javap output.
$ javap com/example/mylibrary/CommandBuilderKt.class
Compiled from “CommandBuilder.kt”
public final class CommandBuilderKt {
public static final T addInput(T, String);
public static final T setMinApi(T, int);
…
}
The extension functions used for compiling static methods that take an additional first parameter that is the extension receiver is represented by the javap output. Although, the set of information provides by the javap output is not sufficient for the Kotlin compiler to interfere whether these methods can be invoked from its code as extension functions or not. Hence, as a mandatory overhead, the Kotlin compiler appends the Metadata annotation in the Java class file. The additional Kotlin-specific information related to the class is present in this annotation. If you want to view the annotations, use the verbose flag along with javap.
You may notice a “d1” field in the metadata annotation; it contains the maximum real metadata in the form of a protocol buffer message. Although, the precise contents of the metadata is not considered to be too important. The most significant fact is that the Kotlin compiler employees this metadata for figuring out whether the methods are extension functions or not.




