Compartir a través de


Liberar memoria cuando la aplicación pasa a segundo plano

En este artículo se muestra cómo reducir la cantidad de memoria que usa la aplicación cuando se mueve al estado en segundo plano para que no se suspenda y posiblemente finalice.

Nuevos eventos en segundo plano

Windows 10, versión 1607, presenta dos nuevos eventos de ciclo de vida de la aplicación, EnteredBackground y LeavingBackground. Estos eventos permiten a la aplicación saber cuándo entra y sale del fondo.

Cuando la aplicación se mueve en segundo plano, es posible que cambien las restricciones de memoria que aplica el sistema. Usa estos eventos para comprobar el consumo de memoria actual y los recursos gratuitos con el fin de mantenerse por debajo del límite para que la aplicación no se suspenda y posiblemente finalice mientras está en segundo plano.

Eventos para controlar el uso de memoria de la aplicación

MemoryManager.AppMemoryUsageLimitChanging se genera justo antes de que se cambie el límite de memoria total que puede usar la aplicación. Por ejemplo, cuando la aplicación se mueve en segundo plano y en Xbox, el límite de memoria cambia de 1024 MB a 128 MB.
Este es el evento más importante que se debe controlar para evitar que la plataforma suspenda o termine la aplicación.

MemoryManager.AppMemoryUsageIncreased se genera cuando el consumo de memoria de la aplicación ha aumentado a un valor mayor en la enumeración AppMemoryUsageLevel . Por ejemplo, de Bajo a Medio. Controlar este evento es opcional, pero se recomienda porque la aplicación sigue siendo responsable de permanecer bajo el límite.

MemoryManager.AppMemoryUsageDecreased se genera cuando el consumo de memoria de la aplicación ha disminuido a un valor inferior en la enumeración AppMemoryUsageLevel . Por ejemplo, de Alto a Bajo. Controlar este evento es opcional, pero indica que la aplicación puede asignar memoria adicional si es necesario.

Control de la transición entre primer plano y segundo plano

Cuando la aplicación se mueve del primer plano al fondo, se genera el evento EnteredBackground. Cuando la aplicación vuelve al primer plano, se genera el evento LeavingBackground. Puede registrar controladores para estos eventos cuando se crea la aplicación. En la plantilla de proyecto predeterminada, esto se hace en el constructor de clase App en App.xaml.cs.

Dado que la ejecución en segundo plano reducirá los recursos de memoria que puede conservar la aplicación, también debes registrarte para los eventos AppMemoryUsageIncreased y AppMemoryUsageLimitChanging que puedes usar para comprobar el uso actual de la memoria de la aplicación y el límite actual. Los controladores de estos eventos se muestran en los ejemplos siguientes. Para obtener más información sobre el ciclo de vida de las aplicaciones para UWP, consulta Ciclo de vida de las aplicaciones.

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

Cuando se genere el evento EnteredBackground , establezca la variable de seguimiento para indicar que se está ejecutando actualmente en segundo plano. Esto será útil al escribir el código para reducir el uso de memoria.

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

Cuando la aplicación realiza la transición al fondo, el sistema reduce el límite de memoria de la aplicación para asegurarse de que la aplicación en primer plano actual tiene recursos suficientes para proporcionar una experiencia de usuario con capacidad de respuesta.

El controlador de eventos AppMemoryUsageLimitChanging permite a la aplicación saber que se ha reducido su memoria asignada y proporciona el nuevo límite en los argumentos de evento pasados al controlador. Compare la propiedad MemoryManager.AppMemoryUsage, que proporciona el uso actual de la aplicación, a la propiedad NewLimit de los argumentos de evento, que especifica el nuevo límite. Si el uso de memoria supera el límite, debe reducir el uso de memoria.

En este ejemplo, esto se hace en el método auxiliar ReduceMemoryUsage, que se define más adelante en este artículo.

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

Nota:

Algunas configuraciones de dispositivo permitirán que una aplicación continúe ejecutándose sobre el nuevo límite de memoria hasta que el sistema experimente la presión de los recursos y algunos no lo harán. En Xbox, en particular, las aplicaciones se suspenderán o finalizarán si no reducen la memoria a debajo de los nuevos límites en un plazo de 2 segundos. Esto significa que puede ofrecer la mejor experiencia en toda la gama más amplia de dispositivos mediante este evento para reducir el uso de recursos por debajo del límite en un plazo de 2 segundos a partir del evento que se está generando.

Es posible que, aunque el uso de memoria de la aplicación esté actualmente bajo el límite de memoria de las aplicaciones en segundo plano cuando realiza la transición por primera vez al fondo, puede aumentar su consumo de memoria con el tiempo y empezar a abordar el límite. El controlador appMemoryUsageIncreased ofrece una oportunidad para comprobar el uso actual cuando aumenta y, si es necesario, liberar memoria.

Compruebe si AppMemoryUsageLevel es High o OverLimit y, si es así, reduzca el uso de memoria. En este ejemplo se controla mediante el método auxiliar ReduceMemoryUsage. También puedes suscribirte al evento AppMemoryUsageDecreased , comprobar si la aplicación está por debajo del límite y, si es así, sabes que puedes asignar recursos adicionales.

/// <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 es un método auxiliar que puedes implementar para liberar memoria cuando la aplicación supere el límite de uso mientras se ejecuta en segundo plano. La forma de liberar memoria depende de los detalles de la aplicación, pero una manera recomendada de liberar memoria es eliminar la interfaz de usuario y los demás recursos asociados a la vista de la aplicación. Para ello, asegúrese de que se ejecuta en segundo plano y, a continuación, establezca la propiedad Content de la ventana null de la aplicación en y anule el registro de los controladores de eventos de la interfaz de usuario y quite las demás referencias que pueda tener en la página. Si no se anula el registro de los controladores de eventos de la interfaz de usuario y se borran otras referencias que es posible que tenga que tener en la página, se impedirá que se liberen los recursos de la página. A continuación, llame a GC. Recopile para reclamar la memoria libre inmediatamente. Normalmente no fuerza la recolección de elementos no utilizados porque el sistema se encargará de ello. En este caso específico, estamos reduciendo la cantidad de memoria cargada a esta aplicación a medida que entra en segundo plano para reducir la probabilidad de que el sistema determine que debe terminar la aplicación para reclamar memoria.

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

Cuando se recopila el contenido de la ventana, cada frame comienza su proceso de desconexión. Si hay Pages en el árbol de objetos visuales en el contenido de la ventana, estos comenzarán a desencadenar su evento Unloaded. Las páginas no se pueden borrar completamente de la memoria a menos que se quiten todas las referencias a ellas. En la devolución de llamada descargada, haga lo siguiente para asegurarse de que la memoria se libere rápidamente:

  • Borre y establezca las estructuras de datos grandes de la página nullen .
  • Anule el registro de todos los controladores de eventos que tienen métodos de devolución de llamada dentro de page. Asegúrese de registrar esas devoluciones de llamada durante el controlador de eventos cargado para la página. El evento Loaded se genera cuando se ha reconstituido la interfaz de usuario y la página se ha agregado al árbol de objetos visuales.
  • Llame GC.Collect al final de la devolución de llamada descargada para recopilar rápidamente cualquiera de las estructuras de datos grandes que acaba de establecer en null. De nuevo, normalmente no fuerza la recolección de elementos no utilizados porque el sistema se encargará de ello. En este caso específico, estamos reduciendo la cantidad de memoria cargada a esta aplicación a medida que entra en segundo plano para reducir la probabilidad de que el sistema determine que debe terminar la aplicación para reclamar memoria.
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();
}

En el controlador de eventos LeavingBackground , establezca la variable de seguimiento (isInBackgroundMode) para indicar que la aplicación ya no se está ejecutando en segundo plano. A continuación, compruebe si el contenido de la ventana actual es null, que será si ha eliminado las vistas de la aplicación para borrar la memoria mientras se estaba ejecutando en segundo plano. Si el contenido de la ventana es null, vuelva a generar la vista de la aplicación. En este ejemplo, el contenido de la ventana se crea en el método auxiliar 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);
    }
}

El método auxiliar CreateRootFrame vuelve a crear el contenido de la vista de la aplicación. El código de este método es casi idéntico al código del controlador OnLaunched proporcionado en la plantilla de proyecto predeterminada. La única diferencia es que el controlador Launch determina el estado de ejecución anterior de la propiedad PreviousExecutionState de LaunchActivatedEventArgs y el método CreateRootFrame simplemente obtiene el estado de ejecución anterior pasado como argumento. Para minimizar el código duplicado, puede refactorizar el código de controlador de eventos de inicio predeterminado para llamar a 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);
    }
}

Instrucciones

Pasar del primer plano al fondo

Cuando una aplicación pasa de primer plano a segundo plano, el sistema funciona en nombre de la aplicación para liberar recursos que no son necesarios en segundo plano. Por ejemplo, los marcos de interfaz de usuario vacían las texturas almacenadas en caché y el subsistema de vídeo libera memoria asignada en nombre de la aplicación. Sin embargo, una aplicación seguirá necesitando supervisar cuidadosamente su uso de memoria para evitar que el sistema lo suspenda o finalice.

Cuando una aplicación se mueve del primer plano al fondo, primero obtendrá un evento EnteredBackground y, a continuación, un evento AppMemoryUsageLimitChanging .

  • Usa el evento EnteredBackground para liberar recursos de interfaz de usuario que sepas que la aplicación no necesita mientras se ejecuta en segundo plano. Por ejemplo, podría liberar la imagen de arte de portada para una canción.
  • Use el evento AppMemoryUsageLimitChanging para asegurarse de que la aplicación usa menos memoria que el nuevo límite en segundo plano. Asegúrese de liberar recursos si no. Si no lo haces, es posible que la aplicación se suspenda o finalice según la directiva específica del dispositivo.
  • Invoque manualmente el recolector de elementos no utilizados si la aplicación supera el nuevo límite de memoria cuando se genera el evento AppMemoryUsageLimitChanging .
  • Usa el evento AppMemoryUsageIncreased para seguir supervisando el uso de memoria de la aplicación mientras se ejecuta en segundo plano si esperas que cambie. Si AppMemoryUsageLevel es High o OverLimit, asegúrese de liberar recursos.
  • Considere la posibilidad de liberar recursos de interfaz de usuario en el controlador de eventos AppMemoryUsageLimitChanging en lugar de en el controlador EnteredBackground como una optimización del rendimiento. Usa un valor booleano establecido en los controladores de eventos EnteredBackground/LeavingBackground para realizar un seguimiento de si la aplicación está en segundo plano o en primer plano. A continuación, en el controlador de eventos AppMemoryUsageLimitChanging , si AppMemoryUsage supera el límite y la aplicación está en segundo plano (según el valor booleano), puede liberar recursos de interfaz de usuario.
  • No realice operaciones de larga duración en el evento EnteredBackground porque puede hacer que la transición entre aplicaciones parezca lenta para el usuario.

Pasar del fondo al primer plano

Cuando una aplicación se mueve del fondo al primer plano, la aplicación obtendrá primero un evento AppMemoryUsageLimitChanging y, a continuación, un evento LeavingBackground .

  • Use el evento LeavingBackground para volver a crear los recursos de la interfaz de usuario descartados por la aplicación al pasar a segundo plano.
  • Ejemplo de reproducción multimedia en segundo plano: muestra cómo liberar memoria cuando la aplicación se mueve al estado de fondo.
  • Herramientas de diagnóstico: usa las herramientas de diagnóstico para observar eventos de recolección de elementos no utilizados y validar que la aplicación está liberando memoria de la manera esperada.