Una tarea es una colección de actividades con las que interactúan los usuarios cuando intentan hacer 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 actividad nueva para verlo. Esta actividad nueva se agrega a la pila de actividades. Luego, cuando el usuario presiona Atrás o realiza el gesto correspondiente, esa actividad nueva 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 punto de partida para la mayoría de las tareas. Cuando un usuario presiona 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 al 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 de la pila.
Cuando la actividad actual inicia otra, la nueva se envía a 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 conserva el estado actual de su interfaz de usuario. Cuando el usuario realiza la acción de volver, la actividad actual se quita 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 agregan cuando se inician y se quitan cuando se descartan con el botón o gesto Atrás. Por lo tanto, la pila de actividades funciona como una estructura del tipo último en entrar, primero en salir, como se muestra en la figura 1.
A medida que el usuario sigue presionando o haciendo el gesto de Atrás, cada actividad de la pila se quita para revelar la anterior, hasta que el usuario regresa 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 del botón 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 Atrás o realiza el gesto correspondiente desde una actividad raíz del selector, 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 mueve la actividad y su tarea al segundo plano en lugar de finalizar la actividad. Este comportamiento coincide con el predeterminado del sistema cuando se sale de una app con el botón o el gesto de inicio.
En la mayoría de los casos, este comportamiento significa que los usuarios pueden reanudar tu app con mayor rapidez desde un estado semicaliente, en lugar de tener que reiniciarla por completo desde un estado frío.
Si necesitas proporcionar una navegación hacia atrás personalizada, te recomendamos que uses las APIs de Activity de AndroidX en lugar de anular
onBackPressed(). Las APIs de Activity de AndroidX se aplazan automáticamente al comportamiento adecuado del sistema si no hay componentes que intercepten la presión del botón Atrás 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 llamar asuper.onBackPressed()en lugar de finalizar. Cuando se llama asuper.onBackPressed(), la actividad y su tarea se mueven a segundo plano cuando corresponde, y se proporciona una experiencia de navegación más coherente para los usuarios en todas las apps.
Tareas en segundo plano y en primer plano
Una tarea es una unidad cohesiva que puede pasar al segundo plano cuando un usuario comienza una tarea nueva o va a la pantalla principal. Mientras está en 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. Luego, una tarea puede volver al primer plano para que los usuarios puedan retomar lo que estaban haciendo.
Considera el siguiente flujo de tareas para la tarea A actual, que tiene tres actividades en su pila, incluidas dos debajo de la actividad actual:
El usuario usa el botón de inicio o el gesto y, luego, inicia una app nueva 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.
Después de interactuar con esa app, el usuario vuelve a la pantalla principal y selecciona la app que originalmente inició la tarea A.
Ahora, la tarea A pasa al primer plano: las tres actividades de su pila están intactas y se reanuda la actividad que se encuentra en la parte superior de la pila. En este punto, el usuario también puede volver a la tarea B. Para ello, debe ir a la pantalla principal y seleccionar el ícono de la app que inició esa tarea o seleccionar la tarea de la app en la pantalla de Recientes.
Varias instancias de actividad
Como 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 envía a la pila, en lugar de llevar cualquier instancia anterior de la actividad a la parte superior. Por lo tanto, se pueden crear varias instancias de una actividad en 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 la IU. Sin embargo, puedes modificar este comportamiento si no quieres que se cree una instancia de una actividad más de una vez. Para obtener más información, consulta Administra tareas.
Entornos multiventana
Cuando las apps se ejecutan de forma simultánea 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 ocurre con las apps para Android que se ejecutan en Chromebooks: el sistema administra las tareas o los grupos de tareas por ventana.
Resumen del ciclo de vida
A continuación, se resume el comportamiento predeterminado de las actividades y tareas:
Cuando la actividad A inicia la actividad B, la actividad A se detiene, pero el sistema conserva su estado, como la posición de desplazamiento y el texto que se ingresó en los formularios. Si el usuario presiona o usa el gesto Atrás mientras está en la actividad B, la actividad A se reanuda con su estado restablecido.
Cuando el usuario sale de una tarea con el botón o el gesto de inicio, se detiene la actividad actual y su tarea pasa a segundo plano. El sistema conserva el estado de cada actividad de la tarea. Si el usuario reanuda la tarea más tarde seleccionando el ícono de selector que la inició, la tarea pasa al primer plano y reanuda la actividad en la parte superior de la pila.
Si el usuario presiona Atrás o realiza el gesto correspondiente, 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 en Android 12 (nivel de API 31) y versiones posteriores.
Puedes 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 iniciadas de forma sucesiva en la misma tarea, en una pila de tipo "último en entrar, 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 ni por cómo existen en la pila de actividades.
Sin embargo, es posible que decidas interrumpir el comportamiento normal. Por ejemplo, tal vez quieras que una actividad de tu app comience una tarea nueva cuando se inicie, en lugar de ubicarse dentro de la tarea actual. O bien, cuando inicias una actividad, es posible que quieras mostrar una instancia existente de ella, en lugar de crear una nueva en la parte superior de la pila de actividades. O bien, es posible que quieras que se borren todas las actividades de la pila de actividades, excepto la actividad raíz, cuando el usuario abandone la tarea.
Puedes hacer estas cosas y mucho más con los atributos del elemento del manifiesto <activity> y las marcas del intent que pasas a startActivity().
Estos son los atributos principales de <activity> que puedes usar para administrar
tareas:
taskAffinitylaunchModeallowTaskReparentingclearTaskOnLaunchalwaysRetainTaskStatefinishOnTaskLaunch
Estas son las principales marcas de intents que puedes usar:
En las siguientes secciones, se explica 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.
En las siguientes secciones, también se analiza cómo el sistema representa y administra las tareas y actividades en la pantalla de Recientes. Normalmente, dejas que el sistema defina cómo se representan tu tarea y tus actividades en la pantalla Recientes, y no necesitas modificar este comportamiento. Para obtener más información, consulta Pantalla Recientes.
Cómo definir modos de inicio
Los modos de inicio 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:
-
Cuando declaras una actividad en tu archivo de manifiesto, puedes especificar cómo se asocia la actividad con las tareas cuando se inicia.
-
Cuando llamas a
startActivity(), puedes incluir una marca en elIntentque declare cómo (o si) la actividad nueva se asocia con la tarea actual.
Por lo tanto, si la actividad A inicia la actividad B, esta última 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 cómo se asocia la actividad B con una tarea, se respeta la solicitud de la actividad A, como se define en el intent, por sobre la solicitud de la actividad B, como se define en su manifiesto.
Define los modos de inicio 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 usando el atributo launchMode del elemento <activity>.
Existen cinco modos de inicio que puedes asignar al atributo launchMode:
"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 varias instancias de la actividad, cada una de las cuales puede pertenecer a diferentes tareas, y una tarea puede tener varias instancias.
"singleTop"- Si ya existe una instancia de la actividad en la parte superior de la tarea actual, el sistema dirige 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 varias instancias de la actividad, cada una de las cuales puede pertenecer a diferentes tareas, y una tarea puede tener varias instancias (pero solo si la actividad que se encuentra 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 las 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 predeterminado
"standard", 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 deonNewIntent(), ya que se encuentra en la parte superior de la pila, y la pila sigue siendo 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"."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 hacia la instancia existente a través de una llamada a su método
onNewIntent(), en lugar de crear una nueva. Mientras tanto, se destruyen todas las demás actividades que se encuentran por encima de ella.
"singleInstance"- El comportamiento es el mismo que para
"singleTask", salvo que el sistema no inicia otras actividades en la tarea que contiene la instancia. La actividad siempre es el único miembro de su tarea. Cualquier actividad que se inicie con esta se abrirá en una tarea independiente.
"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 establece el indicadorFLAG_ACTIVITY_MULTIPLE_TASKoFLAG_ACTIVITY_NEW_DOCUMENT.
Por ejemplo, el navegador de Android especifica singleTask en <activity> para que su actividad no se coloque en la tarea de tu app. En cambio, se inicia una nueva tarea para el navegador o se muestra una tarea en segundo plano existente del navegador.
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 lanzamiento singleTask y existe una instancia de esa actividad en una tarea en segundo plano, toda esa tarea se lleva al primer plano. En este punto, la pila de actividades incluye todas las actividades de la tarea que se trajo al frente en la parte superior de la pila. En la Figura 4, se muestra este tipo de situación.
"singleTask" Si la actividad ya forma parte de una tarea en segundo plano con su propia pila de actividades, toda esa pila también se adelanta, por encima de 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 del elemento <activity>.
Cómo definir modos de inicio con marcas de Intent
Cuando inicias una actividad, puedes modificar su asociación predeterminada con su tarea incluyendo marcas en el intent que entregas a startActivity(). Las marcas que puedes usar para modificar el comportamiento predeterminado
son las siguientes:
FLAG_ACTIVITY_NEW_TASKEl sistema inicia la actividad en una tarea nueva. Si ya se está ejecutando una tarea para la actividad que se está iniciando, esa tarea se lleva al primer plano con su último estado restablecido, y la actividad recibe el nuevo intent en
onNewIntent().Esto produce el mismo comportamiento que el valor
launchModede"singleTask"analizado en la sección anterior.FLAG_ACTIVITY_SINGLE_TOPSi 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 nueva instancia de la actividad.Esto produce el mismo comportamiento que el valor
launchMode"singleTop"que se analizó en la sección anterior.FLAG_ACTIVITY_CLEAR_TOPSi 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 que se encuentran por encima de ella. El intent se entrega a la instancia reanudada de la actividad, que ahora se encuentra en la parte superior, a través de
onNewIntent().No hay ningún valor para el atributo
launchModeque produzca este comportamiento.FLAG_ACTIVITY_CLEAR_TOPse usa con mayor frecuencia junto conFLAG_ACTIVITY_NEW_TASK. Cuando se usan juntas, estos indicadores ubican una actividad existente en otra tarea y la colocan en una posición en la que puede responder al intent.
Cómo controlar las 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 a las actividades definidas en la misma app se les pueden asignar diferentes afinidades de tareas.
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 del paquete predeterminado declarado en el elemento <manifest>, ya que el sistema usa ese nombre para identificar la afinidad de tareas 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_TASKDe forma predeterminada, se inicia una actividad nueva en la tarea de la actividad que llamó a
startActivity(). Se envía a la misma pila de actividades que el emisor.Sin embargo, si el intent que se pasa a
startActivity()contiene la marcaFLAG_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 hay una tarea existente con la misma afinidad que la actividad nueva, esta se inicia en esa tarea. De lo contrario, se inicia una nueva tarea.Si esta marca hace que una actividad inicie una tarea nueva y el usuario usa el botón de inicio o gesto para salir de ella, debe haber alguna forma 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 la suya, por lo que siempre colocan
FLAG_ACTIVITY_NEW_TASKen los intents que pasan astartActivity().Si una entidad externa puede invocar tu actividad con esta marca, proporciona a los usuarios una forma independiente de regresar, como un ícono de selector de un filtro
CATEGORY_LAUNCHERen la actividad raíz de la tarea. Para obtener más información, consulta Inicia una tarea.Cuando una actividad tiene su atributo
allowTaskReparentingestablecido en"true"En este caso, la actividad puede moverse de la tarea en la que se inicia a la tarea por la que tiene afinidad cuando esta pasa a 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 predeterminada de la app, y se puede volver a asignar 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 meteorológico se reasigna a esa tarea y se muestra dentro de ella.
Borra la pila de actividades
Si el usuario abandona 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 según la suposición de que, después de un período prolongado, los usuarios abandonaron lo que estaban haciendo antes y regresan a la tarea para comenzar algo nuevo.
Existen algunos atributos de actividad que puedes usar 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 acaba de describir. La tarea conserva todas las actividades de su pila, incluso después de un período prolongado. clearTaskOnLaunchCuando 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 sale de la tarea y regresa a ella. En otras palabras, es lo opuesto aalwaysRetainTaskState. El usuario siempre regresa a la tarea en su estado inicial, incluso después de abandonarla por un momento.finishOnTaskLaunchEste atributo es similar a
clearTaskOnLaunch, pero opera en una sola actividad, no en una tarea completa. También puede hacer que finalice cualquier actividad, excepto la raíz. Cuando se establece en"true", la actividad sigue formando parte de la tarea solo durante la sesión actual. Si el usuario sale de la tarea y luego regresa a ella, esta ya no estará presente.
Cómo iniciar una tarea
Puedes configurar una actividad como punto de entrada para una tarea si le asignas 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>
Este filtro de intents muestra un ícono y una etiqueta para la actividad en el selector de aplicaciones, lo que les permite a los usuarios iniciar la actividad y volver a la tarea que crea.
Esta segunda habilidad es importante. Los usuarios deben poder salir de una tarea y, luego, volver a ella con este selector de actividades. Por este motivo, solo usa los dos modos de lanzamiento que marcan las actividades como que siempre inician una tarea, "singleTask" y "singleInstance", cuando la actividad tiene un filtro ACTION_MAIN y un filtro CATEGORY_LAUNCHER.
Por ejemplo, imagina lo que podría suceder si faltara el filtro: un intent inicia una actividad "singleTask", lo que inicia una tarea nueva, y el usuario pasa un tiempo trabajando en esa tarea. Luego, el usuario usa el botón o el gesto de inicio. La tarea ahora se envía al segundo plano y deja de estar visible. Ahora, el usuario no tiene forma de volver a la tarea, ya que 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 atributo finishOnTaskLaunch del elemento <activity> en "true". Para obtener más información, consulta Cómo borrar la pila de actividades.
Para obtener más información sobre cómo se representan y administran las tareas y actividades en la pantalla Recientes, consulta Pantalla Recientes.