State of the art: ART

RishiRishi
6 min read

Let us first understand the difference between C++ and Java when it comes to compiling the source code for different architectures:

C++ Compilation Process:

  • C++ source code is compiled directly to machine code (the lowest-level programming language that consists of binary instructions) specific to the target architecture using a C++ compiler like GCC or Clang.

  • To run the compiled C++ program on a different architecture, the source code needs to be recompiled using a compiler tailored for that specific architecture.

  • Each CPU architecture has its own instruction set architecture (ISA), which defines the machine code that the CPU can execute.

  • This means that for each unique architecture (x86, ARM, etc.), the C++ source code must be compiled separately, resulting in different executable binaries.

  • If a new architecture uses a different ISA, the compiled code (machine code) from the previous architecture will not be compatible, Hence to use any C/C++ based software we might need to recompile the whole codebase from scratch for every program to use them on new ISA.

Java Compilation Process:

  • Java source code is first compiled to Java bytecode (an intermediate representation of Java source code) using the javac compiler, regardless of the target architecture.

  • The generated bytecode is then executed by the Java Virtual Machine (JVM), which is responsible for translating the bytecode into machine code optimized for the specific architecture.

  • To enhance performance, the JVM employs JIT compilation, which converts frequently executed bytecode into native machine code during runtime.

  • The same bytecode can be executed on any platform that has a compatible JVM installed, without the need for recompilation for the whole code.

  • If a new architecture uses a different ISA, This time we don't need to recompile the whole codebase for all the apps to make them compatible, as A new single JVM is developed by someone, the bytecode can be fed to that JVM to make it executable for the new architecture.

Google adopted the Java flow as they found it more relevant. Here is the process of how Android applications are compiled and executed.

Here is an email, Tim Lindholm sent to Andy Robin:

After the source code is written,

  • The D8 (legacy DX) tool is used to convert these .class files into Dalvik Executable (DEX) files, which are optimized for the Android runtime environment.

  • Here D8 is essentially equivalent to javac.

  • DEX files have a more compact format that is suitable for mobile devices, allowing for efficient execution and reduced memory usage.

  • The DEX files are essentially equivalent to bytecode however a more optimized bytecode to run on low-end devices like phones.

  • The DEX files, along with other resources (like images, layouts, and manifest files), are packaged into an Android Package (APK) file.

  • The APK is the file format used for distributing and installing applications on Android devices.

  • When a user installs an APK, the DEX files are extracted from the APK and stored in the device’s internal storage.

  • When the application is launched, the Android Runtime (ART) takes over control.

  • ART (analogous to JVM) converts the DEX files into native machine code using Ahead-of-Time (AOT) compilation analogous to JIT in Java flow however AOT ensures to load of the entire program ahead of time, resulting in faster startup times since there is no compilation delay when the application is launched.

  • Once the DEX files are converted to machine code, the application can be executed directly by the device’s CPU, providing improved performance and responsiveness.

Here is a table examining how DEX files are optimized for Android compared to bytecode.

Dex FilesJava Bytecode
Single .dex file can contain multiple classes, reducing overhead.Separate .class files for each class, increasing file count.
Supports string deduplication, storing identical strings only once, minimizing size.No inherent deduplication; each class can have duplicate strings.
Compiled using D8, which includes optimizations for Android.Compiled using javac, without mobile-specific optimizations.
Faster startup and execution due to AOT compilation and optimized bytecode.May experience slower startup due to JIT compilation overhead.
Errors can be caught during installation due to AOT compilation.Errors are often detected at runtime, which can lead to issues in production.

The 64K (65,536) method size limit in DEX files is a restriction imposed by the Dalvik bytecode format used in older versions of Android. It refers to the maximum number of methods that can be referenced within a single DEX file.

These methods in this context include the Android framework, third-party libraries, and the app's own code. Exceeding this limit results in build errors like "cannot fit requested classes in a single dex file" or "Unable to execute dex: method ID not in [0, 0xffff]: 65536."

The limit was manageable in early Android versions but became problematic as apps grew more complex and used more libraries. Popular libraries like Google Play Services and Guava, Firebase can contribute thousands of methods each.

To work around the 64K limit, Android introduced MultiDex support. This allows apps to be split into multiple DEX files. MultiDex is supported natively in Android 5.0 (API level 21) and higher. Using MultiDex has some limitations, like potential performance overhead and startup delays, especially on older Android versions.

To mitigate these issues, Developers use code shrinking tools (like ProGuard) which can perform various optimizations on the bytecode, such as inlining, removal of dead code branches, and simplification of instructions. These optimizations can help reduce the number of methods required to implement certain functionalities.

Let's implement multiDex in a flutter project to integrate Firebase:

Ensure your minSdkVersion is set to 21 or higher in your android/app/build.gradle file.

#groovy
android {
    defaultConfig {
        minSdkVersion 21 // or higher
    }
}

Add the following to android/app/build.grade file:

android {
    defaultConfig {
        ...
        multiDexEnabled true
    }
}

dependencies {
    implementation 'com.android.support:multidex:1.0.3' 
    // Add this line
}

And that's it.

Thank you for taking the time to read through this blog post on the differences between C++ and Java compilation processes, ART, and Dex files. I hope you found the insights valuable and informative, especially regarding how these languages handle code compilation and execution across different architectures. Understanding these distinctions is crucial for developers as it can influence their choice of programming language based on project requirements and platform compatibility. I appreciate your engagement and encourage you to share your thoughts or questions in the comments below. Happy coding!

If you found this guide helpful, You can connect me on the following platforms:

15
Subscribe to my newsletter

Read articles from Rishi directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Rishi
Rishi