Select to view content in your preferred language

.NET 8 / MAUI 8: Big memory leak in MapView on Android introduced

2978
25
01-19-2024 11:00 AM
Labels (3)
SokoFromNZ
Regular Contributor

Hello.

The current ArcGIS Runtime v200.3.0 with the current .NET Maui from VS 17.8.5 has a big memory leak which causes our app to crash on a real world Android device just after a few page changes to the map.

I can reproduce this behavior on any real world device. There the app crashes. The emulator though just allocates more and more memory of course.

I have created two simple apps (see ZIP file).
One is MAUI8+v200.3.0 (Map8 in the video).
One is MAUI7+v200.2.0 (Map7 in the video).

You clearly see the memory of the process qemu-system-x86_64.exe (Android Emulator) go up each time the loaded map gets displayed again by almost 1 GByte on the Map8 app.

On the Map7 app it almost stays the same (On the very top of the video you see the Task Manager with the process, CPU and Memory Usage):

All both apps are doing is creating a map and loading 44 KMZ files with a total of 10MByte. Here is the MainPage.xaml.cs:

 

using System;
using System.Threading.Tasks;
using Esri.ArcGISRuntime.Geometry;
using Esri.ArcGISRuntime.Mapping;
using Esri.ArcGISRuntime.Ogc;
using Microsoft.Maui.Controls;
using Map = Esri.ArcGISRuntime.Mapping.Map;

namespace ArcGISTry1;

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();

        // Create the UI, setup the control references and execute initialization
        _ = Initialize();
    }

#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
    private async Task Initialize()
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
    {
        // Set map to mapview
        MyMapView.Map = new Map(BasemapStyle.OSMLightGray)
        {
            InitialViewpoint = new Viewpoint(new MapPoint(11.406534, 47.719954, SpatialReferences.Wgs84), 6000)
        };

        MyMapView.Loaded += OnLoaded;
    }

    private async void OnLoaded(object? sender, EventArgs e)
    {
        for (var i = 0; i <= 43; i++) {
            var dataset = new KmlDataset(new Uri($"http://soko.yourweb.de/kmz/MemoryLeak/{i:00000}.kmz"));
            await dataset.LoadAsync();
            var kmlLayer = new KmlLayer(dataset);
            MyMapView.Map!.OperationalLayers.Add(kmlLayer);
        }
    }
}

 

Those KMZ files are from my production environment and get loaded like this every day by the users which currently use MAUI7.

Please look at this as soon as you can as I have to do another release in February and with this big bug its impossible to do so.

A workaround would help as well if no pre-release is possible until then.

As mentioned already: This is not an Emulator issue! Its just a good and easy way to show/see the problem. After switching pages on the emulator for 1-2 minutes the process used more than 64GByte on my machine.
On the real device (4 and 6 GByte) it only survies a couple a switches....

thanks

Soko

PS: on the Map7/MAUI7 app you have to move your finger over the map after switching back from the notes-page. This is a known bug in ArcGIS 200.2.0 (see my other post for this).

0 Kudos
25 Replies
dotMorten_esri
Esri Notable Contributor

I'll take a look, but a few things that come to mind:
- Are you seeing with with .NET8 + 200.2 ? (if so it's the .NET 8 upgrade, not 200.3 upgrade causing it)
- Careful will "Loaded". that'll fire each time the mapview re-appears (and this was actually a bug in net7 that it didn't consistently fire). It could just be you keep adding the 44(!!!) KML layers over and over again

 

0 Kudos
dotMorten_esri
Esri Notable Contributor

Don't have access to running on Android right now, but on Windows, I do see that happening and I'm almost certain that is the issue with your sample: 

dotMorten_esri_0-1705698912744.png

 

0 Kudos
dotMorten_esri
Esri Notable Contributor
0 Kudos
SokoFromNZ
Regular Contributor

Hey @dotMorten_esri 

and thanks so much for your quick reply.

You are correct. I was not aware of MAUI7 having the bug not calling Loaded-Event every time the page gets visible again (we discussed the bugyness of MAUI already in another ticket 😉 )...
Thanks for making me aware of this:

But (there is always a but) correcting my error cuts the memory leak only in half:

On the left you see my corrected source (which I confirmed to work via debugging)

Maybe not all your source code was adjusted to this new correct behavior of MAUI8?

Thanks again for your quick help

Soko

0 Kudos
SokoFromNZ
Regular Contributor

PS:

Changing my code to this (unloading and reloading the layers on each change) doesn't help. MemoryLeak is still the same as shown in the video above:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Esri.ArcGISRuntime.Geometry;
using Esri.ArcGISRuntime.Mapping;
using Esri.ArcGISRuntime.Ogc;
using Microsoft.Maui.Controls;
using Map = Esri.ArcGISRuntime.Mapping.Map;

namespace ArcGISTry1;

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();

        // Create the UI, setup the control references and execute initialization
        _ = Initialize();
    }

#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
    private async Task Initialize()
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
    {
        // Set map to mapview
        MyMapView.Map = new Map(BasemapStyle.OSMLightGray)
        {
            InitialViewpoint = new Viewpoint(new MapPoint(11.406534, 47.719954, SpatialReferences.Wgs84), 6000)
        };

        MyMapView.Loaded += OnLoaded;
        MyMapView.Unloaded += OnUnloaded;
    }

    private void OnUnloaded(object? sender, EventArgs e)
    {
        _layers.Clear();
        _layers.AddRange(MyMapView.Map!.OperationalLayers);
        MyMapView.Map!.OperationalLayers.Clear();
    }

    private async void OnLoaded(object? sender, EventArgs e)
    {
        if (_layers.Count == 0)
        {
            for (var i = 0; i <= 43; i++)
            {
                var dataset = new KmlDataset(new Uri($"http://soko.yourweb.de/kmz/MemoryLeak/{i:00000}.kmz"));
                await dataset.LoadAsync();
                var kmlLayer = new KmlLayer(dataset);
                MyMapView.Map!.OperationalLayers.Add(kmlLayer);
            }
        }
        else
        {
            MyMapView.Map!.OperationalLayers.AddRange(_layers);
        }
    }

    private readonly List<Layer> _layers = [];
}
0 Kudos
SokoFromNZ
Regular Contributor

PPS: Not even creating the whole layer from scratch helps. Always +500MByte per page switch:

 

...
    private void OnUnloaded(object? sender, EventArgs e){}

    private async void OnLoaded(object? sender, EventArgs e)
    {
        MyMapView.Map!.OperationalLayers.Clear();

        for (var i = 0; i <= 43; i++)
        {
            var dataset = new KmlDataset(new Uri($"http://soko.yourweb.de/kmz/MemoryLeak/{i:00000}.kmz"));
            await dataset.LoadAsync();
            var kmlLayer = new KmlLayer(dataset);
            MyMapView.Map!.OperationalLayers.Add(kmlLayer);
        }
    }
...

 

Also disabling the HttpResponseCache in CreateMauiApp() does not help:

ArcGISRuntimeEnvironment.Initialize(configuration => configuration.ConfigureHttp(httpConfiguration => httpConfiguration.UseResponseCache(false)));

Please help! I need a workaround here. I am out of ideas 😞 

Thx

Soko

0 Kudos
dotMorten_esri
Esri Notable Contributor

Why do you unload and reload the 44 layers every time the tab shows? You're slowing down map resume significantly, but you're also putting a huge amount of load on the garbage collector. So even with your above change, things don't actually get fully cleaned up until things are out of scope and the garbage collector had a chance to go through the generations.

Why not just add the KML layers when you create the map the first time, and call it good?

Not sure why you want to disable the response cache - that'll only make layers load even slower. No memory will be saved (on the contrary).

0 Kudos
SokoFromNZ
Regular Contributor

Hi,

I've made too many posts here, sorry for the confusion.

On the post with my 2nd video I did exactly what you suggest. Still +500MByte every time (see video).

On my PS and PPS posts I've tried to come up with a workaround for this memory leak - which I couldn't. Just wanted to let you what I've already tried so you don't have to.

To make things very easy I use this MainPage.xaml.cs now:

using System;
using System.Threading.Tasks;
using Esri.ArcGISRuntime.Geometry;
using Esri.ArcGISRuntime.Mapping;
using Esri.ArcGISRuntime.Ogc;
using Microsoft.Maui.Controls;
using Map = Esri.ArcGISRuntime.Mapping.Map;

namespace ArcGISTry1;

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
        _ = Initialize();
    }
    private async Task Initialize()
    {
        // Set map to mapview
        MyMapView.Map = new Map(BasemapStyle.OSMLightGray)
        {
            InitialViewpoint = new Viewpoint(new MapPoint(11.406534, 47.719954, SpatialReferences.Wgs84), 6000)
        };
        
        for (var i = 0; i <= 43; i++)
        {
            var dataset = new KmlDataset(new Uri($"http://soko.yourweb.de/kmz/MemoryLeak/{i:00000}.kmz"));
            await dataset.LoadAsync();
            var kmlLayer = new KmlLayer(dataset);
            MyMapView.Map!.OperationalLayers.Add(kmlLayer);
        }
    }
}

 

and I still get +500MByte each time I switch back to the map-page. 

So +500MByte of my initial post was my fault. But +500Mbyte is somewhere in your Runtime.

Thanks for your help.

Soko

0 Kudos
SokoFromNZ
Regular Contributor

@dotMorten_esri Where you able to reprocude this issue on your side or do you need more info from my side?

0 Kudos