Geben Sie Speicher frei, wenn Ihre App in den Hintergrund verschoben wird

Dieser Artikel beschreibt, wie Sie die Größe des Speichers reduzieren, den Ihre App verwendet, wenn sie in den Hintergrundzustand verschoben wird, so dass sie nicht angehalten und möglicherweise sogar beendet wird.

Neue Hintergrund-Ereignisse

Windows 10, Version 1607, führt zwei neue App-Lebenszyklusereignisse, EnteredBackground und LeavingBackground, ein. Diese Ereignisse ermöglichen Ihrer App zu wissen, wann sie in den Hintergrund verschoben wird bzw. diesen verlässt.

Wenn Ihre App in den Hintergrund verschoben wird, können sich die vom System erzwungen Speicherbeschränkungen ändern. Verwenden Sie diese Ereignisse, um den aktuellen Speicherverbrauch zu prüfen und Ressourcen freizugeben, um unterhalb des Grenzwerts zu bleiben, so dass Ihre App nicht angehalten und möglicherweise beendet wird, während sie sich im Hintergrund befindet.

Ereignisse zur Steuerung der Speichernutzung Ihrer App

MemoryManager.AppMemoryUsageLimitChanging wird ausgeführt, kurz bevor die Obergrenze des Speichers, den die App nutzen kann, erreicht ist. Zum Beispiel: Wenn die App in den Hintergrund verschoben wird und sich die Speichergrenze von 1024 MB zu 128 MB auf der Xbox ändert.
Dies ist das wichtigste Ereignis, um zu verhindern, dass die Plattform die App anhält oder beendet.

MemoryManager.AppMemoryUsageIncreased wird ausgeführt, wenn der Speicherverbrauch der App auf einen höheren Wert in der AppMemoryUsageLevel-Enumeration gestiegen ist. Z. B. von Low auf Medium. Die Verwendung dieses Ereignisses ist optional, wird jedoch empfohlen, da die Anwendung immer noch dafür verantwortlich ist, unter dem Grenzwert zu bleiben.

MemoryManager.AppMemoryUsageDecreased wird ausgeführt, wenn der Speicherverbrauch der App auf einen niedrigeren Wert in der AppMemoryUsageLevel-Enumeration gesunken ist. Z. B. von High auf Low. Die Verwendung dieses Ereignisses ist optional, es zeigt jedoch an, dass die Anwendung bei Bedarf weiteren Speicher zuweisen könnte.

Verarbeiten des Übergangs zwischen Vordergrund und Hintergrund

Wenn Ihre App vom Vordergrund in den Hintergrund wechselt, wird das EnteredBackground-Ereignis ausgelöst. Wechselt die App dann wieder in den Vordergrund, wird das LeavingBackground-Ereignis ausgelöst. Sie können Handler für diese Ereignisse registrieren, wenn Ihre App erstellt wird. In der Standardprojektvorlage geschieht dies im App-Klassenkonstruktor in „App.xaml.cs“.

Da die Ausführung im Hintergrund die Speicherressourcen belastet, die die App behalten kann, sollten Sie die App auch für das AppMemoryUsageIncreased-Ereignis und das AppMemoryUsageLimitChanging-Ereignis registrieren. Anhand dieser Ereignisse werden die aktuelle Speicherauslastung der App und der aktuelle Grenzwert überprüft. Die Handler für diese Ereignisse werden in den folgenden Beispielen erläutert. Weitere Informationen zum Anwendungslebenszyklus für UWP-Apps finden Sie unter App-Lebenszyklus.

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;
}

Wenn das EnteredBackground-Ereignis ausgelöst wird, legen Sie die Nachverfolgungsvariable fest, um anzugeben, dass die App momentan im Hintergrund ausgeführt wird. Dies ist nützlich, wenn Sie Code für die Reduzierung der Speichernutzung schreiben.

/// <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.
}

Wenn Ihre App in den Hintergrund wechselt, reduziert das System das Arbeitsspeicherlimit der App, um sicherzustellen, dass die aktuelle Vordergrund-App über genügend Ressourcen verfügt, um ein zufriedenstellendes Reaktionsverhalten der App zu gewährleisten.

Durch den AppMemoryUsageLimitChanging-Ereignishandler wird der App mitgeteilt, dass der ihr zugewiesene Arbeitsspeicher verringert wurde. Außerdem wird der neue Grenzwert dem Handler in den übergebenen Ereignisargumenten mitgeteilt. Vergleichen Sie die MemoryManager.AppMemoryUsage-Eigenschaft, die den aktuell von der App genutzten Arbeitsspeicher angibt, mit der NewLimit-Eigenschaft der Ereignisargumente, die den neuen Grenzwert angibt. Wenn die Speicherauslastung den Grenzwert überschreitet, müssen Sie die Speicherauslastung verringern.

In diesem Beispiel verwenden Sie zu diesem Zweck die Hilfsmethode ReduceMemoryUsage, die später in diesem Artikel beschrieben wird.

/// <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);
    }
}

Hinweis

Einige Gerätekonfigurationen lassen es zu, dass eine Anwendung den neuen Grenzwert für die Speicherauslastung überschreit, bis die Systemressourcen knapp werden. Dies gilt jedoch nicht für alle Gerätekonfigurationen. Insbesondere auf der Xbox werden Apps angehalten oder beendet, wenn sie ihre Speicherauslastung nicht innerhalb von 2 Sekunden an die neuen Grenzwerte anpassen. Die beste Erfahrung auf möglichst vielen Geräten erzielen Sie mit diesem Ereignis, durch das die Ressourcenverwendung innerhalb von zwei Sekunden nach Auslösen des Ereignisses unter den Grenzwert gesenkt wird.

Es ist möglich, dass zwar der Speicherbedarf Ihrer App aktuell unter dem Speicherlimit für Hintergrund-Apps liegt, wenn sie in den Hintergrund verschoben wird, sie jedoch ihre Speichernutzung mit der Zeit erhöht und sich auf den Grenzwert zubewegt. Mit dem Handler AppMemoryUsageIncreased können Sie die aktuelle Auslastung überprüfen und bei einem Anstieg bei Bedarf Arbeitsspeicher freigeben.

Überprüfen Sie, ob AppMemoryUsageLevel den Wert High oder OverLimit aufweist, und reduzieren Sie ggf. die Speicherauslastung. Der Prozess in diesem Beispiel wird von der Hilfsmethode ReduceMemoryUsage verarbeitet. Sie können auch das AppMemoryUsageDecreased-Ereignis abonnieren und überprüfen, ob sich die App unter dem Grenzwert befindet. Wenn dies der Fall ist, können Sie zusätzliche Ressourcen zuweisen.

/// <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);
    }
}

Sie können die Hilfsmethode ReduceMemoryUsage implementieren, um Arbeitsspeicher freizugeben, wenn die App den Auslastungsgrenzwert für Hintergrund-Apps überschreitet. Die Vorgehensweise bei der Freigabe von Arbeitsspeicher hängt von den Spezifikationen Ihrer App ab. Eine empfohlene Methode besteht darin, die Benutzeroberfläche und andere mit der App-Ansicht zusammenhängende Ressourcen freizugeben. Dazu stellen Sie sicher, dass Sie die App im Hintergrund ausführen; setzen Sie dann die Content-Eigenschaft des App-Fensters auf null, heben Sie die Registrierung der UI-Ereignishandler auf und entfernen Sie alle anderen Verweise, die Sie möglicherweise auf der Seite haben. Wenn Sie die Registrierung Ihrer UI-Ereignishandler nicht aufheben und andere Referenzen auf der Seite nicht löschen, kann die Seitenressource nicht freigegeben werden. Rufen Sie dann GC.Collect auf, um den freigegebenen Speicherplatz sofort freizugeben. In der Regel erzwingen Sie keine Garbage Collection, da das System sie für Sie übernimmt. In diesem speziellen Fall reduzieren wir die Menge des für diese Anwendung in Rechnung gestellten Arbeitsspeichers, da sie in den Hintergrund geht, um die Wahrscheinlichkeit zu verringern, dass das System feststellt, dass es die App beenden soll, um Arbeitsspeicher zurückzufordern.

/// <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();
}

Wenn der Fensterinhalt gesammelt wird, leitet jeder Frame seinen Trennungsprozess ein. Wenn sich unterhalb des Fensterinhalts im visuellen Objektbaum Seiten befinden, lösen diese ihr „Unload“-Ereignis aus. Seiten können erst vollständig aus dem Arbeitsspeicher gelöscht werden, wenn alle Seitenverweise entfernt wurden. Führen Sie im Unloaded-Rückruf die folgenden Schritte aus, um sicherzustellen, dass der Arbeitsspeicher schnell freigegeben wird:

  • Löschen Sie große Datenstrukturen auf der Seite, und legen Sie deren Wert auf null fest.
  • Heben Sie die Registrierung aller Ereignishandler, die über Rückrufmethoden verfügen, innerhalb der Seite auf. Registrieren Sie diese Rückrufe beim Aufrufen des Loaded-Ereignishandlers für die Seite. Das Loaded-Ereignis wird ausgelöst, nachdem die Benutzeroberfläche wiederhergestellt und die Seite der visuellen Objektstruktur hinzugefügt wurde.
  • Rufen Sie GC.Collect am Ende des Unloaded-Rückrufs auf, um schnell eine Garbage Collection für große Datenstrukturen durchzuführen, die Sie gerade auf null festgelegt haben. Auch hier erzwingen Sie in der Regel keine Garbage Collection, da das System sie für Sie übernimmt. In diesem speziellen Fall reduzieren wir die Menge des für diese Anwendung in Rechnung gestellten Arbeitsspeichers, da sie in den Hintergrund geht, um die Wahrscheinlichkeit zu verringern, dass das System feststellt, dass es die App beenden soll, um Arbeitsspeicher zurückzufordern.
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();
}

Im LeavingBackground-Ereignishandler sollten Sie die Nachverfolgungsvariable (isInBackgroundMode) festlegen, um anzugeben, dass die App nicht mehr im Hintergrund ausgeführt wird. Überprüfen Sie als Nächstes, ob der Content für das aktuelle Fenster null ist. Dies ist der Fall, wenn Sie Ihre App-Ansichten geschlossen haben, um bei der Ausführung im Hintergrund Arbeitsspeicher freizugeben. Wenn der Fensterinhalt null ist, bauen Sie die App-Ansicht neu auf. In diesem Beispiel wird der Fensterinhalt in der Hilfsmethode CreateRootFrame erstellt.

/// <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);
    }
}

Durch die CreateRootFrame-Hilfsmethode wird der Ansichtsinhalt Ihrer App neu erstellt. Der Code in dieser Methode ist beinahe identisch mit dem OnLaunched-Handlercode in der Standardprojektvorlage. Der einzige Unterschied besteht darin, dass der Launching-Handler den vorherigen Ausführungsstatus anhand der PreviousExecutionState-Eigenschaft von LaunchActivatedEventArgs ermittelt und die CreateRootFrame-Methode einfach den vorherigen, als Argument übergebenen Ausführungsstatus abruft. Um doppelten Code zu minimieren, können Sie den Standardcode für den Ereignishandler Launching umgestalten, so dass CreateRootFrame aufgerufen wird.

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);
    }
}

Richtlinien

Verschieben vom Vordergrund in den Hintergrund

Wenn eine App vom Vordergrund in den Hintergrund verschoben wird, sorgt das System für die App dafür, dass Ressourcen, die im Hintergrund nicht benötigt werden, freigegeben werden. Beispielsweise leert das UI-Framework zwischengespeicherte Texturen, und das Video-Subsystem gibt für die App Speicher frei. Eine App muss jedoch weiterhin sorgfältig ihre Speichernutzung überwachen, um zu vermeiden, dass sie vom System angehalten oder beendet wird.

Wenn eine App vom Vordergrund in den Hintergrund wechselt, erhält sie zuerst ein EnteredBackground-Ereignis und dann ein AppMemoryUsageLimitChanging-Ereignis.

  • Verwenden Sie das EnteredBackground-Ereignis, um UI-Ressourcen freizugeben, von denen Sie wissen, dass Ihre App sie im Hintergrund nicht benötigt. Beispielsweise könnten Sie das Cover-Bild für einen Song freigeben.
  • Verwenden Sie das AppMemoryUsageLimitChanging-Ereignis, um sicherzustellen, dass Ihre App weniger Speicher verbraucht als der neue Hintergrundgrenzwert vorgibt. Achten Sie darauf, Ressourcen freizugeben, wenn dies nicht der Fall ist. Wenn Sie dies nicht tun, kann Ihre App je nach der gerätespezifischen Richtlinie angehalten oder beendet werden.
  • Rufen Sie manuell den Garbage Collector auf, wenn Ihre App die neue Speichergrenze überschreitet, wenn das AppMemoryUsageLimitChanging-Ereignis auslöst.
  • Verwenden Sie das AppMemoryUsageIncreased-Ereignis für die weitere Überwachung der Speichernutzung Ihrer App im Hintergrund, wenn Sie erwarten, dass diese sich ändert. Wenn die AppMemoryUsageLevelHigh oder OverLimit ist, geben Sie Ressourcen frei.
  • Geben Sie eventuell UI-Ressourcen im AppMemoryUsageLimitChanging-Ereignishandler anstelle des EnteredBackground-Handlers frei, um die Leistung zu erhöhen. Verwenden Sie einen booleschen Wert in den Ereignishandlern EnteredBackground/LeavingBackground, um zu verfolgen, ob sich die App im Vordergrund oder im Hintergrund befindet. Wenn dann im Eventhandler AppMemoryUsageLimitChangingAppMemoryUsage den Grenzwert überschreitet und sich die App im Hintergrund befindet (nach dem booleschen Wert), können Sie UI-Ressourcen freigeben.
  • Führen Sie keine Langzeitaufgaben im EnteredBackground-Ereignis durch, da dies dazu führen kann, dass der Übergang zwischen Anwendungen für die Benutzer langsam vor sich geht.

Verschieben vom Hintergrund in den Vordergrund

Wenn eine App vom Hintergrund in den Vordergrund wechselt, erhält sie zuerst ein AppMemoryUsageLimitChanging-Ereignis und dann ein LeavingBackground-Ereignis.

  • Verwenden Sie das LeavingBackground-Ereignis, um UI-Ressourcen wiederherzustellen, die Ihre App verworfen hat, als Sie in den Hintergrund wechselte.
  • Abspielbeispiel für Hintergrundmedien - zeigt, wie Sie Speicher freigeben, wenn ihre App in den Hintergrund wechselt.
  • Diagnosetools - beobachten Sie mit den Diagnosetools die Garbage Collection-Ereignisse, und prüfen Sie, ob Ihre App Speicher in der erwarteten Weise freigibt.