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.

The Challenge

This #XamarinChallenge is about three very important application related topics:

  1. Build Times
  2. Startup Times
  3. 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.

Sign the APK Configuration Panel

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.

Deploying The Package

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.

App Package 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.

Open App Package Analyzer in Android Studio

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.

App Package Analysis Screen

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.

AAB Lacking Download Size Information

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
AAB Package Download Size

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.