Jonathan Peppers


Xamarin, C# nerd, at Microsoft

Xamarin.Forms Performance on Android

It's been a while since my last post, I've been on parental leave--a great feature of working for Microsoft.

One of the tasks that has always been in the back of my mind on the Xamarin.Android team is to make some improvements to help Xamarin.Forms apps. It turns out that one of our most common Xamarin project is developed with Xamarin.Forms in Visual Studio on Windows. We should improve that scenario!

However, as all performance-related work goes, it isn't that easy. As a starting point, I thought I would share some easy tricks you can apply to your apps right now.

Other Articles

There is already quite a bit of content out there on Xamarin.Forms performance. I would start by making sure you are already doing what is currently recommended by others.

These are some great resources I would start with, now let's dive into some of my discoveries...

Measuring your current performance

Before doing any performance-related work, you need to make sure you have a valid understanding of the current performance in your app. Unforunately I haven't found alot of guidance out there on timing Xamarin.Forms applications, but I'll share the approach I've been using.

If you are trying to benchmark plain C# code at a lower level, you can luckily rely on a few existing benchmarking libraries:

Both of these libraries will run on your desktop machine to time C# code at what you can think of as at a "unit test" level. If you want to time performance in your shared C# code, this is the way to go.

Unfortunately timing performance within a Xamarin.Forms app (or even a classic Xamarin app) is not quite as easy. This would be timing your app at more of an "integration test" level, and I've not found a library that does this yet. To make things even more complex, timing the time it takes a page to appear in Xamarin.Forms requires changes across different classes: your activity/controller, your XF page, and potentially custom renderers.

So my approach is to use a static class such as the following, allowing you to name different time intervals across your app:

Then for timing a Xamarin.Forms Android app, I call Start/Stop methods in various places:

  • Application.OnCreate - Start "OnResume" interval
  • MainActivity.OnResume - Stop "OnResume" interval
  • Put a Start/Stop around the Xamarin.Forms.Forms.Init() call

When recording times like this, make sure to test your app in Release mode on a physical device. Make sure to record several times, because your timings will vary. For an example of setting this up, see an example of mine on Github here (note it's Android only)

You should also be sure to give the Xamarin Profiler a try. I found profilers in general can be really good at spotting issues, but they don't really offer a lot of insight if your app is getting faster or slower due to your changes. The profiler should be great at finding things like: memory leaks, code that creates a lot of garbage, hot paths, etc.

The Linker and Java Binding Projects

As I transitioned to an engineer on the Xamarin.Android team, there was a common situation I noticed happening in many apps.

Xamarin.Android binding projects generate C# glue code that enables us to call into Java APIs from C#. As it goes with any library, you are certainly not using most of those APIs--usually a small subset specific to your app. The default linker option of SDK Only is not going to strip code in dependant assemblies, so your app will contain alot of compiled C# that you don't need.

Now think about the Android support libraries: thousands of APIs you are certainly not going be using. All of this C# glue code is sitting in assemblies bundled with your app that will never be called... My initial experiments showed that pretty much every app using the support library with Link SDK Only has about 4 MB of .NET assemblies that is not needed! That is certainly going to impact startup time! Effectively every Xamarin.Android app uses the support libraries, so I dug into finding a way to improve things.

I discovered that using the [assembly: LinkerSafe] attribute within binding projects is an effective way to completely mitigate this issue. I sent a PR to the support libraries that effectively saves 3.8 MB of APK size on every single Xamarin.Android app! These improvements should be available in the next version of the support libraries (27.x), until then use the link all assemblies option if you can.

NOTE: unfortunately existing apps may take a little work when switching to full linking. If your app uses reflection or the like, you may need to add the [Preserve] attribute or take similar actions.

So for the the future, my guidance on linking is:

  • Use the link all assemblies option for new apps, or choose if you want to go through the pain to enable it for your existing apps. Always do manual testing of your app in Release mode (with the linker on).
  • Enable [assembly:LinkerSafe] in your own binding projects, and suggest it to other developers.
  • Get on the 27.x support library (and API 27), available in Xamarin.Android 8.2 (in preview now).

Proguard

If you are going to setup linking for your app, the next obvious step for Android is proguard. This is not going to directly benefit performance of your Xamarin.Forms app on Android, but it has a myriad of other benefits.

A smaller dex file (compiled Java) means:

  • Smaller APK size
  • Improvements on startup time
  • Helps keep you under the dex limit to avoid needing multi-dex

Just like with enabling the full linker, proguard can cause some issues you will have to work through. For full details on setting up proguard, dig into Jon Douglas's excellent post here.

Images & Bitmaps

The Android.Graphics.Bitmap class is the bane of every Xamarin.Android developer's existence. Due to the nature of the relationship between the C# and Java worlds, the GC can reach a point where your app will completely fall over if you are not explicitly cleaning up Bitmaps.

For instance if we think about the two sides of a Bitmap object:

  • C# side - a few bytes, mainly a few fields holding IntPtrs to the Java world
  • Java side - potentially huge, contains the Byte[] that could be megabytes in size

Naturally the Mono GC isn't tracking the full size of the Bitmap, since it's C# side is very small. This can cause your app to quickly get out of memory exceptions on either the Java or C# side.

In classic Xamarin.Android apps, I generally take the following approach:

  • Don't use Bitmap, use AndroidResource and the resource system. The native APIs are very efficient. Downloaded images are probably the only case where Bitmap is required.
  • If you have to use Bitmap, cache them in memory and reuse them. Google even recommends to Java developers to use the LRUCache class for this.
  • Explicitly call Recycle() followed by Dispose() when you are finished with a Bitmap.

Xamarin.Forms and Bitmap

So to get an idea of how this works in Xamarin.Forms, let's take a peek at the default IImageSourceHandler for Android:

Hmm, this brings some thoughts to mind:

  • Where do these get recycled/disposed? Each custom renderer is in charge of doing it itself...
  • Does anything cache these? Nope.
  • Android resources are loaded as Bitmap! Whoops.

Unfortunately, the API design of Xamarin.Forms somewhat puts us into a corner here. The design they chose completely makes sense: images can come from files, URIs, .NET embedded resources, AND Android resources. Xamarin.Forms should totally use Android.Graphics.Bitmap because it covers all cases. It is somewhat unfortunate what can happen to an image-heavy Xamarin.Forms app on Android: it can get to a point where it falls over.

Let's presume you have some code like this in your app:

Even with small images like 100x100, scrolling down you will quickly hit a limit where images fail to load. In my images-specific sample found here, notice how quickly images cause an app to fall over:

Android Images in Xamarin.Forms

When running the app, you will also quickly notice its sluggishness and ridiculous amount of console output. Out of memory exceptions are happening in the background for quite a while after the app loads...

So what about ListView?

In the above example, we are loading the images up front and pay for the performance cost of the entire ScrollView on load. ListView can virtualize items as you scroll (ListViewCachingStrategy), but in some ways it can be worse. Let's say you use ImageCell (or even just a ViewCell with a complex layout with Image). Only the visible cells will get loaded up front on the page, and subsequent Bitmaps will get created as you scroll. This means the page will load alot quicker, but you run into sluggishness while scrolling.

To understand what's happening let's explore what happens to a data-bound ListView Cell while scrolling:

  • The Cell is created, along with the native views, custom renderers, etc.
  • The BindableProperty of the ImageSource gets set via data-binding (BindingContext is set)
  • The IImageSourceHandler is invoked, creating an Android.Graphics.Bitmap
  • The Bitmap is passed to the native control, and the C# instance is Dispose()'d immediately. Note XF can't call Recycle(), since we don't know when the native side is done with the Bitmap.
  • The Cell gets scrolled off screen, where it can be recycled. The Cell's BindingContext is set to null.
  • The native control's image is cleared
  • Repeat...

Notice how many Android.Graphics.Bitmaps get created here... If two rows in a ListView use the same image, they each use a copy of the exact same image. If you scroll a cell off screen and bring it back, it will load a new Bitmap object when it is brought back onto the screen.

Bear in mind, I am not criticizing on how the Xamarin.Forms implemented this. I would have likely arrived at the same place while developing XF, given the WPF-like APIs they were trying to emulate when building their amazing framework.

Is there a fix?

Luckily, after a bit of digging, I found an extremely simple way to workaround this problem in your own apps.

  • Step 1: only use AndroidResource for your images. Absolutely nothing else!
  • Step 2: use my following magical image handler

WTF?!? how does that work?

When reviewing Xamarin.Forms source code, I noticed this little nugget of fallback logic:

Since this appears to be setup for Image (both the fast renderer and the older one) and ImageCell, we can exploit this fallback logic to our needs. When adding this image handler to my sample, it both loads and scrolls blazingly fast. No out of memory errors--it just works. It has the exact same performance you would expect an image-heavy classic Xamarin.Android app to behave using AndroidResource.

Is there any catch?

Obviously there are a few gotchas:

  • Any custom renderers that use ImageSource, will also need this AndroidResource fallback logic
  • Custom logic would need to be added to your ImageHandler if you also need to load image files straight from disk

The only other issue I see here is that XF's ResourceManager class uses alot of System.Reflection. Maybe some caching code could be added here to speed things up even further? Or use Android APIs instead to grab a resource integer Id from its name.

The Future

I want to dig further into the issues with using heavy Java objects in Xamarin.Android like Android.Graphics.Bitmap. It remains to be seen if there are some improvements that could be made or not.

A shout-out to the Xamarin.Forms team, get in touch with me when I get back from parental leave in a few weeks. I am definitely willing to help with improvements on the Android side of Xamarin.Forms. Maybe there are things we can do in Xamarin.Android itself?


comments powered by Disqus