Tareas y la pila de actividades

Una tarea es una colección de actividades con la que los usuarios interactúan cuando intentan realizar algo 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 nueva actividad 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 tarea nueva y su actividad principal se abre como la actividad raíz de la pila.

Cuando la actividad actual inicia otra, la nueva actividad se envía a la parte superior de la pila y toma el foco. 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 reorganizan, solo se insertan y se quitan de la pila a medida que la actividad actual las inicia y el usuario las descarta mediante el 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 actividades que se envían y se extraen 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 mueve el dedo hacia atrás, se destruye la actividad actual y se reanuda la anterior.

A medida que el usuario presiona o mueve el gesto atrás, 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 pulsació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 tu app desde el selector de aplicaciones y se usan para iniciar una tarea.

Cuando un usuario presiona o hace el gesto de atrás desde una actividad de selector 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 sales 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ápido tu app desde un estado semicaliente, en lugar de tener que reiniciar por completo la app desde un estado frío.

Si necesitas proporcionar navegación hacia atrás personalizada, te recomendamos usar las APIs de actividad de AndroidX en lugar de anular onBackPressed(). Las APIs de actividad de AndroidX difieren automáticamente al comportamiento apropiado del sistema si no hay componentes que intercepten el 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 llamar 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 coherente que puede pasar a segundo plano cuando un usuario inicia una tarea nueva o va a la pantalla principal. En el segundo plano, se detienen todas las actividades de la tarea, 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). Una tarea puede volver a primer plano para que los usuarios puedan retomar desde donde la dejaron.

Considera el siguiente flujo 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 varias actividades

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 específica desde más de una actividad, se crea una instancia nueva de esa actividad y se 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 instancias de una actividad de tu app varias veces, incluso desde diferentes tareas, como se muestra en la figura 3.

Si el usuario retrocede con el gesto o el botón 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 crear una instancia de una actividad más de una vez. Obtén más información en la sección sobre 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 por separado para cada ventana. Cada ventana puede tener varias tareas. Lo mismo sucede con 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 el texto ingresado en los formularios. Si el usuario presiona o usa el gesto atrás mientras está en la actividad B, se reanuda la actividad A con su estado restablecido.

  • Cuando el usuario abandona una tarea usando el botón de inicio o el gesto, la actividad actual se detiene y su tarea pasa a segundo plano. El sistema retiene el estado de cada actividad en la tarea. Si el usuario luego 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 hace un gesto 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 conserva 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 versiones posteriores.

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

Administra tareas

Para administrar las tareas y la pila de actividades, Android coloca todas las actividades que se inician una 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 debes preocuparte por cómo se asocian tus actividades con las tareas o cómo se encuentran en la pila de actividades.

Sin embargo, puedes decidir que quieres interrumpir el comportamiento normal. Por ejemplo, es posible que desees que una actividad de tu app inicie una tarea nueva cuando se 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 ella, en lugar de crear una instancia nueva sobre la pila de actividades. También es posible que quieras borrar todas las actividades de la pila de actividades, excepto la actividad raíz, cuando el usuario salga de la tarea.

Puedes realizar estas y otras acciones mediante 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:

Y 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 discuten las consideraciones sobre la forma en que las tareas y las actividades se representan y administran en la pantalla Recientes. Por lo general, permites que el sistema defina cómo se representan tu tarea y tus actividades en la pantalla Recientes, y no es necesario que modifiques 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 se puede asociar la actividad B con la tarea actual.

Si ambas actividades definen el modo en que la actividad B se asocia 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, como se define en su manifiesto.

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

Cuando declaras una actividad en el 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 lanzamiento 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 direcciona 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 direcciona el intent a esa instancia a través de una llamada a su método onNewIntent(), en lugar de crear una nueva instancia 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 de 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 instancia nueva 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. Por otro lado, si llega un intent para una actividad de tipo B, se agrega una nueva instancia de B a la pila, incluso si su modo de lanzamiento 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 direcciona 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, todas las demás actividades posteriores se destruyen.
  4. "singleInstance".
    El comportamiento es el mismo que el de "singleTask", con la excepción de que el sistema no inicia ninguna otra actividad en la tarea que contiene la instancia. La actividad siempre es el único miembro de su tarea. Cualquier actividad que esta inicie se abrirá 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 diferentes tareas si se configura la marca FLAG_ACTIVITY_MULTIPLE_TASK o FLAG_ACTIVITY_NEW_DOCUMENT.

En otro ejemplo, la app de 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>. Esto 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 este ya tiene una tarea ejecutándose en segundo plano, esa tarea se lleva a cabo para controlar el intent nuevo.

Independientemente de si una actividad se inicia 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, esa tarea completa pasa al primer plano. En este punto, la pila de actividades incluye todas las actividades de la tarea que pasaron a 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 intent

Cuando inicias una actividad, puedes modificar la asociación predeterminada de una actividad con su tarea si incluyes 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 una tarea ya se está ejecutando para la actividad que se está iniciando, esa tarea 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 instancia nueva de esa actividad, el sistema destruye todas las demás actividades sobre 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 a menudo se usa 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 controlar afinidades

Una 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 de 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 actividad nueva 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 pasado 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 comience 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 vuelva a la tarea. Algunas entidades, como el administrador de notificaciones, siempre inician actividades en una tarea externa, nunca como parte de una propia, por lo que siempre colocan 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 manera 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 desde la tarea que inicia hasta 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 en ciudades seleccionadas se define como parte de una app de viajes. Tiene la misma afinidad que otras actividades de la misma app, la afinidad de app predeterminada y se puede cambiar el campo superior con este atributo.

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

Cómo borrar la pila de actividades

Si el usuario deja una tarea durante mucho tiempo, el sistema borra todas las actividades de la tarea excepto la actividad raíz. Cuando el usuario regresa a la tarea, solo se restablece la actividad raíz. El sistema se comporta de esta manera a partir de 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, la tarea se borra en la actividad raíz cada vez que el usuario abandona la tarea y regresa 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 opera 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, proporciona 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 forma de iniciar la actividad y volver a la tarea que crea en cualquier momento después de su inicio.

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 tiene un filtro ACTION_MAIN y CATEGORY_LAUNCHER.

Imagina, por ejemplo, lo que podría suceder si faltara el filtro: un intent inicia una actividad "singleTask", inicia una tarea nueva y el usuario pasa un tiempo trabajando en esa tarea. Luego, el usuario utiliza el gesto o el botón 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 quieras que el usuario pueda volver a una actividad, establece el finishOnTaskLaunch del elemento <activity> en "true". Si deseas obtener más información, consulta la sección para 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 las actividades en esta pantalla.

Más recursos