Xamarin.Forms - Android App Performance and Package Size Reduction #XamarinChallenge
In the spirit of the #XamarinChallenge and the fact that I've recently been dealing with reducing the size of my application (see my previous blog post on Fixing Xamarin.Forms linker issues), I've decided to see what are the other means of getting your app package to decrease in size and potentially improving its performance .
Recap
In case you've missed all of the improvements that the Xamarin.Forms team has been releasing, here's a bunch of links to blog posts/docs/articles that will get you up to speed with all of the latest and greatest in the Xamarin and Android app performance, build times and package size reduction world.
- Faster Xamarin.Android Builds & Smaller Dex Files - https://devblogs.microsoft.com/xamarin/dex-counting-xamarin-android-improved/ by Jonathan Peppers
- Faster Startup Times With Startup Tracing On Android https://devblogs.microsoft.com/xamarin/faster-startup-times-with-startup-tracing-on-android/ by Jon Douglas
- Optimize your Xamarin.Android Builds https://devblogs.microsoft.com/xamarin/optimize-xamarin-android-builds/ by Jonathan Peppers
- Initial support for Android App Bundle publishing format https://docs.microsoft.com/en-us/xamarin/android/release-notes/9/9.4#initial-support-for-android-app-bundle-publishing-format
- Android App Bundle https://developer.android.com/platform/technology/app-bundle
- AAPT2 enabled by default for new projects https://docs.microsoft.com/en-us/xamarin/android/release-notes/9/9.4#aapt2-enabled-by-default-for-new-projects
- AAPT2 (Android Asset Packaging Tool) https://developer.android.com/studio/command-line/aapt2
The Challenge
This #XamarinChallenge is about three very important application related topics:
- Build Times
- Startup Times
- Package Size
Let's go through these in details one by one.
DISCLAIMER: I'm performing all of the improvements on an already existing store app Visual Stimulation. The results others might get can differ.
Build Times
The instruction for this part of the challenge are documented here. Just to reiterate (for convenience), you will need to update the Debug
configuration section in the .csproj
file. For me, these changes looked like this:
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<AndroidUseAapt2>true</AndroidUseAapt2>
<AndroidDexTool>d8</AndroidDexTool>
<DebugType>portable</DebugType>
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
<!-- Above are the added/updated entries -->
<DebugSymbols>True</DebugSymbols>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>TRACE;DEBUG;__ANDROID__</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
<AndroidLinkMode>None</AndroidLinkMode>
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
<BundleAssemblies>false</BundleAssemblies>
<AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
<Debugger>Xamarin</Debugger>
<AotAssemblies>false</AotAssemblies>
<EnableLLVM>false</EnableLLVM>
<AndroidEnableMultiDex>False</AndroidEnableMultiDex>
<EnableProguard>False</EnableProguard>
<AndroidLinkSkip>
</AndroidLinkSkip>
<JavaMaximumHeapSize>1G</JavaMaximumHeapSize>
<AndroidSupportedAbis>armeabi-v7a;x86;x86_64;arm64-v8a</AndroidSupportedAbis>
<AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
</PropertyGroup>
Unfortunately the results of these changes didn't improve the build times but rather slightly increased them.
Cold Build
- Before Build Times Changes:
Time Elapsed 00:00:45.20
- After Build Times Changes:
Time Elapsed 00:00:45.82
Incremental Build
- Before Build Times Changes:
Time Elapsed 00:00:06.78
- After Build Times Changes:
Time Elapsed 00:00:08.17
This feels like the part of tooling where the Xamarin.Forms team could/should work on a bit more, as I would have assumed that the goal was to get these times to go down.
Startup Times
This part of the challenge has really easy setup, which is literally adding or updating the AndroidEnableProfiledAot
entry in your Release
configuration.
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>False</AndroidUseSharedRuntime>
<AndroidLinkMode>Full</AndroidLinkMode>
<AotAssemblies>false</AotAssemblies>
<EnableLLVM>false</EnableLLVM>
<BundleAssemblies>false</BundleAssemblies>
<AndroidDexTool>d8</AndroidDexTool> <!-- This entry is optional -->
<AndroidLinkTool>r8</AndroidLinkTool> <!-- This entry is optional -->
<AndroidPackageFormat>aab</AndroidPackageFormat> <!-- This entry is optional -->
<MandroidI18n />
<AndroidSupportedAbis>armeabi-v7a;arm64-v8a</AndroidSupportedAbis>
<AndroidEnableProfiledAot>true</AndroidEnableProfiledAot> <!-- Add/Update this entry -->
</PropertyGroup>
The results are quite satisfactory, as the app startup time improves by ~18.2% (0.75 sec). The only downside to this change is an increase in the app size of the application package by ~29% (6.3 MB).
Startup Time Improvements
- Before Startup Tracing:
4s 106ms
- After Startup Tracing:
3s 356ms
Application Package Size Increase
- Before Startup Tracing:
21.4 MB
- After Startup Tracing:
27.7 MB
This clearly leaves us with a trade off of improvement of the app start times and increasing app's package size. Make sure that before you make the decision you weight all the pros and cons.
Package Size
The last but not least is the part when we will try to reduce the size of our application package. We yet again need to edit Release
configuration in the .csproj
file, as per the instructions.
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<!-- Other config entries -->
<AndroidDexTool>d8</AndroidDexTool>
<AndroidLinkTool>r8</AndroidLinkTool>
<AndroidPackageFormat>aab</AndroidPackageFormat>
</PropertyGroup>
I ran into an issue with executing the provided in the instructions msbuild
command, therefore I've generated the *.aab
file differently. In Visual Studio go to the Properties window of your Android project and check the Sing the .APK file using the following keystore details option. Next, in the same window, provide the details for the keystore
file that you use for the store releases.
Otherwise you could leave the above step completely out, as by default (debug) keystore
will be used to sign your app. However this can skew your results on the comparison of the before and after app sizes.
Whether you've completed the details of your keystore
or not, the next step would be to deploy your app (not just build it) to a device or an emulator. Deploying it will ensure that the package (either .apk
or .aab
) gets generated.
Once the deployment is finished you should be able to find the generated package in the Release
folder of your Android project. For me the package file was located in the <head_project_name>\bin\Release
directory.
Having the before (*.apk
) and after (*.aab
) packages generated, we proceed to the measuring the download size of the app step.
As I predominantly use Windows for my dev work I needed to use Android Studio to determine the download size of the generated package instead of using the command line to take advantage of the apkanalyzer
tool, as there's an issue with using it on the MS operating system. See below screenshot(s) to understand how to get the apk analyzer to evaluate the app package.
After you pick your *.apk
or *.aab
file then you should see a new tab pop up with the details of your package displayed in it.
NOTE: You might need to update Android Studio to latest to be able to pick *.aab file(s) for analysis.
After a bit of a hassle with figuring out the above I was finally able to get the information about the download size of the application for the *.apk
file
- Before Application Size Changes:
21.4 MB
Unfortunately, the Android Studio analyzer did not show me information about the download size of the package for the *.aab
file.
I was fortunately able to get some information about the download size of the package after uploading it to the Beta track of Google Play Store.
- After Application Size Changes:
15.1 - 16.1 MB
In conclusion, if we take the upper limit of what Google Play Store is indicating about the download size of the *.aab
package and compare it to the *.apk
package we get ~24.7% decrease in size.
Summary
I really enjoyed this exercise, not only because I was able to slightly improve the performance of my application and reduce the app package size but I've also learned few things (e.g. discovered apkanalyzer
tool) while challenging myself.
I've issued my results to the Xamarin.Forms team so that they can work on the improvements the tooling - these are available to view in the Jon Douglas's Github repo.