June 24th, 2019

Faster Xamarin.Android Builds & Smaller Dex Files

Jonathan Peppers
Principal Software Engineer

One of our current focus areas in Xamarin.Android is build performance. The “inner dev loop” directly impacts developer productivity–the time it takes to make a small code change and see the result on a device or emulator. Reevaluating parts of Xamarin.Android’s codebase has been how we’ve been able to make progress.

Dex Limits

We have also noticed that Xamarin.Android developers hit the multidex limit often. If an application is mostly .NET code, the amount of Java code should be under the multidex limit. We had a suspicion there was more to the problem.

A Story About Aapt

Xamarin.Android runs several command-line tools to create a native Android application. One such tool is aapt (Android Asset Packaging Tool), which is used to process Android resource files and create Android APK files. aapt also generates the identifiers that Android developers are familiar with, such as the Resource class in C# or the R class in Java.

aapt (or aapt2) runs a few times during a typical Xamarin.Android build:

      1. Generate an R.java file for each Java library.
      2. Create a Resource.designer.cs file, so we can access the resources from C#.
      3. Produce an APK file that can be deployed to an emulator or device.

As you might imagine, aapt takes a bit of time to run, so it became an area of investigation.

Hello World

We began looking at how Xamarin.Android invokes aapt in the case of Java libraries. If we look at the example of a “Hello World” Xamarin.Forms app, the aapt command-line looks something like this:

aapt package 
    -f -m 
    --non-constant-id 
    --auto-add-overlay 
    --max-res-version 28 
    -M obj\Debug\lp\1\jl\manifest\AndroidManifest.xml 
    -J obj\Debug\android\src 
    -S obj\Debug\res 
    -S obj\Debug\lp\1\jl\res 
    -S obj\Debug\lp\2\jl\res 
    -S obj\Debug\lp\3\jl\res 
    -S obj\Debug\lp\4\jl\res 
    -S obj\Debug\lp\5\jl\res 
    -S obj\Debug\lp\6\jl\res 
    -S obj\Debug\lp\7\jl\res 
    -S obj\Debug\lp\8\jl\res 
    -I "C:\Program Files (x86)\Android\android-sdk\platforms\android-28\android.jar"

NOTE: we have numbers & small directory names to workaround the MAX_PATH limit on Windows.

Android Support Libraries

This command produces an R.java file for the first Java library used by the app, placing it in obj\Debug\android\src in a Java directory structure. Xamarin.Android runs eight such commands in parallel: one per library with a different -M switch. The Java libraries in this particular project are the Android support libraries.

We end up with eight files on disk, for example:

      • obj\Debug\android\src\android\support\library1\R.java
      • obj\Debug\android\src\android\support\library2\R.java
      • obj\Debug\android\src\android\support\library3\R.java
      • obj\Debug\android\src\android\support\library4\R.java
      • obj\Debug\android\src\android\support\library5\R.java
      • obj\Debug\android\src\android\support\library6\R.java
      • obj\Debug\android\src\android\support\library7\R.java
      • obj\Debug\android\src\android\support\library8\R.java

The theory (or question) was if we could avoid running aapt so many times to improve build times? But something else didn’t seem quite right… When comparing the R.java files from a Xamarin project to a similar Java project in Android Studio, the R.java files had quite a few more fields!

Specifically, we compared the android\support\compat\R$drawable class:

      • Android Studio/Java: 11 fields
      • Xamarin.Forms: 300+ fields

Comparing the APK as a whole, the Xamarin app had thousands more fields than the Java app!

Thinking about the aapt command, the command produced an R.java file as if each library referenced every other library. We generated multiple R.java files containing every field in the application.

The Java libraries, of course, do not need all of the fields, some of the Java libraries depend on one another. How can we figure out which ones?

Looking at Android Studio

Many parts of Android are open source, so we looked at Google’s implementation. Android Studio uses a file named R.txt, an easy-to-parse, text-based, version of R.java to declare identifiers needed by each Java library. Xamarin.Android already had access to this file in most cases, but our MSBuild targets were not using R.txt yet.

At a high level, Android Studio does the following:

      • aapt is invoked to generate an R.txt file for the entire Android app.
      • An R.txt file that is shipped alongside each library makes it possible to map the values back to the app’s R.txt file. The library’s R.txt only lists Android resources that it uses.
      • Smaller R.java files are generated using the integer values from the app’s R.txt.

The process was not too complex, so we could implement this in C# for Xamarin.Android. We already had C# code that could parse R.txt files, so the only thing new would be something that could generate R.java files.

The Results

We saw around 12,000 less fields just from this change:

The APK file was also around 120KB smaller.

We also saw an impact to build times. Xamarin.Android builds no longer ran aapt N times, and we were able to implement the R.txt parser and R.java writer in an efficient manner in C#. Using MSBuild’s /clp:performancesummary option, we could see the duration for these MSBuild targets:

Before:
3173 ms  _GenerateJavaDesignerForComponent          1 calls

After:
  20 ms  GenerateLibraryResources                   1 calls

Saving three seconds off of “Hello World” is great! We think these improvements could have an even bigger impact on larger Xamarin.Android projects.

Conclusion

If your Xamarin.Android app currently requires multidex, due to an error such as:

trouble writing output: 
    Too many field references to fit in one dex file: 70468; max is 65536.

In future versions of Xamarin.Android, an app hitting the “too many field references” error might not require multidex anymore.

This enhancement are available in the latest Visual Studio 2019 version 16.2 Preview and Visual Studio for Mac 2019 version 8.2 Preview.

Further reading:

Author

Jonathan Peppers
Principal Software Engineer

I used to build apps in C#. Now build *tools* to build apps in C#. Engineer on the .NET Android / .NET MAUI team.

5 comments

Discussion is closed. Login to edit/delete existing comments.

  • Michal Wesolowski

    Great news! When it will land in Stable channel?

  • Mark Mark Laureta

    Great! Does this improvements have any impact on runtime performance of a XF Android app?

    • adrien.padol@orange.com

      I don’t think Multi-dex has an impact on runtime performance. So no I don’t think these modifications changes anything regarding runtime performances

      • Jonathan PeppersMicrosoft employee Author

        Most of the impact here is build time. Your build will certainly be faster if you don’t have to enable multi-dex. The Android tooling does a bit of logic to figure out which types must be in the main dex file, and so it can skip that work completely.