A quick journey to Android build system

Shouman B. Shuvo

30 July, 2024

A quick journey

Inside the Android Build System

Do you think your code goes straight to phones? Think again! This guide will be your compass as you navigate the intricate Android build process. It’s crucial to understand what happens from the time we create an Android Project until its first launch.

When you create an Android Project

The Android build system compiles the app resources and source code and packages them as a single file that Android Runtime can execute.

Android uses Gradle to automate and manage the build process. When you are running or compiling your app, some gradle tasks are responsible for it. Gradle tasks and the Android Studio Gradle plugin perfectly sync to give you a smoother experience. When we create an Android App Project, we see the following structure:

└── MyApp/  # Project
  ├── gradle/
  │   └── wrapper/
  │       └── gradle-wrapper.properties
  ├── build.gradle(.kts)
  ├── settings.gradle(.kts)
  └── app/  # Module
      ├── build.gradle(.kts)
      └── build/
          ├── libs/
          └── src/
              └── main/  # Source set
                  ├── java/
                  │   └── blog.exa.myapp
                  │       └──ExaActivity.java # MainActivity in our case
                  ├── res/
                  │   ├── drawable/
                  │   ├── values/
                  │   └── …
                  └── AndroidManifest.xml

The build process involves some sequential gradle tasks. We may create an app without using the Android Studio as well, but that will be a lot of headaches 🤕

Hold tight to the seatbelt.

So, we, as developers, will compile this project. Our journey starts here. We will write some code inside our Activity file.

				
					public class ExaActivity {
   public static void main(String[] args) {
       System.out.println("Hello, I'm Shouman!");
   }
}
				
			

In the first step, the compiler will convert the code into bytecodes. To convert this code to bytecode, we may simply use:

				
					javac ExaActivity.java

// The result is ExaActivity.class
// that contains the bytecode
				
			

But this code won’t be running on the JVM. It will run in the ART or the legacy DVM ( OS < 5.0 ). 

When compiling an Android app, the process typically creates a dex file. Dex files are similar and not so identical simultaneously with Java Bytecode. We may have lots of .class files compiled, but for an Android App, we will only have a single dex file for them. However, we may need to enable multi-dex support for apps with over 64k methods. It will take another blog to discuss their differences, so we will not cover more here.

As Android Runtime needs its executable, we need to convert it. Android SDK provides Dexer Tool, which helps us convert:

				
					dx --dex --output=ExaActivity.dex ExaActivity.class
# classes.dex suits better, though
				
			

We may not have only library methods or objects in our source code. We may have other dependencies as well. The dependencies and resources will be compiled or deleted. Let’s compile and package this project for ART. We may run the `assembleDebug` gradle task to have a debug build or simply click build APK from the Build menu in the Android Studio, which will call gradlew assembleDebug under the hood (Simple 😎 flex).


I copied the image from GeeksForGeeks.org for visibility. 

Our app is currently a package that may look somewhat like this:

my_test_app.apk/
    ├── res/
    ├── AndroidManifest.xml
    ├── classes.dex

When you install an app, the Android System hands over the installation process to a system service called “Package Manager Service,” which will have the app in its queue for installation. The Package Manager communicates with a helper daemon (I like this name; I will name my daughter DAY-MOON) process named “installd.”

installd will create a directory for the app in the /data/app/<pkg>* location. The system will copy the app to the /data/app/<pkg>*/base.apk location. After that, It will read the manifest first and then create a user unique to that app unless we have the catch.

 

Source: This portion comes from my other blog (Your Android App is a user in the file system!)

It extracts the package, checks the manifest, determines the entry point (We specify in the manifest where to execute the app), creates a room for the app, and copies(!) the dex file. AOT compilation happens during this time.

AOT Compilation:

ART has AOT to improve app performance. During installation time, ART compiles apps using the on-device dex2oat tool. This process takes the .dex file as input and outputs the device’s executable app. ART stores the frequently used bytecode to machine code for improved performance. 

Previously, Android used Dalvik to execute the dex files, which is known as Dalvik Executable. Android Runtime, aka ART, is the enhanced version of DVM.

The dex file is either interpreted by the runtime or compiled into native code depending on CPU architecture (arm64, x86, etc.). ART may store optional precompiled files in the app’s private directory that happened during AOT.

Launching the app:

When you click over an app’s icon, your launcher sends a request to a service called ActivityManagerService, a part of the system service. The service is responsible for managing the app’s lifecycle. Then it kicks in the ART.

ART uses a package manager to get the app’s directory and locate the dex file. ART loads the dex file into memory. These dex bytecodes are to be interpreted by the ART. For most common paths, ART uses AOT, and for less likely paths, JIT can take place depending on the context.

Android may store precompiled dex files on the device with the .vdex extension. These files can directly run on the CPU, hence reducing CPU overhead. 

Voila! You’ve just leveled up to an Android master.

Food for thought:

Flutter apps do not necessarily compile into dex files. So, how does the Flutter app compile? I will write another blog on this topic.

Shouman B. Shuvo

30 July, 2024