Información sobre las tareas y la pila de actividades

Una tarea es una agrupación de actividades con la que interactúan los usuarios cuando realizan una acción determinada. Las actividades se organizan en una pila (la 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. Si el usuario presiona el botón Atrás, la nueva actividad finaliza y se quita de la pila. En el siguiente video, se proporciona una descripción general de cómo funciona la pila de actividades.

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

La pantalla principal del dispositivo es el punto de partida de la mayoría de las tareas. Cuando el usuario toca un ícono en el selector de apps (o un acceso directo en la pantalla principal), la tarea de esa app pasa a primer plano. Si no existe ninguna tarea para la app (porque esta no se usó recientemente), entonces se crea una nueva tarea y la actividad "principal" de esa app se abre como la actividad raíz de la pila.

Cuando la actividad actual inicia otra, la nueva actividad se coloca en la parte superior de la pila y toma el foco. La actividad anterior permanece en la pila, pero se detiene. Cuando una actividad se detiene, el sistema retiene el estado actual de su interfaz de usuario. Cuando el usuario presiona el botón Atrás, se quita la actividad actual de la parte superior de la pila (se elimina la actividad) y se reanuda la actividad anterior (se restablece el estado anterior de su IU). Las actividades de la pila nunca se reordenan, solo se insertan o se quitan (se insertan en la pila cuando la actividad actual las inicia y se quitan cuando el usuario sale de ellas presionando el botón Atrás). Como tal, la pila de actividades funciona como una estructura de objetos "último en entrar, primero en salir". En la Figura 1, se muestra este comportamiento con una línea de tiempo que muestra el progreso entre actividades junto con la pila de actividades actual en cada punto en el tiempo.

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 el botón Atrás, se elimina la actividad actual y se reanuda la anterior.

Si el usuario continúa presionando el botón Atrás, entonces se quita cada actividad de la pila a fin de mostrar la anterior, hasta que el usuario vuelve a la pantalla principal (o a lo que sea que estaba ejecutando cuando inició la tarea). Cuando se quitan todas las actividades de una pila, la tarea deja de existir.

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 el usuario inicia una nueva tarea o cuando presiona el botón Inicio para mostrar la pantalla principal. En el segundo plano, se detienen todas las actividades, pero la pila de actividades de la tarea permanece intacta (la tarea simplemente pierde el foco mientras otra toma su lugar, como se muestra en la Figura 2). Luego, una tarea puede regresar al "primer plano" por lo que los usuarios pueden reanudar lo que estaban haciendo. Por ejemplo, supongamos que la tarea actual (tarea A) tiene tres actividades en su pila (dos pertenecen a la actividad actual). El usuario presiona el botón Inicio y, luego, inicia una nueva app desde el selector de apps. Cuando aparece la pantalla principal, la tarea A pasa al segundo plano. Cuando se inicia una nueva app, el sistema inicia una tarea para esta (tarea B) con su propia pila de actividades. Luego de interactuar con esa app, el usuario vuelve a la pantalla principal y selecciona la app que originalmente inició la tarea A. A continuación, la tarea A pasa al primer plano (las tres actividades de la pila permanecen intactas y se reanuda la primera actividad de la pila). En este punto, el usuario también puede volver a la tarea B dirigiéndose a la pantalla principal y seleccionando el ícono de la app que inició esa tarea (o seleccionando la tarea de la app desde la pantalla Recientes). Este es un ejemplo de cómo realizar varias tareas a la vez.

Nota: Se pueden mantener varias tareas en segundo plano a la vez. Sin embargo, si el usuario ejecuta muchas tareas en segundo plano a la vez, el sistema puede eliminar algunas actividades a fin de recuperar memoria, lo que provocará que se pierdan los estados de las actividades.

Figura 3: Se crean instancias de una única actividad varias veces.

Debido a que las actividades de la pila de actividades nunca se reordenan, si tu app permite a los usuarios iniciar una actividad en particular desde más de una actividad, se crea una nueva instancia de esa actividad y se inserta en la pila (en lugar de colocar una instancia previa de la actividad en la parte superior). Por lo tanto, se podrían crear instancias de una actividad en tu app varias veces (incluso desde diferentes tareas), como se muestra en la Figura 3. En ese caso, si el usuario navega hacia atrás con el botón Atrás, cada instancia de la actividad se muestra en el orden en que se abrió (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. En la sección Cómo administrar tareas, que se desarrolla más adelante, se describe cómo hacerlo.

A continuación, se resume el comportamiento predeterminado de las actividades y tareas:

  • Cuando la actividad A inicia la actividad B, se detiene la actividad A, pero el sistema conserva su estado (como la posición de deslizamiento y el texto ingresado en los formularios). Si el usuario presiona el botón Atrás mientras está en la actividad B, se reanuda la actividad A y se restablece su estado.
  • Cuando el usuario sale de una tarea presionando el botón Inicio, se detiene la actividad actual y su tarea pasa al segundo plano. El sistema conserva el estado de cada actividad en la tarea. Si luego el usuario reanuda la tarea seleccionando el ícono de selector que inició la tarea, esta pasa al primer plano y reanuda la actividad en la parte superior de la pila.
  • Si el usuario presiona el botón Atrás, la actividad actual se quita de la pila y se elimina. Se reanuda la actividad anterior en la pila. Cuando se elimina una actividad, el sistema no conserva su estado.
  • Puedes crear instancias de las actividades varias veces, incluso desde otras tareas.

Diseño de navegación

Para obtener más información sobre cómo funciona la navegación en la app en Android, consulta la guía sobre navegación de diseño de Android.

Cómo administrar tareas

La manera en que Android administra las tareas y la pila de actividades, como se describe más arriba (colocando las actividades en orden sucesivo en la misma tarea en una pila de "último en entrar, primero en salir"), funciona bien para la mayoría de las apps y no deberías preocuparte por cómo tus actividades se asocian con las tareas o cómo se incluyen en la pila de actividades. Sin embargo, puedes decidir que quieres interrumpir el comportamiento normal. Tal vez quieras que una actividad de tu app comience una nueva tarea cuando se inicie (en lugar de colocarse dentro de la tarea actual); o, cuando inicies una actividad, quizá quieras mover una de sus instancias existentes un nivel adelante (en lugar de crear una nueva instancia en la parte superior de la pila de actividades); o tal vez quieras quitar todas las actividades de la pila, salvo la actividad principal, cuando el usuario salga de la tarea.

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

En este contexto, los atributos <activity> que puedes usar son los siguientes:

Y las principales marcas de intents que puedes usar son las siguientes:

En las siguientes secciones, verás cómo puedes usar los siguientes atributos del manifiesto y las marcas de intents para definir cómo se asocian las actividades con las tareas y su comportamiento en la pila de actividades.

Además, se describen por separado las consideraciones sobre cómo pueden representarse y administrarse las tareas y actividades en la pantalla Recientes. Consulta la pantalla Recientes para obtener más información. Por lo general, deberías permitir que el sistema defina cómo se representan tu tarea y tus actividades en la pantalla Recientes y no necesitas modificar este comportamiento.

Precaución: La mayoría de las apps no deben interrumpir el comportamiento predeterminado de las actividades y tareas. Si determinas que es necesario modificar los comportamientos predeterminados de tu actividad, hazlo con cuidado y asegúrate de probar la usabilidad de esta durante el inicio y cuando regreses a ella desde otras actividades y tareas con el botón Atrás. Asegúrate de probar los comportamientos de navegación que puedan entrar en conflicto con el comportamiento que espera el usuario.

Cómo definir los modos de inicio

Los modos de inicio te permiten definir cómo una nueva instancia de una actividad se asocia con la tarea actual. Puedes definir diferentes modos de inicio de dos maneras:

En este contexto, si la actividad A inicia la actividad B, la actividad B puede definir en su manifiesto cómo debería asociarse con la tarea actual (o si debe hacerlo o no) y la actividad A también puede solicitar la manera en que la actividad B debería asociarse con la tarea actual. Si ambas actividades definen cómo la actividad B debería asociarse con una tarea, entonces se prioriza la solicitud de la actividad A (como se define en el intent) por sobre la de la actividad B (como se define en su manifiesto).

Nota: No es posible usar como marcas de un intent algunos modos de inicio disponibles para el archivo de manifiesto y, asimismo, no pueden definirse algunos modos de inicio disponibles como marcas de un intent en el manifiesto.

Cómo usar el archivo de manifiesto

Cuando declaras una actividad en el archivo de manifiesto, puedes especificar cómo la actividad debe asociarse con una tarea por medio del atributo launchMode del elemento <activity>.

El atributo launchMode especifica una instrucción sobre cómo la actividad debe iniciarse dentro de una tarea. Hay cuatro modos de inicio diferentes que puedes asignar al atributo launchMode:

"standard" (el modo predeterminado)
Valor predeterminado. El sistema crea una nueva instancia de la actividad en la tarea desde la que se inició y dirige el intent a esta. Se pueden crear instancias de la actividad varias veces, cada instancia puede pertenecer a diferentes tareas y una tarea puede tener varias instancias.
"singleTop"
Si una instancia de una actividad ya existe en la parte superior de la tarea actual, el sistema dirige el intent a esa instancia mediante un llamado a su método onNewIntent(), en lugar de crear una nueva instancia de la actividad. Es posible crear 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 se compone de la actividad raíz A con actividades B, C y D en la parte superior (la pila es A-B-C-D; D está en la parte superior). Se recibe un intent para una actividad de tipo D. Si D tiene el modo de inicio "standard", 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 inicio de D es "singleTop", la instancia existente de D recibe el intent mediante onNewIntent(), porque está en la parte superior de la pila (la pila se mantiene como A-B-C-D). Sin embargo, si se recibe un intent de una actividad de tipo B, entonces se agrega una nueva instancia de B a la pila, incluso si el modo de inicio es "singleTop".

Nota: Cuando se crea una nueva instancia de una actividad, el usuario puede presionar el botón Atrás para volver a la actividad anterior. Sin embargo, cuando una instancia existente de una actividad administra un nuevo intent, el usuario no puede presionar el botón Atrás para volver al estado de la actividad antes que se reciba el nuevo intent en onNewIntent().

"singleTask"
El sistema crea una nueva tarea y también instancias de la actividad en la raíz de la nueva tarea. Sin embargo, si ya existe una instancia de la actividad en una tarea diferente, el sistema dirige el intent a la instancia existente por medio de un llamado a su método onNewIntent(), en lugar de crear una nueva. Solo puede existir una instancia de cada actividad por vez.

Nota: Si bien la actividad se inicia en una nueva tarea, el botón Atrás igualmente dirige al usuario a la actividad anterior.

"singleInstance".
Es igual que "singleTask", salvo que el sistema no inicia otras actividades en la tarea que contiene la instancia. La actividad siempre es el único componente de su tarea; cualquier actividad que este inicie se abrirá en una tarea independiente.

A modo de ejemplo, la app de navegador de Android declara que la actividad del navegador web siempre debe estar abierta en su propia tarea (especificando el modo de inicio singleTask en el elemento <activity>). Eso implica que, si tu app emite un intent para abrir el navegador de Android, su actividad no se ubica en la misma tarea que tu app. En su lugar, se inicia una nueva tarea para el navegador o, si el navegador ya tiene una tarea en ejecución en segundo plano, esta pasa al primer plano para administrar el nuevo intent.

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

Figura 4: Una representación de cómo una actividad con el modo de inicio "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 pasa al primer plano, encima de la tarea actual.

Para obtener más información sobre cómo usar los modos de inicio en el archivo de manifiesto, consulta la documentación sobre el elemento <activity>, en donde se explican el atributo launchMode y los valores aceptados.

Nota: Los comportamientos que especificas para tu actividad con el atributo launchMode se pueden anular con las marcas que se incluyen en el intent que inicia tu actividad, como se explica en la siguiente sección.

Cómo usar marcas de intents

Cuando inicias una actividad, puedes modificar la asociación predeterminada de una actividad con su tarea al incluir marcas en el intent que envías a startActivity(). Las marcas que puedes usar para modificar el comportamiento predeterminado son las siguientes:

FLAG_ACTIVITY_NEW_TASK
Inicia la actividad en una nueva tarea. Si una tarea de la actividad que quieres iniciar ya está en ejecución, esta pasa al primer plano con el último estado restablecido y la actividad recibe un nuevo intent en onNewIntent().

Esta acción produce el mismo comportamiento que el valor "singleTask"launchMode, que se explica en la sección anterior.

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

Esta acción produce el mismo comportamiento que el valor "singleTop"launchMode, que se explica en la sección anterior.

FLAG_ACTIVITY_CLEAR_TOP
Si la actividad que se inicia ya está en ejecución en la tarea actual, entonces, en lugar de iniciar una nueva instancia de esa actividad, se eliminan todas las demás actividades encima de esta, y este intent se envía a la instancia reanudada de la actividad (ahora en la parte superior) por medio 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 son una manera de localizar una actividad existente en otra tarea y colocarla en una posición en la que pueda responder al intent.

Nota: Si el modo de inicio de la actividad designada es "standard", esta también se quita de la pila y se inicia una nueva instancia en su lugar a fin de administrar el intent entrante, ya que siempre se crea una nueva instancia para un nuevo intent cuando el modo de inicio es "standard".

Cómo administrar afinidades

La afinidad indica a qué tarea prefiere pertenecer una actividad. De forma predeterminada, todas las actividades de la misma app tienen afinidad entre ellas. Por lo tanto, todas las actividades de la misma app prefieren pertenecer a la misma tarea. Sin embargo, puedes modificar la afinidad predeterminada de una actividad. Las actividades definidas en diferentes apps pueden compartir una afinidad, o se les pueden asignar diferentes afinidades de tareas a las actividades definidas en la misma app.

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

El atributo taskAffinity toma un valor de string, que debe ser único, del nombre de paquete predeterminado declarado en el elemento <manifest>, porque el sistema lo usa para identificar la afinidad de tarea predeterminada de la app.

La afinidad entra en juego en dos circunstancias:

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

    De forma predeterminada, se inicia una nueva actividad en la tarea de la actividad que llamó a startActivity(). Se coloca en la misma pila de actividades que el emisor. Sin embargo, si el intent que se pasó a startActivity() contiene la marca FLAG_ACTIVITY_NEW_TASK, el sistema busca una tarea diferente en donde alojar la nueva actividad. Por lo general, es una tarea nueva. Sin embargo, no es necesario que lo sea. Si ya hay una tarea existente con la misma afinidad que la nueva actividad, la actividad se iniciará en ella. De lo contrario, se inicia una nueva tarea.

    Si esta marca hace que una actividad se inicie en una nueva tarea y el usuario presiona el botón Inicio para salir de ella, debe haber una manera para 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 ellas, por lo que siempre incluyen FLAG_ACTIVITY_NEW_TASK en los intents que pasan a startActivity(). Si tienes una actividad que puede invocarse desde una entidad externa que podría usar esta marca, asegúrate de que el usuario tenga una forma independiente de volver a la tarea que se inició, como con un ícono de selector (la actividad raíz de la tarea tiene un filtro de intent CATEGORY_LAUNCHER; consulta la sección Cómo iniciar una tarea a continuación).

  • Cuando una actividad tiene el atributo allowTaskReparenting establecido en "true".

    En este caso, la actividad puede moverse desde la tarea que inicia hasta aquella por la que tiene afinidad cuando esa tarea pasa al primer plano.

    Por ejemplo, supongamos que una actividad que proporciona información sobre el clima en ciudades seleccionadas se define como parte de una app de viaje. Esta tiene la misma afinidad que otras actividades en la misma app (la afinidad predeterminada de la app) y permite volver a asignar este atributo como elemento superior. Cuando una de tus actividades inicia la actividad de informe del clima, por lo general pertenece a la misma tarea que tu actividad. Sin embargo, cuando la tarea de la app de viaje pasa al primer plano, la actividad que informa sobre el clima vuelve a asignarse a esta y se muestra dentro de ella.

Sugerencia: Si un archivo APK contiene más de una "app" desde el punto de vista del usuario, es probable que quieras usar el atributo taskAffinity para asignar diferentes afinidades a la actividad asociada con cada "app".

Cómo borrar la pila de actividades

Si el usuario sale de una tarea por un tiempo prolongado, el sistema borra la tarea de todas las actividades, salvo 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 porque, luego de un tiempo prolongado, es probable que los usuarios hayan abandonado lo que estaban haciendo antes y vuelvan a la tarea para realizar una nueva acción.

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

alwaysRetainTaskState
Si este atributo se configura en "true" en la actividad raíz de una tarea, no se aplica el comportamiento predeterminado que se describió anteriormente. La tarea retiene todas las actividades en su pila, incluso luego de un período prolongado.
clearTaskOnLaunch
Si este atributo se establece en "true" en la actividad raíz de una tarea, se borrará toda la pila, salvo la actividad raíz, cada vez que el usuario salga de la tarea y vuelva a ella. En otras palabras, es lo opuesto a alwaysRetainTaskState. El usuario siempre vuelve a la tarea en su estado inicial, incluso si sale de ella solo un momento.
finishOnTaskLaunch
Este atributo es como clearTaskOnLaunch, pero opera en una sola actividad, no en toda una tarea. También puede hacer que se borre una actividad, incluida la actividad raíz. Cuando se configura en "true", la actividad permanece como parte de la tarea solo durante la sesión actual. Si el usuario sale y vuelve a ingresar, ya no estará presente.

Cómo iniciar una tarea

Puedes configurar una actividad como el punto de entrada de una tarea al incluir un filtro de intent con "android.intent.action.MAIN" como acción especificada y "android.intent.category.LAUNCHER" como categoría especificada. Por ejemplo:

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

Un filtro de intent de este tipo hace que se muestre un ícono y una etiqueta de la actividad en el selector de apps, lo que brinda al usuario una manera de iniciar la actividad y de volver a la tarea que esta crea cuando lo desee luego de que se haya iniciado.

Esta segunda capacidad es importante: los usuarios deben poder salir de una tarea y volver a ella más adelante por medio de este selector de actividades. Por este motivo, los dos modos de inicio que marcan actividades como que siempre inician tareas, "singleTask" y "singleInstance", solo deberían usarse cuando la actividad tiene una ACTION_MAIN y un filtro CATEGORY_LAUNCHER. Por ejemplo, imagina qué pasaría si faltara el filtro: un intent inicia una actividad "singleTask" y, luego, inicia una nueva tarea y el usuario pasa algo de tiempo en esta tarea. Luego, presiona el botón Inicio. La tarea ahora se envía al segundo plano y deja de estar visible. Luego, el usuario no tiene forma de volver a ella porque no se visualiza en el selector de apps.

En los casos en los que no quieres que el usuario pueda volver a una actividad, configura el finishOnTaskLaunch del elemento <activity> en "true" (consulta Cómo borrar la pila de actividades).

En el artículo sobre la pantalla Recientes, encontrarás más información sobre cómo se representan y administran las tareas y actividades de Recientes.

Más recursos