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

Cet article vous montre comment réduire la quantité de mémoire utilisée par votre application lorsqu’elle passe à l’état d’arrière-plan afin qu’elle ne soit pas suspendue et éventuellement terminée.

Nouveaux événements d’arrière-plan

Windows 10, version 1607, introduit deux nouveaux événements de cycle de vie d’application, EnteredBackground et LeavingBackground. Ces événements permettent à votre application de savoir quand elle entre et quitte l’arrière-plan.

Lorsque votre application se déplace en arrière-plan, les contraintes de mémoire appliquées par le système peuvent changer. Utilisez ces événements pour vérifier la consommation actuelle de mémoire et les ressources gratuites afin de rester au-dessous de la limite afin que votre application ne soit pas suspendue et éventuellement arrêtée pendant qu’elle se trouve en arrière-plan.

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

MemoryManager.AppMemoryUsageLimitChanging est déclenché juste avant la modification de la limite de mémoire totale que l’application peut utiliser. Par exemple, lorsque l’application passe en arrière-plan et sur la Xbox, la limite de mémoire passe de 1024 Mo à 128 Mo.
Il s’agit de l’événement le plus important à gérer pour empêcher la plateforme de suspendre ou de mettre fin à l’application.

MemoryManager.AppMemoryUsageIncreased est déclenché lorsque la consommation de mémoire de l’application a augmenté à une valeur supérieure dans l’énumération AppMemoryUsageLevel . Par exemple, de Bas à Moyen. La gestion de cet événement est facultative, mais recommandée, car l’application est toujours responsable de rester sous la limite.

MemoryManager.AppMemoryUsageDecreased est déclenché lorsque la consommation de mémoire de l’application a diminué à une valeur inférieure dans l’énumération AppMemoryUsageLevel . Par exemple, de High à Low. La gestion de cet événement est facultative, mais indique que l’application peut être en mesure d’allouer de la mémoire supplémentaire 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é. Lorsque votre application revient au premier plan, l’événement LeavingBackground est déclenché. Vous pouvez inscrire des gestionnaires pour ces événements lorsque votre application est créée. Dans le modèle de projet par défaut, cela se fait dans le constructeur de 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 AppMemoryUsageUsageIncreased et AppMemoryUsageLimitChanging que vous pouvez utiliser pour vérifier l’utilisation actuelle de la mémoire de votre application et la limite actuelle. Les gestionnaires de ces événements sont présentés dans les exemples suivants. Pour plus d’informations sur le cycle de vie des applications UWP, consultez cycle de vie des applications UWP.

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 êtes en cours d’exécution en arrière-plan. Cela sera utile lorsque vous écrivez le code pour 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 de l’application pour s’assurer que l’application de premier plan actuelle dispose de 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 fournit l’utilisation actuelle de votre application, à la propriété NewLimit des arguments d’événement, qui spécifie la nouvelle limite. Si votre utilisation de la mémoire dépasse la limite, vous devez réduire votre utilisation de la mémoire.

Dans cet exemple, cette opération est effectuée dans la méthode d’assistance ReduceMemoryUsage, qui est 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);
    }
}

Remarque

Certaines configurations d’appareil permettent à une application de continuer à s’exécuter sur la nouvelle limite de mémoire jusqu’à ce que le système subit une pression sur les ressources, et certains ne le feront pas. Sur Xbox, en particulier, les applications seront suspendues ou arrêtées s’ils ne réduisent pas la mémoire aux nouvelles limites dans les 2 secondes. Cela signifie que vous pouvez offrir la meilleure expérience sur le plus large éventail d’appareils à l’aide de cet événement pour réduire l’utilisation des ressources en dessous de la limite de 2 secondes après l’événement déclenché.

Il est possible que même si l’utilisation de la mémoire de votre application est actuellement inférieure à la limite de mémoire pour les applications en arrière-plan lorsqu’elle passe d’abord en arrière-plan, elle peut augmenter sa consommation de mémoire au fil du temps et commencer à approcher la limite. Le gestionnaire AppMemoryUsageIncreased offre la possibilité de vérifier votre utilisation actuelle lorsqu’elle augmente et, si nécessaire, de libérer de la mémoire.

Vérifiez si AppMemoryUsageLevel est élevé ou OverLimit, et si c’est le cas, réduisez votre utilisation de la mémoire. Dans cet exemple, il s’agit de la méthode d’assistance, ReduceMemoryUsage. Vous pouvez également vous abonner à l’événement AppMemoryUsageDecreased , vérifier si votre application est en dessous de 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 dépasse la limite d’utilisation lors de l’exécution en arrière-plan. La façon dont vous relâchez la mémoire dépend des spécificités de votre application, mais l’une des méthodes recommandées pour libérer de la mémoire consiste à supprimer votre interface utilisateur et les autres ressources associées à la vue de votre application. Pour ce faire, vérifiez que vous êtes en cours d’exécution dans l’état en arrière-plan, puis définissez la propriété Contenu de la fenêtre null de votre application sur et annulez l’inscription de vos gestionnaires d’événements d’interface utilisateur et supprimez les autres références que vous devrez peut-être faire sur la page. L’échec de l’inscription de vos gestionnaires d’événements d’interface utilisateur et l’effacement des autres références que vous devrez peut-être faire sur la page empêche la publication des ressources de page. Appelez ensuite GC. Collectez 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 occupera pour vous. Dans ce cas précis, nous réduisant la quantité de mémoire chargée pour cette application, car elle passe en arrière-plan pour réduire la probabilité que le système détermine qu’il doit mettre fin à 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 frame commence son processus de déconnexion. S’il existe des pages dans l’arborescence d’objets visuels sous le contenu de la fenêtre, celles-ci commencent à déclencher leur événement Unloaded. Les pages ne peuvent pas être complètement effacées de la mémoire, sauf si toutes les références à celles-ci sont supprimées. Dans le rappel déchargé, procédez comme suit pour vous assurer que la mémoire est rapidement libérée :

  • Effacez et définissez toutes les structures de données volumineuses dans votre page nullsur .
  • Annulez l’inscription de tous les gestionnaires d’événements qui ont des méthodes de rappel dans la page. Veillez à inscrire ces rappels pendant le gestionnaire d’événements Loaded pour la page. L’événement Loaded est déclenché lorsque l’interface utilisateur a été rétablie et que la page a été ajoutée à l’arborescence d’objets visuels.
  • Appelez GC.Collect à la fin du rappel déchargé pour récupérer rapidement les grandes structures de données sur lesquelles vous venez de définir null. Là encore, en général, vous ne forcez pas le garbage collection, car le système s’en occupera pour vous. Dans ce cas précis, nous réduisant la quantité de mémoire chargée pour cette application, car elle passe en arrière-plan pour réduire la probabilité que le système détermine qu’il doit mettre fin à 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énements LeavingBackground , définissez la variable de suivi (isInBackgroundMode) pour indiquer que votre application n’est plus en cours d’exécution en arrière-plan. Ensuite, vérifiez si le contenu de la fenêtre active est null- qu’il sera si vous avez supprimé les affichages de votre application afin d’effacer la mémoire pendant l’exécution en arrière-plan. Si le contenu de la fenêtre est 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 recrée 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 launch détermine l’état d’exécution précédent de la propriété PreviousExecutionState de LaunchActivatedEventArgs et de la méthode CreateRootFrame obtient simplement l’état d’exécution précédent passé en tant qu’argument. Pour réduire le code dupliqué, vous pouvez refactoriser le code du gestionnaire d’événements de lancement par défaut pour 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 fonctionne au nom de l’application pour libérer des ressources qui ne sont pas nécessaires en arrière-plan. Par exemple, les frameworks 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 doit toujours surveiller soigneusement son utilisation de la mémoire pour éviter d’être suspendue ou arrêtée par le système.

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

  • Utilisez l’événement EnteredBackground pour libérer des ressources d’interface utilisateur dont vous savez que votre application n’a pas besoin lors de l’exécution en arrière-plan. Par exemple, vous pouvez libérer l’image de couverture pour une chanson.
  • Utilisez l’événement AppMemoryUsageLimitChanging pour vous assurer que votre application utilise moins de mémoire que la nouvelle limite d’arrière-plan. Veillez à libérer des ressources si ce n’est pas le cas. Si ce n’est pas le cas, votre application peut être suspendue ou arrêtée en fonction de la stratégie spécifique de l’appareil.
  • Appelez manuellement le garbage collector 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 en cours d’exécution en arrière-plan si vous vous attendez à ce qu’elle change. Si AppMemoryUsageLevel est Élevé ou OverLimit, veillez à libérer des ressources.
  • Envisagez de libérer des ressources d’interface utilisateur dans le gestionnaire d’événements AppMemoryUsageLimitChanging au lieu du gestionnaire EnteredBackground comme optimisation des performances. Utilisez un jeu de valeurs booléennes dans les gestionnaires d’événements EnteredBackground/LeavingBackground pour déterminer si l’application se trouve en arrière-plan ou au premier plan. Ensuite, dans le gestionnaire d’événements AppMemoryUsageLimitChanging , si AppMemoryUsage dépasse la limite et que l’application est en arrière-plan (en fonction de la valeur booléenne), vous pouvez libérer des ressources d’interface utilisateur.
  • N’effectuez pas d’opérations de longue durée dans l’événement EnteredBackground , car vous pouvez entraîner la transition entre les applications de manière à apparaître lente pour l’utilisateur.

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

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

  • Utilisez l’événement LeavingBackground pour recréer les ressources de l’interface utilisateur que votre application a ignorées lors du déplacement en arrière-plan.
  • Exemple de lecture multimédia en arrière-plan : montre comment libérer de la mémoire lorsque votre application passe à l’état d’arrière-plan.
  • Outils de diagnostic : utilisez les outils de diagnostic pour observer les événements de garbage collection et vérifier que votre application libère de la mémoire comme vous l’attendez.