在将应用移动到后台时释放内存Free memory when your app moves to the background

本文介绍了如何在将应用移至后台状态时减少应用使用的内存量,以便它不会暂停和终止。This article shows you how to reduce the amount of memory that your app uses when it moves to the background state so that it won't be suspended and possibly terminated.

新的后台事件New background events

Windows 10 版本 1607 引入了两个新的应用程序生命周期事件:EnteredBackgroundLeavingBackgroundWindows 10, version 1607, introduces two new application lifecycle events, EnteredBackground and LeavingBackground. 借助这些事件,应用将知道进入和退出后台的时间。These events let your app know when it is entering and leaving the background.

当应用进入后台时,由系统强制执行的内存约束可能会更改。When your app moves into the background, the memory constraints enforced by the system may change. 使用这些事件检查当前的内存占用,以便保持在限制以下,这样当应用在后台运行时,它将不会暂停和终止。Use these events to check your current memory consumption and free resources in order to stay below the limit so that your app won't be suspended and possibly terminated while it is in the background.

控制应用内存使用量的事件Events for controlling your app's memory usage

MemoryManager.AppMemoryUsageLimitChanging 仅在应用可以使用的总内存限制更改时引发。MemoryManager.AppMemoryUsageLimitChanging is raised just before the limit of total memory the app can use is changed. 例如,当应用进入后台,Xbox 上的内存限制从 1024MB 更改为 128MB 时。For example, when the app moves into the background and on the Xbox the memory limit changes from 1024MB to 128MB.
为防止平台暂停或终止应用,这是需要处理的最重要的事件。This is the most important event to handle to keep the platform from suspending or terminating the app.

当应用的内存占用增加到了 AppMemoryUsageLevel 枚举中的较高值时,将引发 MemoryManager.AppMemoryUsageIncreasedMemoryManager.AppMemoryUsageIncreased is raised when the app's memory consumption has increased to a higher value in the AppMemoryUsageLevel enumeration. 例如,从For example, from Low to Medium. 处理此事件是可选项,但建议这样做,因为应用程序仍负责保持在限制以下。Handling this event is optional but recommended because the application is still responsible for staying under the limit.

当应用的内存占用降低到了 AppMemoryUsageLevel 枚举中的较低值时,将引发 MemoryManager.AppMemoryUsageDecreasedMemoryManager.AppMemoryUsageDecreased is raised when the app's memory consumption has decreased to a lower value in the AppMemoryUsageLevel enumeration. 例如,从For example, from High to Low. 处理此事件是可选项,但指示应用程序可以根据需要分配额外的内存。Handling this event is optional but indicates the application may be able to allocate additional memory if needed.

处理前台和后台之间的转换Handle the transition between foreground and background

当应用从前台移至后台时,将引发 EnteredBackground 事件。When your app moves from the foreground to the background, the EnteredBackground event is raised. 当应用返回到前台时,将引发 LeavingBackground 事件。When your app returns to the foreground, the LeavingBackground event is raised. 可以在创建应用时为这些事件注册处理程序。You can register handlers for these events when your app is created. 在默认项目模板中,这将在 App.xaml.cs 的 App 类构造函数中完成。In the default project template, this is done in the App class constructor in App.xaml.cs.

由于在后台运行会减少允许应用保留的内存资源,因此还应该注册 AppMemoryUsageIncreasedAppMemoryUsageLimitChanging 事件,这两个事件可用于检查应用的当前内存使用量和当前限制。Because running in the background will reduce the memory resources your app is allowed to retain, you should also register for the AppMemoryUsageIncreased and AppMemoryUsageLimitChanging events which you can use to check your app's current memory usage and the current limit. 这些事件的处理程序如以下示例中所示。The handlers for these events are shown in the following examples. 有关 UWP 应用的应用程序生命周期的详细信息,请参阅应用生命周期For more information on the application lifecycle for UWP apps, see App lifecycle.

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

当引发 EnteredBackground 事件时,请设置跟踪变量以指示当前正在后台运行。When the EnteredBackground event is raised, set the tracking variable to indicate that you are currently running in the background. 当你编写代码来减少内存使用量时,这将非常有用。This will be useful when you write the code for reducing memory usage.

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

当应用过渡到后台时,系统会降低该应用的内存限制,以确保当前的前台应用具有足够的资源来提供响应迅速的用户体验When your app transitions to the background, the system reduces the memory limit for the app to ensure that the current foreground app has sufficient resources to provide a responsive user experience

AppMemoryUsageLimitChanging 事件处理程序使应用可以知道其分配的内存已减少,同时在传递给该处理程序的事件参数中提供新限制。The AppMemoryUsageLimitChanging event handler lets your app know that its allotted memory has been reduced and provides the new limit in the event arguments passed into the handler. MemoryManager.AppMemoryUsage 属性(提供应用的当前使用量)与事件参数的 NewLimit 属性(指定新限制)比较。Compare the MemoryManager.AppMemoryUsage property, which provides your app's current usage, to the NewLimit property of the event arguments, which specifies the new limit. 如果内存使用量超过该限制,则需要减少内存使用量。If your memory usage exceeds the limit, you need to reduce your memory usage.

在此示例中,是在帮助程序方法 ReduceMemoryUsage 中编写此代码,此方法将在后文中进行定义。In this example, this is done in the helper method ReduceMemoryUsage, which is defined later in this 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);
    }
}

备注

某些设备配置会允许应用程序在超出新内存限制的情况下继续运行,直到系统遇到资源压力,而有些设备则不允许。Some device configurations will allow an application to continue running over the new memory limit until the system experiences resource pressure, and some will not. 尤其是在 Xbox 上,如果应用在 2 秒内未将内存使用降低至新限制以下,应用将暂停或终止。On Xbox, in particular, apps will be suspended or terminated if they do not reduce memory to under the new limits within 2 seconds. 这意味着,通过使用此事件在引发该事件的 2 秒内将资源使用量降低至限制下,就可以在大部分设备上实现最佳体验。This means that you can deliver the best experience across the broadest range of devices by using this event to reduce resource usage below the limit within 2 seconds of the event being raised.

当应用首次过渡到后台时,尽管应用内存使用量当前可能低于后台应用的内存限制,但它的内存占用可能会随着时间的推移而增加,开始接近限制。It is possible that although your app's memory usage is currently under the memory limit for background apps when it first transitions to the background, it may increase its memory consumption over time and begin to approach the limit. 处理程序 AppMemoryUsageIncreased 使你有机会检查使用量增加时的当前使用量,并释放内存(如果需要)。The handler the AppMemoryUsageIncreased provides an opportunity to check your current usage when it increases and, if necessary, free memory.

查看 AppMemoryUsageLevel 是否为 HighOverLimit,如果是,请降低内存使用量。Check to see if the AppMemoryUsageLevel is High or OverLimit, and if so, reduce your memory usage. 在此示例中,这将由帮助程序方法 ReduceMemoryUsage 处理。In this example this is handled by the helper method, ReduceMemoryUsage. 还可以订阅 AppMemoryUsageDecreased 事件,查看应用是否低于限制,如果是,你便知道可以分配其他资源。You can also subscribe to the AppMemoryUsageDecreased event, check to see if your app is under the limit, and if so then you know you can allocate additional resources.

/// <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 是一个帮助程序方法,当应用超过在后台运行的使用量限制时,可实现该方法来释放内存。ReduceMemoryUsage is a helper method that you can implement to release memory when your app is over the usage limit while running in the background. 释放内存的方法视应用的具体情况而定,但用于释放内存的一个建议方法是释放 UI 以及与应用视图关联的其他资源。How you release memory depends on the specifics of your app, but one recommended way to free up memory is to dispose of your UI and the other resources associated with your app view. 为此,请确保正在后台状态下运行,然后将应用窗口的 Content 属性设置为 null、注销 UI 事件处理程序,并删除可能具有的对页面的任何其他引用。To do so, ensure that you are running in the background state then set the Content property of your app's window to null and unregister your UI event handlers and remove any other references you may have to the page. 如果未能注销 UI 事件处理程序和清除可能具有的对页面的任何其他引用,将阻止释放页面资源。Failing to unregister your UI event handlers and clearing any other references you may have to the page will prevent the page resources from being released. 然后,调用 GC.Collect 立即回收释放的内存。Then call GC.Collect to reclaim the freed up memory immediately. 通常,不用强制执行垃圾回收,因为系统会自行处理此操作。Typically you don't force garbage collection because the system will take care of it for you. 在此特定情况下,我们会减少用于此应用程序的内存量,因为它会进入后台,进而降低系统为回收内存而决定终止应用的可能性。In this specific case, we are reducing the amount of memory charged to this application as it goes into the background to reduce the likelihood that the system will determine that it should terminate the app to reclaim memory.

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

当窗口内容收集完成时,每个框架都开始其断开连接处理。When the window content is collected, each Frame begins its disconnection process. 如果窗口内容下的可视化对象树中存在页面,这些页面将开始引发其 Unloaded 事件。If there are Pages in the visual object tree under the window content, these will begin firing their Unloaded event. 除非已删除对页面的所有引用,否则无法从内存中彻底清除它们。Pages cannot be completely cleared from memory unless all references to them are removed. 在 Unloaded 回调中,执行以下操作以确保快速释放内存:In the Unloaded callback, do the following to ensure that memory is quickly freed:

  • 清除页面中任何较大的数据结构,并将它们设置为 nullClear and set any large data structures in your Page to null.
  • 注销页面内具有回调方法的所有事件处理程序。Unregister all event handlers that have callback methods within the Page. 确保在页面的 Loaded 事件处理程序期间注册这些回调。Make sure to register those callbacks during the Loaded event handler for the Page. 当 UI 已完成重建,并且页面已添加到可视化对象树时,将引发 Loaded 事件。The Loaded event is raised when the UI has been reconstituted and the Page has been added to the visual object tree.
  • 在 Unloaded 回调的末尾调用 GC.Collect,以便快速对刚设为 null 的任何大数据结构进行垃圾回收。Call GC.Collect at the end of the Unloaded callback to quickly garbage collect any of the large data structures you have just set to null. 同样,通常不用强制进行垃圾回收,因为系统会自行处理该操作。Again, typically you don't force garbage collection because the system will take care of it for you. 在此特定情况下,我们会减少用于此应用程序的内存量,因为它会进入后台,进而降低系统为回收内存而决定终止应用的可能性。In this specific case, we are reducing the amount of memory charged to this application as it goes into the background to reduce the likelihood that the system will determine that it should terminate the app to reclaim memory.
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();
}

LeavingBackground 事件处理程序中,设置跟踪变量 (isInBackgroundMode) 以指示应用已不在后台运行。In the LeavingBackground event handler, set the tracking variable (isInBackgroundMode) to indicate that your app is no longer running in the background. 接下来,查看当前窗口的 Content 是否为 null,如果已释放应用视图来清除内存(在后台运行时),它将为 null。Next, check to see if the Content of the current window is null-- which it will be if you disposed of your app views in order to clear up memory while you were running in the background. 如果窗口内容为 null,请重新生成应用视图。If the window content is null, rebuild your app view. 在此示例中,使用帮助程序方法 CreateRootFrame 创建窗口内容。In this example, the window content is created in the helper method 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);
    }
}

CreateRootFrame 帮助程序方法用于重新创建应用的视图内容。The CreateRootFrame helper method recreates the view content for your app. 此方法中的代码几乎默认项目模板中提供的 OnLaunched 处理程序代码完全相同。The code in this method is nearly identical to the OnLaunched handler code provided in the default project template. 一个区别是:Launching 处理程序确定 LaunchActivatedEventArgsPreviousExecutionState 属性中的先前执行状态,而 CreateRootFrame 方法只是获取作为参数传入的先前执行状态。The one difference is that the Launching handler determines the previous execution state from the PreviousExecutionState property of the LaunchActivatedEventArgs and the CreateRootFrame method simply gets the previous execution state passed in as an argument. 若要最大程度地减少重复代码,可以重构默认的 Launching 事件处理程序代码以调用 CreateRootFrameTo minimize duplicated code, you can refactor the default Launching event handler code to call 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);
    }
}

指南Guidelines

从前台移动到后台Moving from the foreground to the background

当应用从前台移动到后台时,系统将代表应用工作,来释放在后台不需要使用的资源。When an app moves from the foreground to the background, the system does work on behalf of the app to free up resources that are not needed in the background. 例如,UI 框架会刷新缓存的纹理,并且视频子系统会代表应用释放分配的内存。For example, the UI frameworks flush cached textures and the video subsystem frees memory allocated on behalf of the app. 但是,应用将仍然需要仔细监视其内存使用量,以避免被系统暂停或终止。However, an app will still need to carefully monitor its memory usage to avoid being suspended or terminated by the system.

当应用从前台移动到后台时,它将先获取 EnteredBackground 事件,然后获取 AppMemoryUsageLimitChanging 事件。When an app moves from the foreground to the background it will first get an EnteredBackground event and then a AppMemoryUsageLimitChanging event.

  • 使用****EnteredBackground 事件,以释放所知道的应用(在后台运行时)不需要的 UI 资源。Do use the EnteredBackground event to free up UI resources that you know your app does not need while running in the background. 例如,可以释放某首歌曲的封面画面图像。For example, you could free the cover art image for a song.
  • 使用****AppMemoryUsageLimitChanging 事件,以确保应用使用比新后台限制更少的内存。Do use the AppMemoryUsageLimitChanging event to ensure that your app is using less memory than the new background limit. 如果不是,请确保释放资源。Make sure that you free up resources if not. 如果不这样做,根据设备特定的策略,应用可能会暂停或终止。If you do not, your app may be suspended or terminated according to device specific policy.
  • AppMemoryUsageLimitChanging 事件引发时,如果应用超出新的内存限制,手动调用垃圾回收器。Do manually invoke the garbage collector if your app is over the new memory limit when the AppMemoryUsageLimitChanging event is raised.
  • 使用****AppMemoryUsageIncreased 事件,以在应用在后台运行时继续监视应用的内存使用量(如果预计会出现变化)。Do use the AppMemoryUsageIncreased event to continue to monitor your app’s memory usage while running in the background if you expect it to change. 如果 AppMemoryUsageLevelHighOverLimit,请确保释放资源。If the AppMemoryUsageLevel is High or OverLimit make sure that you free up resources.
  • 作为一种性能优化,请考虑AppMemoryUsageLimitChanging 事件处理程序中释放 UI 资源,而不是在 EnteredBackground 处理程序中释放。Consider freeing UI resources in the AppMemoryUsageLimitChanging event handler instead of in the EnteredBackground handler as a performance optimization. 使用 EnteredBackground/LeavingBackground 事件处理程序中设定的布尔值,来跟踪应用是在后台还是在前台运行。Use a boolean value set in the EnteredBackground/LeavingBackground event handlers to track whether the app is in the background or foreground. 然后在 AppMemoryUsageLimitChanging 事件处理程序中,如果 AppMemoryUsage 超出限制并且应用在后台运行(基于布尔值),则可以释放 UI 资源。Then in the AppMemoryUsageLimitChanging event handler, if AppMemoryUsage is over the limit and the app is in the background (based on the Boolean value) you can free UI resources.
  • 不要EnteredBackground 事件中执行长时间运行的操作,因为可能会导致用户感觉应用程序之间的过渡较慢。Do not perform long running operations in the EnteredBackground event because you can cause the transition between applications to appear slow to the user.

从后台移动到前台Moving from the background to the foreground

当应用从后台移动到前台时,应用将先获取 AppMemoryUsageLimitChanging 事件,然后获取 LeavingBackground 事件。When an app moves from the background to the foreground, the app will first get an AppMemoryUsageLimitChanging event and then a LeavingBackground event.

  • 使用****LeavingBackground 事件,重新创建应用在进入后台时丢弃的 UI 资源。Do use the LeavingBackground event to recreate UI resources that your app discarded when moving into the background.
  • 后台媒体播放示例 - 介绍了如何在将应用移动到后台状态时释放内存。Background media playback sample - shows how to free memory when your app moves to the background state.
  • 诊断工具 - 使用诊断工具,可以观察垃圾回收事件,并验证应用是否按预期方式释放内存。Diagnostic Tools - use the diagnostic tools to observe garbage collection events and validate that your app is releasing memory the way you expect it to.