Xamarin.Form – memory management

This post is part 4 of series of Xamarin.Forms – real life stories posts.

The nature of the application we were creating is that it is in use almost 24×7. And memory leaks, even small ones, pile up. And at some stage iOS throws you a memory warning, and shortly afterwards, application gets killed. First time we noticed the problem was when the application was about 50% ready and serious testing started. Our tester complained that after 2 hours of use, the application became slow and then it crashes. Investigation of  crash files with native iOS tools showed that operating system killed the app because of excessive memory use.

Now we know the exact cause of the problem, how do we solve it?

Let’s look into memory allocations AKA memory leaks. If there is a suspicion that memory is leaking, we need to prove it before fixing it.

Note: we are going to discuss memory management investigation on iOS/Android. There are plenty of memory profiling tools for Windows – feel free to use your favourite one.

In iOS there are 2 options: Xamarin Profiler and native XCode instruments. For Android, only Xamarin Profiler can be used.

Xamarin profiler is still in Preview. But it is the only tool that shows allocated objects using their Xamarin notation. However, it shows ONLY Xamarin allocated objects. This means that if native objects are leaking, Xamarin Profiler won’t show them. You can use native XCode memory allocation tool (from Instruments collection) which displays all allocated objects. For Xamarin objects the mapping is easy: Xamarin_Forms_Platform_iOS_ViewCellRenderer_ViewTableCell native object is Xamarin.Forms.Platforms.iOS.ViewCellRenderer.ViewTableCell class

XCodeMemoryAllocations

How do we prove existence of memory leaks?

  • It can be done only on a real device. In our case profiler running against iOS sumulator shows very bright picture. It is completely different when we run profiler against app running on iPad.
  • Assuming your application is of cyclic nature (i.e. you have main screen, from the main screen you navigate to some functionality, and you come back to main screen afterwards), navigate through your app following the cycle, e.g. Page1 → Page2 → Page3 → Page2 → Page1. Measure your memory allocation every time you on Page1. If it grows, it’s likely you have memory leak.
  • Sometimes memory grows not because memory leak, but because garbage collector running depends on many factors. In this case long running test should be executed. To do this, create Xamarin UI test that implements  Page1 → Page2 → Page3 → Page2 → Page1 cycle. Put it into while(true) loop – and run. Be careful with the timing. Put reasonable delay between navigations so execution speed will be close to the one of real users. Then seat, look on memory allocation graph, and relax. If it is stable – you are good. If not – move to next step: fixing memory leaks. Unfortunately this test works in iOS only as you cannot run Xamarin Profiler and UI test in the same time.

In the event we do have memory leaks – there is no easy and generic solution. At the very least, we need to find out

  • which functionality is leaking memory
  • what type of objects remains in memory and not cleaned up by garbage collector
  • ideally: which code allocated these objects

Once we know where and what is leaking, it is much easier to help garbage collector to de-allocate these objects.

Find functionality leaking memory

In general, memory should be de-allocated when user navigates back on navigation stack. In our case of Page1 → Page2 → Page3 → Page2 → Page1 cycle, memory use should increase on Page1 → Page2 → Page3 part of the cycle, and decrease on Page3 → Page2 → Page1 part.

To do this, try to break your cycle to smaller ones. For example, instead of running Page1 → Page2 → Page3 → Page2 → Page1 cycle, try to run separate tests for Page1 → Page2 → Page1 and Page2 → Page3 → Page2 cycles.

Let’s say we can see that Page2 → Page3 → Page2 mini-cycle leaks memory. Now we need to identify what exactly is leaking. We can do it using snapshots. Create “Snapshot 1” when you are back to Page 2 after 1st cycle, “Snapshot 2″when you are back to Page 2 after 2nd cycle, “Snapshot 3” when you are back to Page 2 after 7th cycle.

Now stop execution and look on the difference between “Snapshot 2” and “Snapshot 1”. Do you see anything obvious? Do you see Xamarin.Forms objects (especially from you application namespace) that should have been disposed? Do you know who instantiated this object? Main suspect is Page 3, as this is the one who’s been removed from stack.

For example:Screen Shot 2016-07-07 at 11.30.49 AM

What we can see on the screenshot above? We can see 4 snapshots (generation in Apple language). When you expand Snapshot 4, you can see which objects exist in Snapshot 4 and don’t exists in Snapshot 3. But assuming that these 2 snapshots were taken at the same place in navigation stack we expect memory state to be almost identical, i.e. every object is a potential memory leak. We can see a lot of native objects. In most cases it’s useless as we don’t deal with native objects in Xamarin.Forms. But everything from Xamarin or your application namespace is relevant. In this example it looks like Xamarin.Forms.Platform.iOS.LabelRenderer is leaking. Not surprisingly there is matching UILabel is leaking as well (4 rows above LabelRenderer). If there’s no obvious leaking candidate, look into Snapshot 3. It will show objects that are suspected in memory leak. Try to find those with persistent count is multiple of 5. Why 5? Because there were 5 iterations between Snapshot 3 and 2. It means it is highly likely that these objects are leaking on every iteration.

But why does it leak? Let’s see how Xamarin.Forms works .

Let’s look on the diagram below:

image

Xamarin.Forms.Button control is managed by ButtonRenderer on each platform and linked to Xamarin wrapper of the appropriate native control (on iOS and Android) or to Windows control directly on WIndows family. It means that data bound to Xamarin.Forms control could be actually linked to external, non-.Net managed resource. This is a warning sign. But framework should take care about this, right? Well, it should.. And it does, most of the time…

If we work using classic MVVM model then we have our model resources (strings, collections, etc.) bound to UI controls. In Xamarin.Forms case, managed string could be bound to an unmanaged resource. This could prevent garbage collector from collecting those controls.

Here is the list of patterns we have implemented in our project to minimise memory leaks. Some of these patterns may be not relevant anymore as memory leak bugs get fixed in every subsequent version of Xamarin.Forms. Our app was released based on Xamarn.Forms 2.1:

Custom renderers cleanup

The following class was used for custom renderer management:

public class DisposeManager
{
    private readonly IDisposable ;
    Page NavigationPage ;

    public DisposeManager(IDisposable disposableRenderer, Element element)
    {
        _disposableRenderer = disposableRenderer;
        SetupDisposeEvents(element);
    }

    private void SetupDisposeEvents(Element element)
    {
        var contentPage = GetContainingPage(element);

        _navigationPage = GetContainingNavigationPage(contentPage);
        if (_navigationPage != null)
        {
            _navigationPage.Popped += OnPagePopped;
        }
    }

    private Page GetContainingPage(Element element)
    {
        Element parentElement = element.ParentView;

        if (parentElement is Page)
        {
            return (Page)parentElement;
        }
        return GetContainingPage(parentElement);
    }

    private NavigationPage GetContainingNavigationPage(Element element)
    {
        var parentElement = element.Parent;

        if (parentElement == null)
        {
            return null;
        }

        if (parentElement is NavigationPage)
        {
            return (NavigationPage)parentElement;
        }

        return GetContainingNavigationPage(parentElement);
    }

    private void OnPagePopped(object s, NavigationEventArgs e)
    {
        if (e.Page == _contentPage)
        {
            _disposableRenderer.Dispose();
            _navigationPage.Popped -= OnPagePopped;
        }
    }
}

It ensures that renderer is disposed when container page is popped out from navigation stack. The usage is very simple:

public class SomeRenderer : ListViewRenderer
{
    private DisposeManager _disposeManager;

    protected override void OnElementChanged (ElementChangedEventArgs<ListView> e)
    {
        base.OnElementChanged (e);
        if (e.NewElement != null) {
            _disposeManager = new DisposeManager (this, e.NewElement);
        }
    }
}

Binding Cleanup

To help garbage collector to release resources we need to break as much binding connections as possible. In order not to affect the way application works, we need to do it just before the current page is removed from navigation stack. To make implementation of this pattern easy, we make all ViewModel implement INavigable interface, amd all Views to inherit from ParentContentPage:

public interface INavigable
{
    void CleanupViewModel();
}

public class ParentContentPage : ContentPage
{
    public NavigationPage NavigationPage { get; set; }

    protected override void OnAppearing ()
    {
        base.OnAppearing ();
        var navigableViewModel = this.BindingContext as INavigable;
        if (navigableViewModel != null) {
            var isPageCreation = NavigationPage == null;
            if (isPageCreation) {
                NavigationPage = GetContainingNavigationPage ();
                if (NavigationPage != null) {
                    NavigationPage.Popped += OnPagePopped;
                }
            }

            navigableViewModel.OnAppearing (isPageCreation);
        }

    }
    public virtual void OnPagePopped ()
    {
        var navigableViewModel = BindingContext as INavigable;
        if (navigableViewModel != null) {
            navigableViewModel.CleanupViewModel ();
        }
        Device.BeginInvokeOnMainThread (() => BindingContext = null);
        NavigationPage.Popped -= OnPagePopped;
    }

    private void OnPagePopped (object s, NavigationEventArgs e)
    {
        if (e.Page == this) {
            OnPagePopped ();
        }
    }

    protected NavigationPage GetContainingNavigationPage ()
    {
        return GetContainingNavigationPage (this);
    }

    protected NavigationPage GetContainingNavigationPage (Element element)
    {
        var parentElement = element.Parent;

        if (parentElement == null)
            return null;

        if (parentElement is NavigationPage)
            return (NavigationPage)parentElement;
        return GetContainingNavigationPage (parentElement);
    }
}

This implementation monitors main NavigationPage object. When current page is popped form the application navigation stack, CleanupViewModel function gets called on the page ViewModel. In this function we need to:

  • Cleanup all collections used as ListView data source
  • Set all Command’s to null to release command event handlers
  • Do anything else that could prevent View/ViewModel from being released by garbage collector

Explicit call to garbage collector on navigation back in the stack

This one is a controversial one. Some people say that you should never explicitely call to garbage collector. And, in general, I would agree with this. However, in Xamarin the magic call to GC.Collect() can do wanders. If nothing else helps, just call to GC.Collect(); immidiately after calling await _navigation.PopAsync(true) .

Avoid hidden images

Imagine you need to display user’s image on the screen. But if user didn’t provide any image, placeholder should be displayed. One of the ways to implement this functionality is to always display placholder image. If user’s image is available, it will be displayed on top. For some reason this caused massive memory leak in iOS. And the image that was leaking was placeholder image.

Solution: instead of using 2 images, use only one. If user image is not available, bind image source to placeholder.

To summarise

The list of patterns above doesn’t guarantee memory leak free app. Also, there’s no gurantee that those patterns will change anything, as Xamarin guys work hard and making things better with every version. I have just described what worked for us. I will be very happy if this will save some time to another developer, because we have spent a lot of time trying to rectify and minimise these problems.


Readify has upcoming events on Xamarin, Mobility and Cross Platforms: visit Readify.net/events to find out more or follow Readify on Twitter

 

 

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s