In the modern world of mobile applications, there has been a strong push over the last years to make applications launch as quick as possible. This has resulted in users expecting applications to startup quickly. In saying all of this, you may have seen that your Xamarin Android application isn't so snappy and tends to load a lot slower than their native counterparts. But why is this and how do we improve the startup performance of our Xamarin Forms Android application? To understand this, let's take a step back and understand how Android works under the hood.
Android AOT
If you have ever opened up an Android APK file, you would have seen some .dex files (Dalvik EXecutable), which are essentially a compact form of bytecode. These .dex files are then executed by the Android Runtime (ART). Now ART has a nifty trick to improve application performance and memory usage through the use of AOT (Ahead-of time) compilation. What it does is during the installation of the APK, it calls a utility called dex2oat (Dalvik Executable to Of Ahead Time) to convert the bytecode native machine code.
But why go through this whole process? Why not just bundle the AOT files like iOS does? The answer is simple, AOT complied files tend to be much larger and therefore increase the download size of the APK. Xamarin applications cant hook into the process above as there is an additional requirement of first needing C# code to be interpreted through Mono. So where does this leave Xamarin developers? Basically, there are two options, we can either use Mono to convert all our code to AOT compiled files, but as we saw earlier, this will significantly increase the size of an our APK. The other option, which will be discussed in detail here, is to use a startup tracing with a custom profile. This natively compiles just the code that is used during application startup, while leaving the rest of the code to be compiled Just-In-Time (JIT). This gives us the best of both worlds by giving us the benefit of having a faster application startup, while only slightly increasing the APK file size.
Startup Tracing with Custom Profile
Before we go any further, I would just like to clarify some potential confusion. Some of you may have heard of a feature called Startup Tracing (Without the custom profile) that was introduced to Xamarin a few years back. This is essentially a predecessor to the custom profiles and it only precompiled the most expensive methods of a blank Xamarin Forms application. This was a good first stab at trying to improve startup performance, but its benefits weren't as good as they could be as your final application would normally have added a lot more method calls during startup, which wouldn't be natively compiled.
Startup Tracing with a Custom Profile, build upon the above by inspecting your running code and building up a custom profile that tells Mono exactly which methods and classes need to be pre-compiled. Now that's what we want!
Show Me The Code
Creating a Custom Profile is relatively easy and involves passing some arguments to MSBuild. Before you start creating your custom profile, you need to be aware of the following pre-requisites:
- You need to include the INTERNET permission in your AndroidManifest.xml
- Only have one physical device or emulator connected. It doesn't really make a difference as to which you use, as we are only going to inspect the code that gets executed during startup.
- The commands below need to run from a terminal in the directory of your Android .csproj file
Once you have all the above are in place, in the terminal, type in the following:
msbuild /t:BuildAndStartAotProfiling
MSBuild will now start building your application so that it can profile what methods and classes are being called during startup.
Once the build has been completed, the application will startup as normal in the emulator/device and once you are happy that it has fully loaded, you can type the following command to stop the profiling.
msbuild /t:FinishAotProfiling
You should now see a file called custom.aprof created in your Android folder, which contains a list of all the classes and methods that were called during your profiling session. We now need to edit the Android project .csproj file to include the custom profile so that MSBuild knows how to process it. This can be done by adding the following to your list of ItemGroups
<ItemGroup>
<AndroidAotProfile Include="$(MSBuildThisFileDirectory)custom.aprof" />
</ItemGroup>
The last step is to tell MSBuild to only process and create the precompiled code on our Release builds by adding/editing the following two lines:
<AndroidEnableProfiledAot>true</AndroidEnableProfiledAot>
<AndroidUseDefaultAotProfile>false</AndroidUseDefaultAotProfile>
And that's it! The first line will enable Startup Tracing with the Custom Profile and the second line disables the generic profile that was discussed earlier. Now when your application is built in Release mode, Mono will automatically inspect the custom.aprof file and precompile all the methods listed within there. It is also worth noting, that if you are actively developing your application, it would be worth repeating this process every few months the methods called during your startup may have changed.
Summary
Startup Tracing with a Custom Profile provides us with the best of both worlds, we can improve startup performance while only slightly increasing the size of our APK. In my experience, I have typically seen about a halving in startup time, but this will obviously be different from application to application. The benefits of this far outweigh the effort involved, so I would highly recommend you in doing it.