Tareas y la pila de actividades

Una tarea es una colección de actividades con las que los usuarios interactúan cuando intentan realizar una acción en tu app. Estas actividades se organizan en una pila llamada pila de actividades, en el orden en que se abre cada actividad.

Por ejemplo, una app de correo electrónico podría tener una actividad para mostrar una lista de mensajes nuevos. Cuando el usuario selecciona un mensaje, se abre una actividad nueva para verlo. Esta nueva actividad se agrega a la pila de actividades. Luego, cuando el usuario presiona o hace un gesto atrás, esa nueva actividad finaliza y se quita de la pila.

Ciclo de vida de una tarea y su pila de actividades

La pantalla principal del dispositivo es el lugar donde se inician la mayoría de las tareas. Cuando un usuario toca el ícono de una app o un acceso directo en el selector de aplicaciones o en la pantalla principal, la tarea de esa app pasa a primer plano. Si no existe una tarea para la app, se crea una nueva y se abre la actividad principal de esa app como la actividad raíz en la pila.

Cuando la actividad actual inicia otra, la nueva actividad se coloca en la parte superior de la pila y se enfoca. La actividad anterior permanece en la pila, pero se detiene. Cuando se detiene una actividad, el sistema retiene el estado actual de su interfaz de usuario. Cuando el usuario realiza la acción de retroceso, la actividad actual aparece de la parte superior de la pila y se destruye. Se reanuda la actividad anterior y se restablece el estado anterior de su IU.

Las actividades de la pila nunca se reordenan, solo se insertan y se quitan de la pila cuando son iniciadas por la actividad actual y las descarta el usuario a través del botón Atrás o el gesto. Por lo tanto, la pila de actividades funciona como una estructura de objetos del último en entrar, primero en salir. En la Figura 1, se muestra un cronograma con las actividades que se insertan y que se quitan de una pila de actividades.

Figura 1: Una representación de cómo cada nueva actividad en una tarea agrega un elemento a la pila de actividades Cuando el usuario presiona o hace un gesto atrás, la actividad actual se destruye y la actividad anterior se reanuda.

A medida que el usuario sigue presionando o con el gesto de retroceder, se quita cada actividad de la pila para mostrar la anterior, hasta que el usuario vuelve a la pantalla principal o a la actividad que se estaba ejecutando cuando comenzó la tarea. Cuando se quitan todas las actividades de la pila, la tarea deja de existir.

Comportamiento de la presión hacia atrás para las actividades del selector raíz

Las actividades del selector raíz son actividades que declaran un filtro de intents con ACTION_MAIN y CATEGORY_LAUNCHER. Estas actividades son únicas porque actúan como puntos de entrada a la app desde el selector de aplicaciones y se usan para iniciar una tarea.

Cuando un usuario presiona o mueve el gesto hacia atrás desde una actividad de inicio raíz, el sistema controla el evento de manera diferente según la versión de Android que ejecute el dispositivo.

Comportamiento del sistema en Android 11 y versiones anteriores
El sistema finaliza la actividad.
Comportamiento del sistema en Android 12 y versiones posteriores

El sistema pasa la actividad y su tarea a segundo plano en lugar de finalizarla. Este comportamiento coincide con el predeterminado del sistema cuando navegas fuera de una app con el gesto o el botón de inicio.

En la mayoría de los casos, este comportamiento significa que los usuarios pueden reanudar más rápidamente tu app desde un estado semicaliente, en lugar de tener que reiniciar la app por completo desde un estado frío.

Si necesitas proporcionar navegación hacia atrás personalizada, te recomendamos que uses las APIs de actividad de AndroidX en lugar de anular onBackPressed(). Las APIs de actividad de AndroidX difieren automáticamente del comportamiento adecuado del sistema si no hay componentes que intercepten el Back Tap del sistema.

Sin embargo, si tu app anula onBackPressed() para controlar la navegación hacia atrás y finalizar la actividad, actualiza tu implementación para que llame a super.onBackPressed() en lugar de finalizar. Llamar a super.onBackPressed() mueve la actividad y su tarea a segundo plano cuando corresponda, y proporciona una experiencia de navegación más coherente para los usuarios en todas las apps.

Tareas en primer y segundo plano

Figura 2: Dos tareas: La tarea B recibe la interacción del usuario en primer plano, mientras que la tarea A está en segundo plano, esperando para reanudarse

Una tarea es una unidad cohesiva que puede pasar a segundo plano cuando un usuario comienza una tarea nueva o va a la pantalla principal. Mientras está en segundo plano, todas las actividades de la tarea se detienen, pero la pila de actividades de la tarea permanece intacta: la tarea pierde el foco mientras se lleva a cabo otra, como se muestra en la figura 2. Luego, una tarea puede volver a primer plano para que los usuarios puedan retomar desde donde la dejaron.

Considera el siguiente flujo de tareas para la tarea A actual que tiene tres actividades en su pila, incluidas dos en la actividad actual:

  1. El usuario usa el botón o el gesto de inicio y, luego, inicia una nueva app desde el selector de aplicaciones.

    Cuando aparece la pantalla principal, la tarea A pasa a segundo plano. Cuando se inicia la app nueva, el sistema inicia una tarea para esa app (tarea B) con su propia pila de actividades.

  2. Después de interactuar con esa app, el usuario vuelve a la pantalla principal y selecciona la app que inició originalmente la tarea A.

    Ahora, la tarea A pasa al primer plano (las tres actividades de la pila están intactas y se reanuda la actividad de la parte superior de la pila). En este punto, el usuario también puede regresar a la Tarea B si va a la pantalla principal y selecciona el ícono de la app que inició esa tarea, o bien selecciona la tarea de la app desde la pantalla Recientes.

Instancias de actividad múltiples

Figura 3: Se pueden crear instancias de una sola actividad varias veces.

Debido a que las actividades de la pila de actividades nunca se reorganizan, si tu app permite que los usuarios inicien una actividad en particular desde más de una actividad, se crea una instancia nueva de esa actividad y se la envía a la pila, en lugar de colocar cualquier instancia anterior de la actividad en la parte superior. Por lo tanto, se pueden crear varias instancias de una actividad de tu app, incluso desde diferentes tareas, como se muestra en la figura 3.

Si el usuario navega hacia atrás con el botón o el gesto Atrás, las instancias de la actividad se revelan en el orden en que se abrieron, cada una con su propio estado de IU. Sin embargo, puedes modificar este comportamiento si no quieres que se creen instancias de una actividad más de una vez. Obtén más información sobre este tema en la sección sobre la administración de tareas.

Entornos multiventana

Cuando las apps se ejecutan simultáneamente en un entorno multiventana, compatible con Android 7.0 (nivel de API 24) y versiones posteriores, el sistema administra las tareas de cada ventana por separado. Cada ventana puede tener varias tareas. Lo mismo se aplica a las apps para Android que se ejecutan en Chromebooks: el sistema administra tareas (o grupos de tareas) por ventana.

Resumen del ciclo de vida

En resumen, el comportamiento predeterminado de las actividades y las tareas es el siguiente:

  • Cuando la actividad A inicia la actividad B, se detiene la actividad A, pero el sistema conserva su estado, como su posición de desplazamiento y cualquier texto ingresado en los formularios. Si el usuario presiona o usa el gesto de retroceso mientras está en la actividad B, se reanuda la actividad A y se restablece su estado.

  • Cuando el usuario abandona una tarea con el botón o el gesto de inicio, la actividad actual se detiene y su tarea pasa a segundo plano. El sistema retiene el estado de cada actividad en la tarea. Si, luego, el usuario selecciona el ícono de selector que inició la tarea para reanudarla, esta pasará a primer plano y se reanudará la actividad en la parte superior de la pila.

  • Si el usuario presiona o mueve hacia atrás, la actividad actual se quita de la pila y se destruye. Se reanuda la actividad anterior de la pila. Cuando se destruye una actividad, el sistema no retiene su estado.

    Este comportamiento es diferente para las actividades de selector raíz cuando tu app se ejecuta en un dispositivo que ejecuta Android 12 o una versión posterior.

  • Se pueden crear instancias de las actividades varias veces, incluso desde otras tareas.

Administra tareas

Android administra las tareas y la pila de actividades colocando todas las actividades que se inician en sucesión en la misma tarea, en una última pila en entrada, primero en salir. Esto funciona muy bien para la mayoría de las apps y, por lo general, no tienes que preocuparte por cómo se asocian tus actividades con las tareas o cómo existen en la pila de actividades.

Sin embargo, puedes decidir que deseas interrumpir el comportamiento normal. Por ejemplo, es posible que desees que una actividad de tu app comience una tarea nueva cuando se la inicie, en lugar de colocarse dentro de la tarea actual. O bien, cuando inicias una actividad, es posible que desees reenviar una instancia existente de la misma, en lugar de crear una instancia nueva sobre la pila de actividades. O bien, es posible que quieras borrar todas las actividades de la pila de actividades, excepto la actividad raíz, cuando el usuario abandona la tarea.

Puedes realizar estas y otras acciones con los atributos del elemento del manifiesto <activity> y las marcas en el intent que pasas a startActivity().

Estos son los atributos <activity> principales que puedes usar para administrar tareas:

Estas son las principales marcas de intent que puedes usar:

En las siguientes secciones, se analiza cómo usar estos atributos de manifiesto y marcas de intents para definir cómo se asocian las actividades con las tareas y cómo se comportan en la pila de actividades.

También se analizan las consideraciones sobre cómo se representan y administran las tareas y actividades en la pantalla Recientes. Por lo general, permites que el sistema defina cómo se representan tus tareas y actividades en la pantalla Recientes, y no necesitas modificar este comportamiento. Para obtener más información, consulta la pantalla Recientes.

Cómo definir los modos de lanzamiento

Los modos de lanzamiento te permiten definir cómo se asocia una nueva instancia de una actividad con la tarea actual. Puedes definir los modos de inicio de dos maneras, que se describen en las siguientes secciones:

Por lo tanto, si la actividad A inicia la actividad B, la actividad B puede definir en su manifiesto cómo se asocia con la tarea actual, y la actividad A puede usar una marca de intent para solicitar cómo la actividad B puede asociarse con la tarea actual.

Si ambas actividades definen cómo se asocia la actividad B con una tarea, se respeta la solicitud de la actividad A, como se define en el intent, en lugar de la de la actividad B, según se define en su manifiesto.

Cómo definir modos de lanzamiento con el archivo de manifiesto

Cuando declaras una actividad en tu archivo de manifiesto, puedes especificar cómo se asocia la actividad con una tarea mediante el atributo launchMode del elemento <activity>.

Existen cinco modos de inicio que puedes asignar al atributo launchMode:

  1. "standard"
    Es el modo predeterminado. El sistema crea una nueva instancia de la actividad en la tarea desde la que se inició y enruta el intent hacia ella. Se pueden crear instancias de la actividad varias veces, cada instancia puede pertenecer a diferentes tareas y una tarea puede tener varias instancias.
  2. "singleTop"
    Si ya existe una instancia de la actividad en la parte superior de la tarea actual, el sistema enruta el intent a esa instancia a través de una llamada a su método onNewIntent(), en lugar de crear una instancia nueva de la actividad. Se crean instancias de la actividad varias veces, cada instancia puede pertenecer a diferentes tareas, y una tarea puede tener varias instancias (pero solo si la actividad en la parte superior de la pila de actividades no es una instancia existente de la actividad).

    Por ejemplo, supongamos que la pila de actividades de una tarea consta de la actividad raíz A con actividades B, C y D en la parte superior (por lo que la pila es A-B-C-D, con D en la parte superior). Llega un intent para una actividad de tipo D. Si D tiene el modo de inicio "standard" predeterminado, se inicia una nueva instancia de la clase, y la pila se convierte en A-B-C-D-D. Sin embargo, si el modo de lanzamiento de D es "singleTop", la instancia existente de D recibe el intent a través de onNewIntent(), porque se encuentra en la parte superior de la pila, y la pila permanece como A-B-C-D. Si, por otro lado, llega un intent a una actividad de tipo B, se agrega una nueva instancia de B a la pila, incluso si su modo de inicio es "singleTop".

  3. "singleTask"
    El sistema crea la actividad en la raíz de una tarea nueva o ubica la actividad en una tarea existente con la misma afinidad. Si ya existe una instancia de la actividad, el sistema enruta el intent a la instancia existente a través de una llamada a su método onNewIntent(), en lugar de crear una instancia nueva. Mientras tanto, se destruyen todas las demás actividades encima de ella.
  4. "singleInstance".
    El comportamiento es el mismo que para "singleTask", con la excepción de que el sistema no lanza otras actividades en la tarea que contiene la instancia. La actividad siempre es el único miembro de su tarea. Todas las actividades que esta inicie se abrirán en una tarea independiente.
  5. "singleInstancePerTask".
    La actividad solo puede ejecutarse como la actividad raíz de la tarea, la primera actividad que creó la tarea y, por lo tanto, solo puede haber una instancia de esta actividad en una tarea. A diferencia del modo de lanzamiento singleTask, esta actividad se puede iniciar en varias instancias en distintas tareas si se establece la marca FLAG_ACTIVITY_MULTIPLE_TASK o FLAG_ACTIVITY_NEW_DOCUMENT.

En otro ejemplo, la app del navegador de Android declara que la actividad del navegador web siempre se abre en su propia tarea especificando el modo de inicio singleTask en el elemento <activity>. Eso significa que, si tu app emite un intent para abrir el navegador de Android, su actividad no se coloca en la misma tarea que tu app. En cambio, se inicia una tarea nueva para el navegador o, si el navegador ya tiene una tarea ejecutándose en segundo plano, esa tarea se desplaza para controlar el intent nuevo.

Independientemente de que una actividad se inicie en una tarea nueva o en la misma tarea que la actividad que la inició, el botón Atrás y el gesto siempre llevan al usuario a la actividad anterior. Sin embargo, si inicias una actividad que especifica el modo de inicio singleTask y existe una instancia de esa actividad en una tarea en segundo plano, toda esa tarea pasa al primer plano. En este punto, la pila de actividades incluye todas las actividades de la tarea que pasó en la parte superior de la pila. En la Figura 4, se muestra este tipo de situación.

Figura 4: Representación de cómo una actividad con el modo de lanzamiento "singleTask" se agrega a la pila de actividades. Si la actividad ya forma parte de una tarea en segundo plano con su propia pila de actividades, toda la pila también avanza, sobre la tarea actual.

Para obtener más información sobre el uso de modos de inicio en el archivo de manifiesto, consulta la documentación sobre el elemento <activity>.

Cómo definir modos de lanzamiento con marcas de intents

Cuando inicias una actividad, puedes modificar su asociación predeterminada con su tarea. Para ello, debes incluir marcas en el intent que entregas a startActivity(). Las marcas que puedes usar para modificar el comportamiento predeterminado son las siguientes:

FLAG_ACTIVITY_NEW_TASK

El sistema inicia la actividad en una tarea nueva. Si ya se está ejecutando una tarea para la actividad que se está iniciando, esta pasa al primer plano con su último estado restablecido, y la actividad recibe el intent nuevo en onNewIntent().

Esto produce el mismo comportamiento que el valor "singleTask" launchMode que se analizó en la sección anterior.

FLAG_ACTIVITY_SINGLE_TOP

Si la actividad que se inicia es la actual, en la parte superior de la pila de actividades, la instancia existente recibe una llamada a onNewIntent() en lugar de crear una instancia nueva de la actividad.

Esto produce el mismo comportamiento que el valor launchMode de "singleTop" que se analizó en la sección anterior.

FLAG_ACTIVITY_CLEAR_TOP

Si la actividad que se inicia ya se está ejecutando en la tarea actual, en lugar de iniciar una nueva instancia de esa actividad, el sistema destruye todas las demás actividades encima de ella. El intent se entrega a la instancia reanudada de la actividad, ahora en la parte superior, a través de onNewIntent().

No hay ningún valor para el atributo launchMode que produce este comportamiento.

FLAG_ACTIVITY_CLEAR_TOP se usa generalmente junto con FLAG_ACTIVITY_NEW_TASK. Cuando se usan juntas, estas marcas localizan una actividad existente en otra tarea y la colocan en una posición en la que puede responder al intent.

Cómo procesar afinidades

La afinidad indica a qué tarea "prefiere" pertenecer una actividad. De forma predeterminada, todas las actividades de la misma app tienen afinidad entre sí: "prefieren" estar en la misma tarea.

Sin embargo, puedes modificar la afinidad predeterminada para una actividad. Las actividades definidas en diferentes apps pueden compartir una afinidad, y se pueden asignar diferentes afinidades de tareas a las actividades definidas en la misma app.

Puedes modificar la afinidad de una actividad con el atributo taskAffinity del elemento <activity>.

El atributo taskAffinity toma un valor de cadena que debe ser diferente del nombre de paquete predeterminado declarado en el elemento <manifest>, ya que el sistema usa ese nombre para identificar la afinidad de tarea predeterminada de la app.

La afinidad entra en juego en dos circunstancias:

  1. Cuando el intent que inicia una actividad contiene la marca FLAG_ACTIVITY_NEW_TASK.

    De forma predeterminada, se lanza una nueva actividad en la tarea de la actividad que llamó a startActivity(). Se inserta en la misma pila de actividades que el llamador.

    Sin embargo, si el intent que se pasó a startActivity() contiene la marca FLAG_ACTIVITY_NEW_TASK, el sistema busca una tarea diferente para alojar la actividad nueva. A menudo, esta es una tarea nueva. Sin embargo, no es necesario que lo sea. Si ya existe una tarea con la misma afinidad que la actividad nueva, la actividad se lanza en esa tarea. De lo contrario, se inicia una tarea nueva.

    Si esta marca hace que una actividad inicie una tarea nueva y el usuario utiliza el botón de inicio o el gesto para salir de ella, debe haber alguna manera de que el usuario pueda volver a la tarea. Algunas entidades, como el administrador de notificaciones, siempre inician actividades en una tarea externa, nunca como parte de ellas, por lo que siempre ponen FLAG_ACTIVITY_NEW_TASK en los intents que pasan a startActivity().

    Si una entidad externa que podría usar esta marca puede invocar tu actividad, asegúrate de que el usuario tenga una forma independiente de volver a la tarea que se inició, por ejemplo, con un ícono de selector, en el que la actividad raíz de la tarea tiene un filtro de intents CATEGORY_LAUNCHER. Para obtener más información, consulta la sección sobre cómo iniciar tareas.

  2. Cuando una actividad tiene su atributo allowTaskReparenting establecido en "true".

    En este caso, la actividad puede moverse de la tarea que inicia a la tarea con la que tiene afinidad cuando esta pasa al primer plano.

    Por ejemplo, supongamos que una actividad que informa las condiciones climáticas de determinadas ciudades se define como parte de una app de viajes. Tiene la misma afinidad que otras actividades de la misma app, tiene la afinidad predeterminada de la app, y se puede cambiar el campo superior con este atributo.

    Cuando una de tus actividades inicia la actividad del informe del clima, inicialmente pertenece a la misma tarea que tu actividad. Sin embargo, cuando la tarea de la app de viajes pasa al primer plano, la actividad del informe del clima se reasigna a esa tarea y se muestra dentro de ella.

Borra la pila de actividades

Si el usuario deja una tarea por mucho tiempo, el sistema borra todas las actividades de la tarea, excepto la actividad raíz. Cuando el usuario vuelve a la tarea, solo se restablece la actividad raíz. El sistema se comporta de esta manera con base en la suposición de que, después de un período prolongado, los usuarios abandonan lo que estaban haciendo antes y vuelven a la tarea para comenzar algo nuevo.

Existen algunos atributos de actividades que puedes utilizar para modificar este comportamiento:

alwaysRetainTaskState
Cuando este atributo se establece en "true" en la actividad raíz de una tarea, no se produce el comportamiento predeterminado que se describió anteriormente. La tarea retiene todas las actividades en su pila incluso después de un período prolongado.
clearTaskOnLaunch

Cuando este atributo se establece en "true" en la actividad raíz de una tarea, esta se borra hasta la actividad raíz cada vez que el usuario abandona la tarea y vuelve a ella. En otras palabras, es lo opuesto a alwaysRetainTaskState. El usuario siempre vuelve a la tarea en su estado inicial, incluso después de dejarla por un momento.

finishOnTaskLaunch

Este atributo es como clearTaskOnLaunch, pero funciona en una sola actividad, no en una tarea completa. También puede hacer que finalice cualquier actividad, excepto la actividad raíz. Cuando se establece en "true", la actividad sigue siendo parte de la tarea solo durante la sesión actual. Si el usuario abandona la tarea y, luego, vuelve a ella, ya no estará presente.

Cómo iniciar una tarea

Para configurar una actividad como punto de entrada de una tarea, puedes proporcionarle un filtro de intents con "android.intent.action.MAIN" como la acción especificada y "android.intent.category.LAUNCHER" como la categoría especificada:

<activity ... >
    <intent-filter ... >
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    ...
</activity>

Un filtro de intents de este tipo hace que se muestren un ícono y una etiqueta para la actividad en el selector de aplicaciones, lo que brinda a los usuarios una manera de iniciar la actividad y volver a la tarea que crea en cualquier momento después de iniciarse.

Esta segunda habilidad es importante. Los usuarios deben poder dejar una tarea y volver a ella más tarde con este selector de actividades. Por este motivo, solo usa los dos modos de inicio que marcan las actividades como que siempre inician una tarea, "singleTask" y "singleInstance", cuando la actividad tenga un filtro ACTION_MAIN y un filtro CATEGORY_LAUNCHER.

Imagina, por ejemplo, lo que podría suceder si faltara el filtro: un intent inicia una actividad "singleTask" e inicia una tarea nueva, y el usuario pasa algún tiempo trabajando en esa tarea. Luego, el usuario utiliza el botón o gesto de inicio. La tarea se envía a segundo plano y no es visible. Ahora el usuario no tiene forma de volver a la tarea, porque esta no está representada en el selector de aplicaciones.

En los casos en los que no desees que el usuario pueda volver a una actividad, establece finishOnTaskLaunch en "true".<activity> Para obtener más información, consulta la sección sobre cómo borrar la pila de actividades.

En la pantalla Recientes, encontrarás más información sobre cómo se representan y administran las tareas y actividades en la pantalla Recientes.

Más recursos