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:
-
-
- Generate an
R.java
file for each Java library. - Create a
Resource.designer.cs
file, 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.
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 anR.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’sR.txt
file. The library’sR.txt
only lists Android resources that it uses. - Smaller
R.java
files are generated using the integer values from the app’sR.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:
-
- See the pull request on Github, for further detail and source code.
- See Shrinking Your Android App Size for more tips & tricks for smaller APKs.
Great news! When it will land in Stable channel?
I don’t think we have public release dates, but you can make a guess based on what has happened for past VS releases: https://docs.microsoft.com/en-us/visualstudio/install/visual-studio-build-numbers-and-release-dates
Great! Does this improvements have any impact on runtime performance of a XF Android app?
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
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.