Deep dive into Android build process
If you are not familiar with terms like Android Runtime, Dalvik, DEX, JIT and AOT please read my article before going ahead.
Android build process entails several tools and steps that convert a project into an APK (Android Application Package) or an AAB (Android App Bundle). Let’s dive into these steps.
Step 1: Resource compilation
Every Android developer has written the following line of code at some point or the other:
//Java
Button b = findViewById(R.id.sample_button);//Kotlin
var b : Button = findViewById(R.id.sample_button);
Ever wondered where this “R” class comes from? R.java is the love child of Android Asset Packaging Tool (AAPT2) and your project’s res/ directory. This tool takes the AndroidManifest.xml and all the resources under res/directory and parses, indexes, and compiles them to create the R class which has all the resource ids. I will not be going in depths of AAPT2 as it is out of scope for this topic. You can read more about it here.
Step 2: Source code compilation
In this step, all your Kotlin and Java files (including the R.java file generated in step 1 and the code from the app’s dependencies and libraries) are compiled into .class files by kotlinc and javac compilers respectively. Your project’s aidl files are converted to Java interfaces and then subsequently compiled to .class files.
But this bytecode is not any use in the .class form. Android apps do not run on JVM. They run on Android Runtime (ART) and pre Lollipop on Dalvik Virtual Machine (DVM). These environments do not understand .class files. They only understand DEX files (Dalvik EXecutable).
Step 3: Shrinking, obfuscation and optimization
This step is optional. Build process will go through this step only if you have enabled shrinking in your release build like this:
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile(
'proguard-android-optimize.txt'),
'proguard-rules.pro'
}
}
...
}
This step involves the following sub steps:
- Code shrinking — also known as tree-shaking, detects and safely removes unused classes, fields, methods, and attributes from your app and its library dependencies.
- Resource shrinking — removes unused resources including the ones in the app’s library dependencies.
- Obfuscation — replaces the original names of classes and members with short meaningless names. This results in reduced DEX file sizes.
- Optimization — inspects and removes unnecessary code. For instance, if your else{} block cannot be reached in any case, it will be removed in this step.
Two main tools to execute this step are R8 and ProGuard. When you build your project using Android Gradle plugin 3.4.0 or higher, the plugin no longer uses ProGuard to perform compile-time code optimization. Instead, it uses the R8 compiler. You can choose to modify the default behavior of R8 using the ProGuard rules files. R8 offers the following advantages when compared to ProGuard:
- Proguard reduces the app size by 8.5% whereas R8 reduces it by 10%.
- R8 offeres better, more extensive Kotlin support.
- In case of ProGuard, it first converted java bytecode to optimized java bytecode, which was subsequently converted to DEX files by a dexer. But R8 directly converts java bytecode to optimized DEX code.
Step 4: Dalvik bytecode (DEX files) generation
This step is executed within step 3 IF step 3 is executed.
At this juncture, our .class files are converted into .dex files or Dalvik EXecutable files. This job is done by D8 tool or the R8 tool if you have enabled code optimization as discussed in step 3.
D8 is a dexer that converts .class files to .dex files that can be understood by ART. It also enables you to use Java 8 features in your code through a compile process called desugaring.
In general, desugaring refers to the process of providing backward compatibility for new Java libraries.
ART does not support Java 8 language features by default. Desugaring essentially converts Java 8 bytecode to Java 7 bytecode. D8 not only converts our application’s .class files to .dex files, but also adds dex code for the newer java libraries and then bundles them together in the APK.
Step 5: Packaging
The packager combines the DEX files from step 3/step4 and the compiled resources from step 1 into an APK or an AAB. Before generating your final APK, the packager uses the zipalign tool to optimize your app to use less memory when running on a device. zipalign is a zip archive alignment tool. It ensures that all uncompressed files in the archive are aligned relative to the start of the file. This allows those files to be accessed directly, removing the need to copy this data in RAM and reducing your app's memory usage.
Before your app can be installed, this APK/AAB must be signed.
- If it is a debug version of your app, packager signs your app with the debug keystore, and you don’t need to do anything.
- If it is a release version of your app, the packager signs your app with the release keystore that you need to configure. For more details about creating a release keystore, read about signing your app in Android Studio.
That’s all folks!
If Android tickles your fancy, stick around as tons of interesting stuff coming up. If you liked this article, do follow me as I will keep posting tech oriented articles like this one.
Peace!