Introducción a las actividades

La clase Activity es un componente fundamental de una app para Android, y la forma en que se inician y se crean las actividades es una parte fundamental del modelo de aplicación de la plataforma. A diferencia de los paradigmas de programación en los que las apps se inician con un método main, el sistema Android inicia el código en una instancia de Activity invocando métodos de devolución de llamada específicos que corresponden a etapas específicas de su ciclo de vida.

En este documento, se introduce el concepto de actividades y se proporciona una guía simple sobre cómo trabajar con ellas. Para obtener información adicional sobre las prácticas recomendadas para diseñar la arquitectura de tu app, consulta la Guía de arquitectura de apps.

El concepto de actividades

La experiencia con la app para dispositivos móviles difiere de la versión de escritorio, ya que la interacción del usuario con la app no siempre comienza en el mismo lugar. En cambio, el recorrido del usuario suele comenzar de forma no determinística. Por ejemplo, si abres una app de correo electrónico desde la pantalla principal, es posible que veas una lista de correos electrónicos. Por el contrario, si usas una app de redes sociales que luego inicia tu app de correo electrónico, es posible que accedas directamente a la pantalla de la app de correo electrónico para redactar uno.

La clase Activity está diseñada para facilitar este paradigma. Cuando una app invoca a otra, la app que realiza la llamada invoca una actividad en la otra app, en lugar de a la app en sí. De esta manera, la actividad sirve como el punto de entrada para la interacción de una app con el usuario. Implementas una actividad como una subclase de la clase Activity.

Una actividad proporciona la ventana en la que la app dibuja su IU. Por lo general, esta ventana llena la pantalla, pero puede ser más pequeña y flotar sobre otras ventanas.

Por lo general, una actividad en una app se especifica como la actividad principal, que es la primera pantalla que aparece cuando el usuario inicia la app. En las apps modernas de Compose, esta es la única actividad necesaria, ya que aloja elementos componibles en una arquitectura de una sola actividad en lugar de poseer una jerarquía de vistas. En lugar de que la app tenga varias actividades para las pantallas, los elementos componibles en el host de la actividad componen varios destinos de navegación.

Si quieres usar actividades en tu app, debes registrar información sobre estas en el manifiesto de la app. Además, es una buena práctica conocer los ciclos de vida de las actividades. En el resto de este documento, se presentan estos temas.

Cómo configurar el manifiesto

Para que tu app pueda usar actividades, debes declararlas, y también declarar algunos de sus atributos, en el manifiesto.

Cómo declarar actividades

Para declarar tu actividad, abre tu archivo de manifiesto y agrega un elemento <activity> como secundario del elemento <application>. Por ejemplo:

<manifest ... >
  <application ... >
      <activity android:name=".ExampleActivity" />
      ...
  </application ... >
  ...
</manifest >

El único atributo obligatorio para este elemento es android:name, que especifica el nombre de la clase de la actividad. También puedes agregar atributos que definan las características de la actividad, como una etiqueta, un ícono o un tema de IU. Para obtener más información sobre estos y otros atributos, consulta la documentación de referencia del elemento <activity>.

Cómo declarar filtros de intents

Los filtros de intents son una función muy útil de la plataforma de Android. Estos proporcionan la capacidad de iniciar una actividad no solo en función de una solicitud explícita, sino también de una implícita. Por ejemplo, una solicitud explícita podría indicar al sistema que debe "Iniciar la actividad 'Enviar correo electrónico' en la app de Gmail". En cambio, una solicitud implícita le indica al sistema que debe "Iniciar una pantalla 'Enviar correo electrónico' en cualquier actividad que pueda realizar la tarea". Cuando la IU del sistema le pregunta al usuario qué app debe usar para realizar la tarea, ese es un ejemplo de un filtro de intent.

Para aprovechar esta función, declara un atributo <intent-filter> en el elemento <activity>. La definición de este elemento incluye un elemento <action> y, de manera opcional, un elemento <category> o <data>. Estos elementos se combinan para especificar el tipo de intent al que puede responder la actividad. Por ejemplo, el siguiente fragmento de código muestra cómo configurar una actividad que envía datos de texto y correos electrónicos, y recibe solicitudes de otras actividades para hacerlo:

<activity android:name=".ExampleActivity" android:icon="@drawable/app_icon">
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="text/plain" />
    </intent-filter>
   <intent-filter>
        <action android:name="android.intent.action.SENDTO" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:scheme="mailto" />
    </intent-filter>
</activity>

En este ejemplo, el elemento <action> especifica que esta actividad envía datos. La declaración del elemento <category> como DEFAULT permite a la actividad recibir solicitudes de inicio. El elemento <data> especifica el tipo de datos que puede enviar esta actividad. En el siguiente fragmento de código, se muestra cómo llamar a la actividad descrita anteriormente para redactar un correo electrónico:

fun composeEmail(addresses: Array<String>, subject: String) {
    val intent = Intent(Intent.ACTION_SENDTO).apply {
        data = Uri.parse("mailto:") // Only email apps handle this.
        putExtra(Intent.EXTRA_EMAIL, addresses)
        putExtra(Intent.EXTRA_SUBJECT, subject)
    }
    if (intent.resolveActivity(packageManager) != null) {
        startActivity(intent)
    }
}

Si deseas que tu app sea independiente y que no permita que otras apps activen sus actividades, no necesitas filtros de intents adicionales. Las actividades que no quieras que estén disponibles para otras aplicaciones no deben incluir filtros de intents, y puedes iniciarlas por medio de intents explícitos. Para obtener más información sobre cómo tus actividades pueden responder a los intents, consulta Intents y filtros de intents.

Cómo controlar intents entrantes

En el siguiente ejemplo, se muestra un patrón para administrar el ciclo de vida de la actividad mientras se controlan varios tipos de intents: recursos compartidos de texto único, imágenes únicas y arrays de varias imágenes. Al enrutar estas entradas variadas a través de una función handleIntent centralizada, se garantiza que las acciones ACTION_SEND y ACTION_SEND_MULTIPLE se analicen y deleguen correctamente al ViewModel para una actualización reactiva de la IU.

class ExampleActivity : ComponentActivity() {
  private val viewModel: MyViewModel by viewModels()

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    handleIntent(intent)
    setContent {
      ComposeApp(viewModel)
    }
  }

  override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    setIntent(intent)
    handleIntent(intent)
  }

  private fun handleIntent(intent: Intent?) {
    when (intent?.action) {
      Intent.ACTION_SEND -> {
        if ("text/plain" == intent.type) {
          intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
            viewModel.handleText(it) // Update UI to reflect text being shared
          }
        } else if (intent.type?.startsWith("image/") == true) {
          (intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java))?.let {
            viewModel.handleImage(it) // Update UI to reflect image being shared
          }
        }
      }

      Intent.ACTION_SEND_MULTIPLE -> {
          if (intent.type?.startsWith("image/") == true) {
              intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM, Uri::class.java)?.let {
                  viewModel.handleMultipleImages(it) // Update UI to reflect multiple images being shared
              }
          } else {
              // Handle other types
          }
      }

      else -> {
          // Handle other intents
      }
    }
  }
}

Cómo declarar permisos

Puedes usar la etiqueta <activity> del manifiesto para controlar qué apps pueden iniciar una actividad en particular. Una actividad principal no puede iniciar una actividad secundaria, a menos que ambas tengan los mismos permisos en su manifiesto. Si declaras un elemento <uses-permission> para una actividad principal, cada actividad secundaria debe tener un elemento <uses-permission> coincidente.

Por ejemplo, si tu app quiere usar una app hipotética llamada SocialApp para compartir una publicación en las redes sociales, SocialApp debe definir el mismo permiso que la app que la llama debe tener:

<manifest>
<activity android:name="...."
   android:permission="com.google.socialapp.permission.SHARE_POST"

/>

Luego, para poder llamar a SocialApp, tu app debe tener el mismo conjunto de permisos que se estableció en el manifiesto de SocialApp:

<manifest>
   <uses-permission android:name="com.google.socialapp.permission.SHARE_POST" />
</manifest>

Para obtener más información sobre los permisos y la seguridad en general, consulta la Lista de verificación de seguridad.

Cómo administrar el ciclo de vida de la actividad

A lo largo de su vida útil, una actividad pasa por varios estados. Para administrar las transiciones entre estados, debes usar una serie de devoluciones de llamadas. En las siguientes secciones, se presentan estas devoluciones de llamadas. En una app de Compose, no se recomienda conectarse directamente a estas devoluciones de llamada. En su lugar, usa la API de Lifecycle para observar los cambios de estado. Para obtener más información, consulta Cómo integrar el ciclo de vida con Compose.

onCreate

Debes implementar esta devolución de llamada, que se activa cuando el sistema crea tu actividad. Tu implementación debe inicializar los componentes esenciales de tu actividad. Por ejemplo, tu app debería crear vistas y vincular datos a listas aquí.

En una app de Compose, usa esta devolución de llamada para configurar tu elemento componible host con setContent, como se muestra a continuación:

class MyActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text(text = stringResource(id = R.string.greeting))
        }
    }
}

Cuando finaliza onCreate, la siguiente devolución de llamada siempre es onStart.

onStart

Cuando se cierra onCreate, la actividad pasa al estado Iniciada y se vuelve visible para el usuario. Esta devolución de llamada contiene los preparativos finales de la actividad para pasar al primer plano y convertirse en interactiva.

onResume

El sistema invoca esta devolución de llamada justo antes de que la actividad comience a interactuar con el usuario. En este punto, la actividad se encuentra en la parte superior de la pila de actividades y captura todas las entradas del usuario. La mayor parte de la funcionalidad principal de una app se implementa en el método onResume.

La devolución de llamada onPause siempre sigue a onResume.

onPause

El sistema llama a onPause cuando la actividad pierde el foco y pasa al estado Paused. Este estado se produce, por ejemplo, cuando el usuario presiona el botón Atrás o Recientes. Cuando el sistema llama a onPause para tu actividad, significa que esta aún está parcialmente visible, pero, a menudo, indica que el usuario está saliendo de la actividad y que esta pronto pasará al estado Detenida o Reanudada.

Una actividad en estado Detenida puede continuar con la actualización de la IU si el usuario espera que esta acción ocurra. Entre los ejemplos de tal actividad, se incluye mostrar una pantalla de mapa de navegación o la reproducción de un reproductor multimedia. Incluso si estas actividades pierden el foco, el usuario espera que su IU continúe actualizándose.

No debes usar onPause para guardar datos de la aplicación o datos del usuario, realizar llamadas de red o ejecutar transacciones de base de datos. Para obtener información sobre cómo guardar datos, consulta Cómo guardar y restablecer el estado transitorio de la IU.

Una vez que finalice la ejecución de onPause, la siguiente devolución de llamada será onStop o onResume, según lo que ocurra después de que la actividad entre en el estado Paused.

onStop

El sistema llama a onStop cuando la actividad ya no es visible para el usuario. Esto puede ocurrir porque se está destruyendo la actividad, porque se inicia una nueva o porque una ya existente pasa al estado Reanudada y cubre la actividad que se detuvo. En todos estos casos, la actividad detenida ya no está visible en absoluto.

La siguiente devolución de llamada que el sistema invoca puede ser onRestart (si la actividad vuelve a interactuar con el usuario) o onDestroy (si esta actividad finaliza por completo).

onRestart

El sistema invoca esta devolución de llamada cuando una actividad en estado Detenida está por volver a iniciarse. onRestart restablece el estado de la actividad desde el momento en que se detuvo.

Esta devolución de llamada siempre va seguida de onStart.

onDestroy

El sistema invoca esta devolución de llamada antes de que se destruya una actividad.

Esta devolución de llamada es la última que recibe la actividad. onDestroy suele implementarse para garantizar que todos los recursos de una actividad se liberen cuando esta, o el proceso que la contiene, se elimina.

En esta sección, solo se proporciona una introducción a este tema. Para obtener un análisis más detallado del ciclo de vida de la actividad y sus devoluciones de llamadas, consulta El ciclo de vida de la actividad.