I have always been a big proponent of reducing the developer feedback loop, which is the time it takes from the moment you start building your code until you can verify if it is working correctly. Over the years Microsoft has made several improvements to Xamarin to reduce this feedback loop and in doing so have provided us a plethora of options for us to configure. However, if you incorrectly configure your application, you can lose countless hours of productivity waiting for builds to complete. So, let's take a look how to setup your Android and iOS applications so that they can be as quick as possible for Debug Builds while still maintaining the efficiency on Release builds.
iOS Build Configuration
First, we will look at the various build options that we have for iOS. You can get access to these by right-clicking on your iOS project and selecting Properties . If you want the TL;DR version of this, you can change your settings to match the Debug and Release images below. You can also continue reading to understand what each option does.
Lets dig a bit deeper and get an understanding of what these settings do under the hood.
- Linker Behavior
The linker is a process that removes any C# code that is not being used in your application, such as classes, methods, events etc. which reduces the overall application size. However, this process increases the build time as the code needs to be analysed to see which methods can be removed. On Debug builds leave this on Don't Link and ideally on Release builds you would want to choose Link All Assemblies, but this will require that you properly test your Release build, as methods/classes etc may be removed that you actually require. The safer option is Link Framework SDK's only which will only remove methods/classes etc. that are part of the Xamarin SDK, so it won't be as efficient.
- Use the LLVM optimizing compiler
Enabling this will generate code that is smaller and faster, however this will increase the build time of the application. You should only enable this on Release Builds.
- Enable the Mono Interpreter
This is only available on Debug/Release Builds which target a physical device and enables features such as Hot Reload. If the configuration is targeting the simulator, this option will be disabled. It is recommended to only enable this feature in Debug Builds.
- Strip native debugging symbols
Enabling this setting will ensure that the debug symbols generated are not bundled as part of the final application but will be written to dSYM files. This can significantly reduce the size of the application, so it is generally turned on for Release builds. In Debug builds, this setting can vary from project to project, as stripping the debug symbols takes time, but results in a smaller file being deployed to the simulator. In some cases, the time taken to reduce the size is longer than it would take to deploy, so this setting should be tested out on your project.
- Enable incremental builds
This will allow you to only build the files that have changed since the last build, instead of the entire application. This option should be enabled on Debug Builds only.
- Enable device specific builds
This will ensure that a build is only done for the attached device or simulator instead of building for all the architectures specified in your Supported Architectures. This should only be enabled on Debug Builds.
- Additional mtouch arguments
The most common mtouch argument you will use here is to tell the linker to skip assemblies from being stripped and can be done using the following syntax --linkskip=AssemblyName. That setting should only be used in Release mode in combination with the Link All Assemblies option in the Linker.
There is also the --time --time (Yes, double time) argument which gives you timings on how long each part of the build process is taking. This allows you to measure different configurations, like the strip native debugging symbols above and will output something similar to the following:
Setup: 63 ms Resolve References: 773 ms Extracted native link info: 1382ms . . Total time: 4211 ms
- Optimize PNG images
Apple has created a modified version of the pngcrush utility that allows the PNG's to be loaded faster on iOS. This setting should only be enabled on Release Builds.
Android Build Configuration
Now let's take a look at some of the Android build options that we have available to us, and as you will see, some of them will be very similar to iOS. You can access these settings if you right-click on your Android project and select Properties . For those looking for the TL;DR you can use the images below
Now let's dive a bit deeper into each option to get an understanding of what they do.
- Use Shared Runtime
This essentially installs the Mono runtime (mscorlib.dll, Mono.Android.dll etc.) as a shared runtime package on the device, so that they don't need to be included in your package in every build. This coupled with the setting below (Use fast deployment) can greatly decrease the time it takes to deploy your package. This is only enabled on Debug builds, as in the Release build you want those assemblies to be packaged into your bundle.
- Use fast deployment
This setting goes hand in hand with the Use Shared Runtime option above. When enabled, it doesn't include the base class & binding libraries that are in the Mono runtime into the bundle. Instead, the bundle refers to the shared runtime on the device which reduces the size of the bundle to be deployed. In addition, only the files that have been changed are copied to the device thereby reducing the time taken to deploy. This setting should only be enabled on Debug Builds.
- Use incremental Android packaging system (aapt2)
This splits the packaging of the application into two different steps, namely Compiling & Linking. The benefit that this provides is that only files that have changed are compiled thereby saving you time. Enable this option for Debug & Release Builds.
- Dex compiler
The Dex compiler is an application that translates your Java bytecode into DEX bytecode. It is recommended to use the d8 compiler in both the Release & Debug builds as is faster and more efficient than its legacy counterpart, dx.
- Code shrinker
This feature is used to shrink Java code in your application by removing any unused classes, methods etc. The legacy option is called ProGuard and is no longer recommended. Instead, its newest sibling, R8 is the recommended option for Release Builds. For Debug Builds, it is recommended to leave this option empty so it will not remove any code, thereby making your builds faster.
- Enable startup tracing
I have a whole blog post dedicated to this, but a TL;DR is that this does some Ahead of Time (AOT) compilation of your application to make it start faster. Only enable this feature on Release Builds.
As with iOS, this will strip out any unused C# classes, methods etc. from your code, thereby reducing it size. For Debug Builds, you should change the option to None so it doesn't slow down your build process. For Release Builds, it is recommended to have the option set at least to Sdk assemblies only. If possible, you should set it to Sdk and User assemblies however, as with iOS, this will require that you thoroughly test your application to ensure no classes or methods were mistakenly removed.
- Skip linking assemblies
As with the mtouch arguments on iOS, this gives you the ability to skip linking on any assemblies that are defined in the list. To do so, you just enter in the actual name of the dll. For example, System.Text.Json;Xamarin.AndroidX.Work.Runtime;
It is important to keep the developer feedback loop as small and tight as possible, so that we can be more productive. We have seen that both Android and iOS have a vast array of options that we can change to optimize our Debug Builds to make sure that they are as quick as possible, while at the same time ensuring your Release Builds are as efficient as possible. I would highly recommend that you review your existing project setup and ensure that it is as optimal as possible. In the long run it can save you countless hours in productivity!