Administra los cambios en la configuración

Organiza tus páginas con colecciones Guarda y categoriza el contenido según tus preferencias.

Algunas configuraciones del dispositivo pueden cambiar mientras se ejecuta la app. Entre algunos ejemplos, se incluyen los siguientes:

  • Tamaño de la pantalla de la app
  • Orientación de la pantalla
  • Tamaño y grosor de la fuente
  • Configuración regional
  • Diferencias entre el modo oscuro y el modo claro
  • Disponibilidad del teclado

La mayoría de estos cambios en la configuración se deben a algunas interacciones del usuario. Por ejemplo, cuando se rota o pliega el dispositivo, cambia la cantidad de espacio de pantalla disponible para tu app. Del mismo modo, modificar los parámetros de configuración del dispositivo, como el tamaño de la fuente, el idioma o el tema preferido, cambia sus valores respectivos en el objeto Configuration.

Estos parámetros suelen requerir cambios lo suficientemente grandes en la IU de tu app como para que la plataforma de Android tenga un mecanismo específico destinado a ellos. Este mecanismo es la Recreación de actividades.

Recreación de actividades

El sistema vuelve a crear una actividad cuando se produce un cambio de configuración. El sistema llama a onDestroy() y destruye la instancia de actividad existente. Luego, crea una instancia nueva con onCreate(). Esta instancia de actividad nueva se inicializa con la configuración nueva y actualizada. Esto también significa que el sistema vuelve a crear la IU con la nueva configuración.

El comportamiento de recreación ayuda a tu app a adaptarse a las nuevas configuraciones al volver a cargarse de forma automática con recursos alternativos que coinciden con la nueva configuración del dispositivo.

Ejemplo de recreación

Considera un objeto TextView que muestra un título estático con android:text="@string/title", como se define en un archivo en formato XML de diseño. Cuando se crea la vista, el texto se configura exactamente una vez, según el idioma actual. Si el idioma cambia, el sistema vuelve a crear la actividad. En consecuencia, el sistema también vuelve a crear la vista y la inicializa con el valor correcto según el lenguaje nuevo.

La recreación también borra cualquier estado que hayas conservado como campos en la actividad o en cualquiera de sus Fragments, Views y otros objetos contenidos. Esto se debe a que la recreación de actividades crea una instancia completamente nueva de la actividad y la IU. Además, la actividad anterior ya no es visible ni válida, por lo que cualquier referencia restante a ella o a sus objetos contenidos estará inactiva. Pueden provocar errores, fugas de memoria y fallas.

Expectativas de los usuarios

El usuario de una app espera que se conserve el estado. Cuando un usuario completa un formulario y abre otra app en el modo multiventana para consultar información, la experiencia del usuario no será buena si regresa y los datos del formulario se borraron o si regresa a otra parte de la app. Debes garantizar una experiencia coherente a través de los cambios de configuración y la recreación de actividades.

Para verificar si el estado se preserva en tu app, puedes realizar acciones que provocan cambios de configuración mientras la app está en primer plano y cuando está en segundo plano. Estas acciones incluyen lo siguiente:

  • Rotar el dispositivo
  • Ingresar al modo multiventana
  • Cambiar el tamaño de la app en el modo multiventana o en una ventana de formato libre
  • Plegar un dispositivo plegable con varias pantallas
  • Cambiar el tema del sistema, como del modo oscuro al modo claro
  • Cambiar el tamaño de fuente
  • Cambiar el idioma del sistema o de la app
  • Conectar o desconectar un teclado de hardware
  • Conectar o desconectar un conector

Existen 3 enfoques principales que puedes adoptar para preservar el estado relevante con la recreación de la actividad. Lo que es relevante depende del tipo de estado que desees preservar:

  • La persistencia local para controlar el cierre del proceso para datos complejos o grandes. El almacenamiento local persistente incluye bases de datos o DataStore.
  • Objetos retenidos, como ViewModels, para controlar el estado relacionado con la IU en la memoria mientras el usuario usa la app de forma activa.
  • Estado de la instancia guardada para controlar el cierre del proceso iniciado por el sistema y mantener el estado transitorio que depende de las entradas del usuario o de la navegación.

En la página Cómo guardar estados de la IU, se describen en detalle las APIs de cada una de ellas y cuándo es apropiado usarlas.

Cómo restringir la recreación de actividades

Puedes evitar la recreación de actividades automática para ciertos cambios de configuración. La recreación de actividad genera la recreación de la IU completa y de cualquier objeto derivado de esta. Tal vez tengas buenas razones para no hacerlo. Por ejemplo, es posible que tu app no necesite actualizar los recursos durante un cambio de configuración específico o que tengas una limitación de rendimiento. En ese caso, puedes declarar que tu actividad maneja el cambio de configuración por sí misma y evitar que el sistema la reinicie.

Puedes inhabilitar la recreación de actividades para determinados cambios de configuración. Para ello, agrega el tipo de configuración a android:configChanges en la entrada <activity> de AndroidManifest.xml. Los valores posibles aparecen en la documentación del atributo android:configChanges.

El siguiente código de manifiesto inhabilita la recreación de actividades de MyActivity cuando cambia la orientación de la pantalla y la disponibilidad del teclado:

<activity
    android:name=".MyActivity"
    android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
    android:label="@string/app_name">

Algunos cambios de configuración siempre hacen que la actividad se reinicie. No puedes inhabilitarlos. Por ejemplo, no puedes inhabilitar el cambio de colores dinámico que se introdujo en el nivel de API 32. Otros cambios de configuración podrían comportarse de manera similar en el futuro.

Cómo reaccionar a los cambios de configuración en el sistema de View

En el sistema de View, cuando se produce un cambio de configuración en el que se inhabilitó la recreación de actividades, esta recibe una llamada a Activity.onConfigurationChanged(). Las vistas adjuntas también reciben una llamada a View.onConfigurationChanged(). En el caso de los cambios de configuración que no hayas agregado a android:configChanges, el sistema volverá a crear la actividad como de costumbre.

El método de devolución de llamada onConfigurationChanged() recibe un objeto Configuration que especifica la nueva configuración del dispositivo. Lee los campos en el objeto Configuration para determinar cuál debe ser tu configuración nueva. Para realizar los cambios posteriores, actualiza los recursos que usas en tu interfaz. Cuando el sistema llama a este método, el objeto Resources de tu actividad se actualiza para mostrar recursos basados en la nueva configuración. Esto te permite restablecer elementos de la IU sin que el sistema reinicie la actividad.

Por ejemplo, la siguiente implementación de onConfigurationChanged() verifica si hay algún teclado disponible:

Kotlin

override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)

    // Checks whether any keyboard is available
    if (newConfig.keyboardHidden === Configuration.KEYBOARDHIDDEN_YES) {
        Toast.makeText(this, "Keyboard available", Toast.LENGTH_SHORT).show()
    } else if (newConfig.keyboardHidden === Configuration.KEYBOARDHIDDEN_NO) {
        Toast.makeText(this, "No keyboard", Toast.LENGTH_SHORT).show()
    }
}

Java

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    // Checks whether any keyboard is available
    if (newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_YES) {
        Toast.makeText(this, "Keyboard available", Toast.LENGTH_SHORT).show();
    } else if (newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO){
        Toast.makeText(this, "No keyboard", Toast.LENGTH_SHORT).show();
    }
}

Si no necesitas actualizar tu aplicación según estos cambios de configuración, como alternativa, puedes optar por no implementar onConfigurationChanged(). En este caso, todos los recursos utilizados antes del cambio de configuración se siguen utilizando, y tú solamente evitaste el reinicio de tu actividad. Por ejemplo, es posible que una app para TV no reaccione cuando se conecta o desconecta un teclado Bluetooth.

Estado de retención

Cuando usas esta técnica, debes conservar el estado durante el ciclo de vida normal de la actividad. Esto se debe a los siguientes motivos:

  • Cambios inevitables: Los cambios de configuración que no puedes evitar pueden reiniciar tu aplicación.
  • Cierre del proceso: Tu aplicación debería poder controlar el cierre del proceso de inicio del sistema. Si el usuario abandona tu aplicación y esta pasa a segundo plano, es posible que el sistema la destruya.

Cómo reaccionar a los cambios de configuración en Jetpack Compose

Jetpack Compose permite que tu app reaccione con mayor facilidad ante los cambios de configuración. Sin embargo, si inhabilitas la recreación de actividades para todos los cambios de configuración en los que sea posible, debes asegurarte de que tu app maneje correctamente los cambios de configuración.

El objeto Configuration está disponible en la jerarquía de la IU de Compose con el CompositionLocal de LocalConfiguration. Cada vez que cambia, se vuelven a componer las funciones de componibilidad que leen de LocalConfiguration.current. Si quieres obtener información acerca del funcionamiento de los CompositionLocals, consulta la documentación sobre datos de alcance local con CompositionLocal.

Ejemplo

En el siguiente ejemplo, un elemento componible muestra una fecha con un formato específico. Este objeto componible reacciona a los cambios de configuración regional del sistema llamando a ConfigurationCompat.getLocales() con LocalConfiguration.current.

@Composable
fun DateText(year: Int, dayOfYear: Int) {
    val dateTimeFormatter = DateTimeFormatter.ofPattern(
        "MMM dd",
        ConfigurationCompat.getLocales(LocalConfiguration.current)[0]
    )
    Text(
        dateTimeFormatter.format(LocalDate.ofYearDay(year, dayOfYear))
    )
}

Para evitar la recreación de actividades cuando cambia la configuración regional, la actividad que aloja el código de Compose debe inhabilitar los cambios de configuración regional. Para hacerlo, debes configurar android:configChanges como locale|layoutDirection.

Ideas claves y conceptos sobre los cambios de configuración

En resumen, debes saber lo siguiente cuando realices cambios en la configuración:

  • Configuraciones: Las configuraciones del dispositivo definen cómo debe mostrarse la IU al usuario; por ejemplo, el tamaño de pantalla de la app, la configuración regional o el tema del sistema.
  • Cambios en la configuración: Las configuraciones cambian debido a la interacción del usuario. Por ejemplo, el usuario puede cambiar la configuración del dispositivo o cómo interactúa físicamente con él. La configuración cambiará. No hay forma de "evitar" los cambios en la configuración.
  • Recreación de actividad: Como resultado de los cambios en la configuración, se recrean las actividades de forma predeterminada. Este es un mecanismo integrado para volver a inicializar el estado de la app de la configuración nueva.
  • Destrucción de la actividad: La recreación de actividades hace que el sistema destruya la instancia de actividad anterior y cree una nueva en su lugar. La instancia anterior ahora es obsoleta y cualquier referencia a ella provocará fugas de memoria, errores o fallas.
  • Estado: El estado de la instancia de actividad anterior no está presente en la instancia de actividad nueva, ya que son dos instancias de objeto diferentes. Conserva la app y el estado del usuario, como se describe en Cómo guardar estados de la IU.
  • Inhabilitación: Inhabilitar la recreación de actividades para un tipo de cambio de configuración es una posible optimización. Para ello, debes asegurarte de que tu app se actualice de forma adecuada en respuesta a la nueva configuración.

Prácticas recomendadas

Para proporcionar una buena experiencia del usuario, ten en cuenta lo siguiente:

  • Los cambios de configuración son frecuentes: No supongas que los cambios de configuración son inusuales o que nunca suceden, sin importar el nivel de API, el factor de forma o el kit de herramientas de la IU. Cuando un usuario genera un cambio de configuración, espera que las apps se actualicen y continúen funcionando correctamente con la nueva configuración.
  • Conservar el estado: No pierdas el estado del usuario cuando se produzca una recreación de actividades. Conserva el estado como se describe en Cómo guardar estados de la IU.
  • Evita recurrir a inhabilitar como una solución rápida: No inhabilites la recreación de actividades como un atajo para evitar la pérdida de estado. Si quieres inhabilitar la recreación de actividades, debes cumplir con la promesa de controlar el cambio. Además, podrías perder el estado debido a la recreación de actividades de otros cambios de configuración, el cierre del proceso o el cierre de la app. Es imposible inhabilitar por completo la recreación de actividad. Conserva el estado como se describe en Cómo guardar estados de la IU.
  • No evites cambios en la configuración: No pongas restricciones sobre la orientación, la relación de aspecto o el cambio de tamaño para evitar cambios en la configuración y recreación de actividad. Esto afecta de forma negativa a los usuarios que deseen usar tu app de la manera que prefieran.

Cómo controlar los cambios de configuración basados en el tamaño

Los cambios de configuración basados en el tamaño pueden ocurrir en cualquier momento. Esto sucede con mayor frecuencia cuando tu app se ejecuta en un dispositivo de pantalla grande en el que los usuarios pueden ingresar al modo multiventana. Los usuarios esperan que tu app funcione bien en ese entorno.

Existen dos tipos generales de cambios de tamaño: significativos y mínimos. Un cambio de tamaño "significativo" es aquel en el que un conjunto diferente de recursos alternativos se aplica a la nueva configuración debido a una diferencia en el tamaño de la pantalla, como el ancho, la altura o el ancho mínimo. Entre estos recursos, se incluyen aquellos que la app define por sí misma, y cualquiera de sus bibliotecas que contengan recursos.

Cómo restringir la recreación de actividades para los cambios de configuración basados en el tamaño

Cuando inhabilitas larecreación de actividades para cambios de configuración basados en el tamaño, el sistema no vuelve a crear una actividad. En su lugar, recibe una llamada a Activity.onConfigurationChanged(). Todas las vistas adjuntas reciben una llamada a View.onConfigurationChanged().

Cómo permitir la recreación de actividades para los cambios de configuración basados en el tamaño

En el nivel de API 24 y superiores, la recreación de actividades solo se produce para los cambios de configuración basados en el tamaño si el cambio de tamaño es significativo. Cuando el sistema no vuelve a crear una actividad debido a que no tiene el tamaño suficiente, puede llamar a Activity.onConfigurationChanged() y View.onConfigurationChanged() en su lugar.

Ten en cuenta las siguientes advertencias sobre las devoluciones de llamada de los objetos Activity y View:

  • En el nivel de API 30 y superiores, no se llamará a la devolución de llamada Activity.onConfigurationChanged().
  • En el nivel de API 32 y 33, no se llamará a View.onConfigurationChanged(). Este es un error que se corrigió en versiones futuras de la API.

En el caso del código que depende de la detección de cambios de configuración basados en el tamaño, se recomienda usar una View de utilidad con un View.onConfigurationChanged() anulado, en lugar de depender de la recreación de actividades o de Activity.onConfigurationChanged().