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.
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 asuper.onBackPressed()
en lugar de finalizar. Llamar asuper.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
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:
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.
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
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:
taskAffinity
launchMode
allowTaskReparenting
clearTaskOnLaunch
alwaysRetainTaskState
finishOnTaskLaunch
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:
Mediante el archivo de manifiesto
Cuando declaras una actividad en el 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 enIntent
que declara cómo (o si) la nueva actividad se asocia con la tarea actual.
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
:
"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.
"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 deonNewIntent()
, 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"
."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.
"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.
"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 marcaFLAG_ACTIVITY_MULTIPLE_TASK
oFLAG_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.
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 conFLAG_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:
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 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 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 astartActivity()
.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.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 aalwaysRetainTaskState
. 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.