Libérer de la mémoire quand l’application bascule en arrière-plan

Cet article vous explique comment réduire la quantité de mémoire utilisée par votre application lorsqu’elle bascule en arrière-plan, afin qu’elle ne soit pas suspendue, voire arrêtée.

Nouveaux événements en arrière-plan

Windows 10, version 1607, introduit deux nouveaux événements de cycle de vie d’application, EnteredBackground et LeavingBackground. Ces événements indiquent à votre application à quel moment elle passe en arrière-plan et à quel moment elle revient au premier plan.

Lorsque votre application passe en arrière-plan, les contraintes de mémoire appliquées par le système peuvent changer. Utilisez ces événements pour vérifier votre consommation de mémoire actuelle et libérer des ressources afin de rester sous la limite autorisée et éviter que votre application ne soit suspendue, voire arrêtée, alors qu’elle se trouve en arrière-plan.

Événements permettant de contrôler l’utilisation de la mémoire de votre application

L’événement MemoryManager.AppMemoryUsageLimitChanging se déclenche juste avant que la limite de mémoire totale pouvant être utilisée par l’application soit modifiée. Par exemple, lorsque l’application passe en arrière-plan et sur la Xbox, la limite de mémoire passe de 1 024 Mo à 128 Mo.
Il s’agit du principal événement à gérer pour éviter que la plateforme suspende ou arrête l’application.

L’événement MemoryManager.AppMemoryUsageIncreased se déclenche lorsque le niveau de consommation de mémoire de l’application augmente dans l’énumération AppMemoryUsageLevel. Par exemple, si la consommation passe de Low à Medium. Facultative, la gestion de cet événement est toutefois recommandée, car l’application doit toujours rester sous la limite autorisée.

L’événement MemoryManager.AppMemoryUsageDecreased se déclenche lorsque le niveau de consommation de mémoire de l’application baisse dans l’énumération AppMemoryUsageLevel. Par exemple, si la consommation passe de High à Low. La gestion de cet événement est facultative mais indique que l’application est capable d’allouer davantage de mémoire si nécessaire.

Gérer la transition entre le premier plan et l’arrière-plan

Lorsque votre application passe du premier plan à l’arrière-plan, l’événement EnteredBackground est déclenché. Quand votre application revient au premier plan, l’événement LeavingBackground est déclenché. Vous pouvez enregistrer des gestionnaires pour ces événements au moment de la création de votre application. Dans le modèle de projet par défaut, l’enregistrement s’effectue au niveau du constructeur de la classe App, dans App.xaml.cs.

Étant donné que l’exécution en arrière-plan réduit les ressources de mémoire que votre application est autorisée à conserver, vous devez également vous inscrire aux événements AppMemoryUsageIncreased et AppMemoryUsageLimitChanging que vous pouvez utiliser pour case activée l’utilisation actuelle de la mémoire de votre application et la limite actuelle. Les gestionnaires de ces événements sont affichés dans les exemples suivants. Pour plus d’informations sur le cycle de vie des applications UWP, consultez la rubrique Cycle de vie de l’application.

public App()
{
    this.InitializeComponent();

    this.Suspending += OnSuspending;

    // Subscribe to key lifecyle events to know when the app
    // transitions to and from foreground and background.
    // Leaving the background is an important transition
    // because the app may need to restore UI.
    this.EnteredBackground += AppEnteredBackground;
    this.LeavingBackground += AppLeavingBackground;

    // During the transition from foreground to background the
    // memory limit allowed for the application changes. The application
    // has a short time to respond by bringing its memory usage
    // under the new limit.
    Windows.System.MemoryManager.AppMemoryUsageLimitChanging += MemoryManager_AppMemoryUsageLimitChanging;

    // After an application is backgrounded it is expected to stay
    // under a memory target to maintain priority to keep running.
    // Subscribe to the event that informs the app of this change.
    Windows.System.MemoryManager.AppMemoryUsageIncreased += MemoryManager_AppMemoryUsageIncreased;
}

Lorsque l’événement EnteredBackground est déclenché, définissez la variable de suivi pour indiquer que vous exécutez actuellement en arrière-plan. Ce procédé est utile lorsque vous écrivez le code permettant de réduire l’utilisation de la mémoire.

/// <summary>
/// The application entered the background.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void AppEnteredBackground(object sender, EnteredBackgroundEventArgs e)
{
    _isInBackgroundMode = true;

    // An application may wish to release views and view data
    // here since the UI is no longer visible.
    //
    // As a performance optimization, here we note instead that
    // the app has entered background mode with _isInBackgroundMode and
    // defer unloading views until AppMemoryUsageLimitChanging or
    // AppMemoryUsageIncreased is raised with an indication that
    // the application is under memory pressure.
}

Lorsque votre application passe en arrière-plan, le système réduit la limite de mémoire pour l’application afin de garantir que l’application qui s’exécute actuellement au premier plan dispose des ressources suffisantes pour fournir une expérience utilisateur réactive.

Le gestionnaire d’événements AppMemoryUsageLimitChanging permet à votre application de savoir que sa mémoire allouée a été réduite et fournit la nouvelle limite dans les arguments d’événement passés dans le gestionnaire. Comparez la propriété MemoryManager.AppMemoryUsage, qui fait état de l’utilisation actuelle de votre application, à la propriété NewLimit des arguments d’événement qui définit la nouvelle limite. Si votre utilisation de la mémoire dépasse la limite, vous devez la réduire.

Dans cet exemple, cette opération s’effectue dans la méthode d’assistance ReduceMemoryUsage, définie plus loin dans cet article.

/// <summary>
/// Raised when the memory limit for the app is changing, such as when the app
/// enters the background.
/// </summary>
/// <remarks>
/// If the app is using more than the new limit, it must reduce memory within 2 seconds
/// on some platforms in order to avoid being suspended or terminated.
///
/// While some platforms will allow the application
/// to continue running over the limit, reducing usage in the time
/// allotted will enable the best experience across the broadest range of devices.
/// </remarks>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MemoryManager_AppMemoryUsageLimitChanging(object sender, AppMemoryUsageLimitChangingEventArgs e)
{
    // If app memory usage is over the limit, reduce usage within 2 seconds
    // so that the system does not suspend the app
    if (MemoryManager.AppMemoryUsage >= e.NewLimit)
    {
        ReduceMemoryUsage(e.NewLimit);
    }
}

Notes

Certaines configurations d’appareil permettent à une application de continuer à s’exécuter au-delà de la nouvelle limite de mémoire jusqu’à ce que le système manque de ressources, mais ce n’est pas toujours le cas. Sur Xbox en particulier, les applications sont interrompues ou arrêtées si elles ne réduisent pas leur utilisation de mémoire en deçà de la nouvelle limite dans un délai de 2 secondes. Cela signifie que vous pouvez fournir la meilleure expérience sur un large éventail d’appareils en utilisant cet événement pour réduire l’utilisation des ressources en deçà de la limite dans les 2 secondes suivant le déclenchement de l’événement.

Même si l’utilisation de la mémoire de votre application est en dessous de la limite autorisée pour les applications en arrière-plan au moment où l’application passe en arrière-plan, il est possible que l’application augmente sa consommation de mémoire au fil du temps pour s’approcher de la limite autorisée. Le gestionnaire de l’événement AppMemoryUsageIncreased vous permet de savoir quand votre utilisation augmente, et de libérer de la mémoire le cas échéant.

Vérifiez si AppMemoryUsageLevel présente l’état High ou OverLimit. Le cas échéant, réduisez votre utilisation de la mémoire. Dans cet exemple, le processus est géré via la méthode d’assistance, ReduceMemoryUsage. Vous pouvez également vous abonner à l’événement AppMemoryUsageDecreased, case activée pour voir si votre application est sous la limite, et si c’est le cas, vous savez que vous pouvez allouer des ressources supplémentaires.

/// <summary>
/// Handle system notifications that the app has increased its
/// memory usage level compared to its current target.
/// </summary>
/// <remarks>
/// The app may have increased its usage or the app may have moved
/// to the background and the system lowered the target for the app
/// In either case, if the application wants to maintain its priority
/// to avoid being suspended before other apps, it may need to reduce
/// its memory usage.
///
/// This is not a replacement for handling AppMemoryUsageLimitChanging
/// which is critical to ensure the app immediately gets below the new
/// limit. However, once the app is allowed to continue running and
/// policy is applied, some apps may wish to continue monitoring
/// usage to ensure they remain below the limit.
/// </remarks>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MemoryManager_AppMemoryUsageIncreased(object sender, object e)
{
    // Obtain the current usage level
    var level = MemoryManager.AppMemoryUsageLevel;

    // Check the usage level to determine whether reducing memory is necessary.
    // Memory usage may have been fine when initially entering the background but
    // the app may have increased its memory usage since then and will need to trim back.
    if (level == AppMemoryUsageLevel.OverLimit || level == AppMemoryUsageLevel.High)
    {
        ReduceMemoryUsage(MemoryManager.AppMemoryUsageLimit);
    }
}

ReduceMemoryUsage est une méthode d’assistance que vous pouvez implémenter pour libérer de la mémoire lorsque votre application qui s’exécute en arrière-plan dépasse la limite d’utilisation autorisée. La manière dont vous libérez de la mémoire dépend des spécificités de votre application, mais nous vous recommandons entre autres de vous libérer de votre interface utilisateur et d’autres ressources associées à l’affichage de votre application. Pour ce faire, vérifiez que vous exécutez en arrière-plan, puis définissez la propriété Content de la fenêtre null de votre application sur et désinscrivez vos gestionnaires d’événements d’interface utilisateur et supprimez toutes les autres références que vous pourriez avoir à la page. Sans cela, vous ne pourrez pas libérer les ressources de la page. Appelez ensuite GC.Collect pour récupérer immédiatement la mémoire libérée. En règle générale, vous ne forcez pas le garbage collection, car le système s’en chargera pour vous. Dans ce cas spécifique, nous réduisons la quantité de mémoire facturée à cette application à mesure qu’elle passe en arrière-plan pour réduire la probabilité que le système détermine qu’il doit arrêter l’application pour récupérer de la mémoire.

/// <summary>
/// Reduces application memory usage.
/// </summary>
/// <remarks>
/// When the app enters the background, receives a memory limit changing
/// event, or receives a memory usage increased event, it can
/// can optionally unload cached data or even its view content in
/// order to reduce memory usage and the chance of being suspended.
///
/// This must be called from multiple event handlers because an application may already
/// be in a high memory usage state when entering the background, or it
/// may be in a low memory usage state with no need to unload resources yet
/// and only enter a higher state later.
/// </remarks>
public void ReduceMemoryUsage(ulong limit)
{
    // If the app has caches or other memory it can free, it should do so now.
    // << App can release memory here >>

    // Additionally, if the application is currently
    // in background mode and still has a view with content
    // then the view can be released to save memory and
    // can be recreated again later when leaving the background.
    if (isInBackgroundMode && Window.Current.Content != null)
    {
        // Some apps may wish to use this helper to explicitly disconnect
        // child references.
        // VisualTreeHelper.DisconnectChildrenRecursive(Window.Current.Content);

        // Clear the view content. Note that views should rely on
        // events like Page.Unloaded to further release resources.
        // Release event handlers in views since references can
        // prevent objects from being collected.
        Window.Current.Content = null;
    }

    // Run the GC to collect released resources.
    GC.Collect();
}

Lorsque le contenu de la fenêtre est collecté, chaque élément Frame démarre son processus de déconnexion. Si l’arborescence des objets visuels sous le contenu de la fenêtre comporte des éléments Pages, ces derniers commenceront à déclencher leur événement Unloaded. Les Pages ne peuvent pas être complètement effacées de la mémoire, sauf si l’ensemble des références associées sont supprimées. Dans le rappel Unloaded, procédez comme suit afin de garantir la libération rapide de la mémoire :

  • Effacez les grandes structures de données de votre Page, et définissez-les sur null.
  • Annulez l’enregistrement de l’ensemble des gestionnaires d’événements comprenant des méthodes de rappel dans la Page. Veillez à inscrire ces rappels durant le gestionnaire d’événement Loaded associé à la Page. L’événement Loaded est déclenché quand l’interface utilisateur a été reconstituée et que la Page a été ajoutée à l’arborescence des objets visuels.
  • Appelez GC.Collect à la fin du rappel Unloaded afin de procéder rapidement à un nettoyage de la mémoire des grandes structures de données que vous venez de définir sur null. Là encore, en règle générale, vous ne forcez pas le garbage collection, car le système s’en chargera pour vous. Dans ce cas spécifique, nous réduisons la quantité de mémoire facturée à cette application à mesure qu’elle passe en arrière-plan pour réduire la probabilité que le système détermine qu’il doit arrêter l’application pour récupérer de la mémoire.
private void MainPage_Unloaded(object sender, RoutedEventArgs e)
{
   // << free large data sructures and set them to null, here >>

   // Disconnect event handlers for this page so that the garbage
   // collector can free memory associated with the page
   Window.Current.Activated -= Current_Activated;
   GC.Collect();
}

Dans le gestionnaire d’événement LeavingBackground, définissez la variable de détection (isInBackgroundMode) pour indiquer que votre application ne s’exécute plus en arrière-plan. Ensuite, case activée pour voir si le contenu de la fenêtre active est null- ce qu’il sera si vous avez supprimé vos vues d’application afin d’effacer la mémoire pendant que vous étiez en arrière-plan. Si le contenu de la fenêtre est défini sur null, régénérez l’affichage de votre application. Dans cet exemple, le contenu de la fenêtre est créé dans la méthode d’assistance CreateRootFrame.

/// <summary>
/// The application is leaving the background.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void AppLeavingBackground(object sender, LeavingBackgroundEventArgs e)
{
    // Mark the transition out of the background state
    _isInBackgroundMode = false;

    // Restore view content if it was previously unloaded
    if (Window.Current.Content == null)
    {
        CreateRootFrame(ApplicationExecutionState.Running, string.Empty);
    }
}

La méthode d’assistance CreateRootFrame régénère le contenu d’affichage de votre application. Le code de cette méthode est presque identique au code du gestionnaire OnLaunched fourni dans le modèle de projet par défaut. La seule différence est que le gestionnaire Launching détermine l’état d’exécution précédent à partir de la propriété PreviousExecutionState de la classe LaunchActivatedEventArgs, alors que la méthode CreateRootFrame récupère simplement l’état précédent d’exécution transmis en tant qu’argument. Pour réduire le code dédupliqué, vous pouvez refactoriser le code du gestionnaire d’événement Launching par défaut afin d’appeler CreateRootFrame.

void CreateRootFrame(ApplicationExecutionState previousExecutionState, string arguments)
{
    Frame rootFrame = Window.Current.Content as Frame;

    // Do not repeat app initialization when the Window already has content,
    // just ensure that the window is active
    if (rootFrame == null)
    {
        // Create a Frame to act as the navigation context and navigate to the first page
        rootFrame = new Frame();

        // Set the default language
        rootFrame.Language = Windows.Globalization.ApplicationLanguages.Languages[0];

        rootFrame.NavigationFailed += OnNavigationFailed;

        if (previousExecutionState == ApplicationExecutionState.Terminated)
        {
            //TODO: Load state from previously suspended application
        }

        // Place the frame in the current Window
        Window.Current.Content = rootFrame;
    }

    if (rootFrame.Content == null)
    {
        // When the navigation stack isn't restored navigate to the first page,
        // configuring the new page by passing required information as a navigation
        // parameter
        rootFrame.Navigate(typeof(MainPage), arguments);
    }
}

Consignes

Passage du premier plan à l’arrière-plan

Lorsqu’une application passe du premier plan à l’arrière-plan, le système se charge de libérer les ressources dont l’application n’a pas besoin en arrière-plan. Par exemple, les infrastructures d’interface utilisateur vident les textures mises en cache et le sous-système vidéo libère la mémoire allouée pour le compte de l’application. Toutefois, une application devra toujours surveiller attentivement son utilisation de la mémoire pour ne pas être suspendue ou arrêtée par le système.

Lorsqu’une application passe du premier plan à l’arrière-plan, elle reçoit d’abord un événement EnteredBackground, puis un événement AppMemoryUsageLimitChanging.

  • Utilisez l’événement EnteredBackground pour libérer les ressources d’interface utilisateur dont votre application n’a pas besoin pour s’exécuter en arrière-plan. Vous pouvez par exemple supprimer l’image de pochette d’une chanson.
  • Utilisez l’événement AppMemoryUsageLimitChanging pour vous assurer que votre application utilise moins de mémoire que la nouvelle limite autorisée en arrière-plan. Si ce n’est pas le cas, assurez-vous de libérer des ressources. Si vous ne le faites pas, votre application peut être suspendue ou arrêtée selon la stratégie définie pour l’appareil.
  • Appelez manuellement le récupérateur de mémoire si votre application dépasse la nouvelle limite de mémoire lorsque l’événement AppMemoryUsageLimitChanging est déclenché.
  • Utilisez l’événement AppMemoryUsageIncreased pour continuer à surveiller l’utilisation de la mémoire de votre application lors de son exécution en arrière-plan si vous pensez qu’elle peut évoluer. Si le niveau AppMemoryUsageLevel est défini sur High ou OverLimit, veillez à libérer des ressources.
  • Pour optimiser les performances, libérez les ressources d’interface utilisateur dans le gestionnaire d’événement AppMemoryUsageLimitChanging plutôt que dans le gestionnaire EnteredBackground. Utilisez une valeur booléenne définie dans les gestionnaires d’événements EnteredBackground/LeavingBackground pour savoir si l’application s’exécute en arrière-plan ou au premier plan. Puis, dans le gestionnaire d’événement AppMemoryUsageLimitChanging, si la valeur AppMemoryUsage est au-dessus de la limite et que l’application s’exécute en arrière-plan (tel que défini par la valeur booléenne), vous pouvez libérer des ressources d’interface utilisateur.
  • N’effectuez pas d’opérations longues dans l’événement EnteredBackground afin de fournir à l’utilisateur une expérience de transition fluide entre les applications.

Passage de l’arrière-plan au premier plan

Lorsqu’une application passe de l’arrière-plan au premier plan, elle reçoit d’abord un événement AppMemoryUsageLimitChanging, puis un événement LeavingBackground.

  • Utilisez l’événement LeavingBackground pour recréer les ressources d’interface utilisateur que votre application a supprimées lors de son passage en arrière-plan.