Skip to content

Performance

Håvard Moås edited this page Jan 23, 2024 · 18 revisions

As with any application people, performance is key. When people are using a mobile application, they normally perform tasks on the go and they depend on their application to stay connected. People might start discarding a poor performing application, especially if they depend on it to help them in situations where they don't have much time to react. DIPS creates applications for the Norwegian Health Care, where the user group is people in situations like this. We want to provide some a helping hand in focusing on performance with this wiki page.

All examples need needs you to set DUI.IsDebug=true; this should be done with by you in MauiProgram with an #if DEBUG. Please see Getting Started for a complete example.

Page loading

The time it takes for your page to start reacting is essential for the performance of a mobile application. You do not want to end up with people having to wait for your pages to render while they navigate, which do feel like the application is "freezing" while navigating.

Profiling page loading

In order for you to do profiling of your pages loading, we have added a ShouldLogLoadingTime property that can be used if you are using ContentPage . This will log the time it took from the page was constructed to the page was loaded. This will most likely highlight the needs for "lazy loading" of views or choosing the correct component for your page. We hope it will be helpful!

Memory Leaks

Memory leaks are essential to get rid of for the application to perform at its best. MAUI has provided a Wiki page with information regarding memory leaks, to help you better understand memory leaks. In addition to this we have provided a set of helping methods in DIPS.Mobile.UI.

Monitoring Garbage Collection

DIPS.Mobile.UI provides multiple ways for you to check if an object was garbage collected. An objects finalizer will run when an objects gets collected by the garbage collector. Garbage collections happens at different times during the application lifecycle, but you can force garbage collecting. This is recommended to only do while debugging.

Finalizers logging

Both ContentPage and a ViewModel can log when its finalizer has ran. To opt-in, use the ShouldLogWhenGarbageCollected property.

GarbageCollection

We have added GarbageCollection that wraps the GC from .NET. Use this to force collection and wait for finalizers.

ContentPage

ContentPage can monitor garbage collection when navigated to. To opt-in, set the ShouldGarbageCollectAndLogWhenNavigatedTo for a page of your choice. This will print the current Garbage Collection total memory. If you have set the ShouldLogWhenGarbageCollected for other pages that people can navigate out of, they should log. If they do not, they are zombies and live forever.

Shell

Shell has the ability to garbage collect when a Pop, PopToRoot or Remove has been initiated from people or programatically. This will monitor the page that people navigated to from the a page, and try to garbage collect and monitor it. It will print if was garbage collected, or not. To opt in, use the ShouldGarbageCollectPreviousPage.

GCCollectionMonitor

If you want to monitor a specific object when it should be garbage collected (like when an object in a page should have been destroyed), use the GCCollectionMonitor class.

Here is an example of when NavigationPage runs Pop and you want to monitor the previous page.

private void NavPageOnPopped(object? sender, NavigationEventArgs e)
{
    m_monitor.Observe(e.Page);
    m_monitor.CheckAliveness();
}

Clean up

To clean up your views, you need to subscribe to events from the framework that will let you do that. This section describes the possibilities of using UnLoaded events.

ContentPage

Use UnLoaded to clean up memory leaks. Most commonly is to unsubscribe to events and to make sure you do not have a reference to an object that lives longer than the page.

VisualElement

There is no easy way for you to clean up for views and handlers compared to pages. There is a issue in .NET MAUI that hopefully will make the developer experience easier for you: https://github.com/dotnet/maui/issues/16332

Due to the different times UnLoaded runs for the platforms, we have to mention Loaded as well.

Here is a summary of when the events are invoked:

Loaded is invoked when:

  • The page is loaded the first time. (Both platforms)
  • When the element is visible in a CollectionView. (Both platforms)
  • Hot Reloading. (Both platforms)
  • The page was navigated back to. (iOS)
  • The page is navigating to a new page (iOS)
    • UnLoaded is invoked after.

UnLoaded is invoked when:

  • The page was navigated out of. (Both platforms)
  • Invoked 2x times when the page was navigated to a new page. (iOS)
  • When the element disappears from a CollectionView. (Both Platforms)

This makes Loaded and UnLoaded potential events to subscribe to events/add observers and to remove them. But we recommend that you test this with the monitoring and to test the view thoroughly as the events has different invocations for the platforms, which can lead to bugs.

When working with VisualElement

When working with VisualElements, or more commonly ContentView, use the Loaded and UnLoaded events to subscribe and unsubscribe.

public MyView()
{
    Loaded += Load;
    Unloaded += UnLoad;
}



private void Load(object? sender, EventArgs e)
{
    //Subscribe to whatever
}

private void UnLoad(object? sender, EventArgs e)
{
    //Unsubsribe to whatever
    //Clean up resources
}

No need to unsubscribe to Loaded and UnLoaded as these will not give you a memory leak.

When working with Handler

  1. Follow the "When working with VisualElement approach for your virtual view.
  2. Create Connect() and Disconnect() partial methods in your handler. Implement them in the platforms.
  3. Delegate Connect() and Disconnect() to the handler for Loaded and UnLoaded events.
public MyView()
{
    Loaded += Load;
    Unloaded += UnLoad;
}



private void Load(object? sender, EventArgs e)
{
    if (Handler is MyViewHandler myViewHandler)
    {
        myViewHandler.Connect();
    }
}

private void UnLoad(object? sender, EventArgs e)
{
    if (Handler is MyViewHandler myViewHandler)
    {
        myViewHandler.Disconnect();
    }
}

Shared handler:

public partial class MyViewHandler : ContentViewHandler
{
    internal partial void Connect();
    internal partial void Disconnect();

}

Platform handler:

public partial class MyViewHandler
{
    internal partial void Connect()
    {
        //Subscribe to whatever
    }
    
    internal partial void Disconnect()
    {
        //Unsubsribe to whatever
        //Clean up resources
    }
}
Clone this wiki locally