Faster Xamarin.Android Builds & Smaller Dex Files
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.
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:
- Generate an
R.javafile for each Java library.
- Create a
Resource.designer.csfile, so we can access the resources from C#.
- Produce an APK file that can be deployed to an emulator or device.
- Generate an
As you might imagine,
aapt takes a bit of time to run, so it became an area of investigation.
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:
-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:
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 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
At a high level, Android Studio does the following:
aaptis invoked to generate an
R.txtfile for the entire Android app.
R.txtfile that is shipped alongside each library makes it possible to map the values back to the app’s
R.txtfile. The library’s
R.txtonly lists Android resources that it uses.
R.javafiles are generated using the integer values from the app’s
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
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:
3173 ms _GenerateJavaDesignerForComponent 1 calls
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.
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.