En Android, casi todo lo que hace tu app para hablar con otros componentes o con otras aplicaciones pasa por un mismo protagonista: el Intent. Da igual que quieras abrir la cámara, lanzar una nueva pantalla, compartir un texto en redes sociales o programar una alarma: en todos esos casos hay un Intent detrás moviendo los hilos.
Dominar los intents, tanto implícitos como explícitos, es clave para moverte con soltura por la plataforma. En esta guía vamos a entrar a fondo en el arte de la navegación en Android, viendo cómo funcionan por dentro, cómo se crean, cómo se resuelven, cómo se declaran en el manifiesto y qué implicaciones de seguridad y buenas prácticas debes tener en cuenta en las versiones modernas de Android.
¿Qué es realmente un Intent en Android?
Un Intent es, en esencia, un objeto de mensajería que describe una operación que se quiere realizar. No es más que un sobre con información: qué acción se quiere hacer, sobre qué datos, con qué extras, con qué flags y (a veces) qué componente concreto debe ejecutarla.
En el ecosistema Android, los intents sirven como pegamento entre cuatro grandes tipos de componentes: Activities (pantallas de UI), Services (lógica en segundo plano), BroadcastReceivers (receptores de emisiones) y ContentProviders (acceso estructurado a datos). Gracias a ellos, cualquier componente puede pedir a otro que haga algo, sin necesidad de conocerlo en detalle.
Imagina un partido de fútbol donde el balón va de jugador en jugador hasta la portería. El balón es como el Intent: viaja entre componentes, lleva información y dispara acciones. Tu app puede lanzar intents a sus propias pantallas, a servicios internos, a apps de terceros o al propio sistema.
Casos de uso fundamentales de los Intents
En la práctica, casi todo se puede reducir a tres grandes usos: iniciar actividades, iniciar o vincular servicios y enviar broadcasts. Vamos a verlos con calma.
1. Iniciar una Activity
Una Activity representa una única pantalla de la interfaz. Para abrirla desde otra actividad, utilizas startActivity(intent) con un Intent adecuado. Ese Intent describe qué actividad lanzar y qué datos necesita.
Si además quieres que la actividad destino devuelva un resultado (por ejemplo, una foto, un texto o una selección del usuario), puedes usar startActivityForResult() (en código heredado) o los Activity Result APIs modernos. El resultado vuelve encapsulado en otro Intent, accesible en el callback correspondiente (en código antiguo, onActivityResult()).
En una navegación típica entre pantallas, sueles usar intents explícitos (nombrando la Activity destino) y pasar datos mediante extras. Esto aplica tanto si estás saltando de MainActivity a DetailActivity como en ejemplos educativos tipo formulario → pantalla de resultados.
2. Iniciar o vincular un Service
Un Service es un componente sin interfaz gráfica que ejecuta operaciones en segundo plano: descargas, sincronizaciones, reproducción de audio, etc. En versiones previas a Android 5.0 podías iniciar servicios directamente con startService(intent); hoy en día, para trabajos programados, se recomienda JobScheduler y APIs modernas como WorkManager.
Cuando inicias un servicio con startService(), el Intent indica qué servicio debe arrancar y con qué datos, por ejemplo un servicio de descarga que recibe la URL del archivo. Si el servicio expone una interfaz cliente-servidor, puedes vincularte a él con bindService(intent), recibiendo un IBinder para comunicarte de forma más directa.
En servicios la norma es clara: usa siempre intents explícitos y no declares filtros de intent en el servicio, por motivos de seguridad. De hecho, desde Android 5.0 se lanza una excepción si intentas hacer bindService() con un intent implícito.
3. Enviar y recibir broadcasts
Una emisión (broadcast) es un mensaje que puede recibir cualquier app interesada. El sistema lanza broadcasts para eventos como el arranque del dispositivo, la conexión del cargador, cambios de red, etc. Tú también puedes mandar tus propios broadcasts a otras apps o a tu propia app.
Para lanzar una transmisión se usan métodos como sendBroadcast() o sendOrderedBroadcast(). El Intent que envías describe la acción del evento y los datos asociados. Cualquier BroadcastReceiver cuyo filtro coincida puede recibirlo y reaccionar.
Los receptores se pueden registrar estáticamente en el AndroidManifest con <intent-filter>, o dinámicamente en tiempo de ejecución con registerReceiver(). Registrarlos dinámicamente te permite escuchar solo durante un periodo concreto (por ejemplo, mientras la app está visible).
Tipos de Intents: Explícitos vs Implícitos
Android distingue entre dos grandes familias de intents: explícitos e implícitos. Entender la diferencia es básico para navegar correctamente entre pantallas o saltar a otras apps.
Intents explícitos
Un intent explícito especifica exactamente qué componente debe responder, normalmente mediante un ComponentName o pasando la clase destino en el constructor Intent(Context, Class). Esto se usa casi siempre para navegar dentro de tu propia app, donde conoces las clases que quieres lanzar.
Ejemplo típico: iniciar un DownloadService interno o pasar de MainActivity a ShowActivity con datos extra. En código, algo tipo:
Intent intent = new Intent(this, ShowActivity.class);
startActivity(intent);
Este tipo de intents son ideales para controlar totalmente el flujo interno de tu app. No necesitas filtros de intent en el manifiesto si siempre los llamas de forma explícita, y de hecho muchos componentes internos se diseñan así, sin estar expuestos hacia fuera.
Intents implícitos
Un intent implícito no indica un componente concreto, sino que declara una acción general y opcionalmente unos datos: ver una web, compartir un texto, realizar una búsqueda, marcar un número, abrir un mapa, hacer una foto, etc. Es el sistema quien decide qué app (o lista de apps) puede encargarse de esa acción.
Por ejemplo:
- Abrir una URL en un navegador:
ACTION_VIEWcon una URIhttp://.... - Enviar un email preconfigurado:
ACTION_SENDcon tipo MIMEmessage/rfc822y extras para asunto y destinatario. - Abrir Google Maps en unas coordenadas:
ACTION_VIEWcon un URIgeo:lat,long. - Mostrar los contactos del dispositivo:
ACTION_VIEWcon un URIcontent://contacts/people/.
Cuando lanzas un intent implícito con startActivity(), Android compara su contenido con los filtros de intent declarados en los AndroidManifest.xml de las apps instaladas. Si hay varias candidatas, aparece el selector de apps para que el usuario elija. Si solo hay una, se abre directamente. Si ninguna coincide, se lanza ActivityNotFoundException.
Componentes clave de un Intent
Un mismo objeto Intent puede contener muchos campos, pero hay cuatro que son los que determinan la resolución: componente, acción, datos y categorías. Luego están los extras y las flags, que no afectan a la resolución, pero sí al comportamiento.
Nombre del componente
El nombre del componente es opcional, pero cuando lo estableces conviertes el Intent en explícito. Internamente se representa como un ComponentName que incluye el paquete y la clase, por ejemplo com.ejemplo.app.ExampleActivity.
Lo puedes configurar con métodos como setComponent(), setClass() o setClassName(), o usando el constructor Intent(Context, Class). Para servicios, como ya hemos comentado, es obligatorio por seguridad.
Acción
La acción es una cadena que describe de forma genérica qué se quiere hacer. Android define muchas acciones estándar en la clase Intent y en otras clases del framework (por ejemplo, algunas pantallas de Ajustes tienen sus propias acciones).
Algunas de las más habituales al trabajar con actividades son:
ACTION_VIEW: visualizar algo (una web, una foto, una dirección en un mapa…).ACTION_SEND: compartir contenido con otra app (correo, redes sociales, apps de mensajería…).ACTION_EDIT: editar un recurso existente.ACTION_WEB_SEARCH: realizar una búsqueda web.ACTION_DIALyACTION_CALL: marcar un número o llamar directamente.
Puedes definir tus propias acciones para que otras partes de tu app (o apps externas) invocan componentes tuyos de forma semántica. En ese caso, conviene usar como prefijo el nombre de tu paquete, por ejemplo com.mipaq.app.action.MOSTRAR_DETALLE.
Datos: URI y tipo MIME
Los datos se representan mediante un URI (objeto Uri) y/o un tipo MIME. Ambos pueden ir combinados para describir con precisión qué se va a manipular.
La combinación típica es algo como:
ACTION_VIEW+ URIhttp://ejemplo.com→ abrir navegador.ACTION_VIEW+ URIgeo:lat,long→ abrir mapas.ACTION_EDIT+ URI de un documento → editar ese documento.ACTION_SEND+ tipo MIMEtext/plain+ extra de texto → compartir texto.
Para establecer los datos tienes métodos como setData() (solo URI), setType() (solo MIME) y setDataAndType() (ambos). Importante: si necesitas URI + MIME, usa siempre setDataAndType(). Llamar a setData() y luego setType() (o al revés) hace que uno anule al otro.
Cuando el URI es de tipo content:, el sistema puede inferir el MIME consultando el ContentProvider asociado. Esto le ayuda a resolver mejor qué app puede manejar ese recurso.
Categorías
Las categorías son cadenas adicionales que indican qué tipo de componente debe manejar el intent. Se suelen usar mucho en filtros de intent del manifiesto. Ejemplos típicos:
CATEGORY_BROWSABLE: permite que un navegador inicie la actividad para mostrar datos referenciados por un enlace.CATEGORY_LAUNCHER: marca la actividad que aparece en el launcher como punto de entrada principal.CATEGORY_DEFAULT: la categoría por defecto para intents implícitos iniciados constartActivity().
En el código, añades categorías con addCategory(). En los filtros de intent del manifiesto se declaran con elementos <category>.
Extras y Bundle
Los extras son pares clave-valor que viajan dentro del Intent para aportar información adicional necesaria para la acción: asunto de un email, texto a compartir, parámetros de configuración, etc.
Se añaden con los métodos sobrecargados putExtra() o construyendo un Bundle que luego se pasa con putExtras(). En la Activity o servicio receptor los recuperas con getIntent().getExtras() o con métodos tipo getStringExtra(), getIntExtra(), etc.
El framework define muchas claves estándar EXTRA_* (como EXTRA_EMAIL, EXTRA_SUBJECT, EXTRA_TEXT…), y si defines tus propias claves también es buena idea usar tu paquete como prefijo para evitar colisiones, por ejemplo com.mipaq.app.EXTRA_ID_PRODUCTO.
Si esperas que tu Intent lo reciba otra app, evita usar objetos Parcelable o Serializable personalizados en los extras: si la otra app no tiene acceso a esas clases, se lanzará un RuntimeException al deserializar el Bundle.
Flags
Las flags son marcas que se añaden con setFlags() o addFlags() y actúan como metadatos. Indican al sistema cómo iniciar la actividad (nueva tarea, reutilizar existente, limpiar la pila, etc.) o cómo tratarla después (si debe aparecer en recientes, comportamiento de back, múltiples tareas, etc.).
Por ejemplo, FLAG_ACTIVITY_NEW_TASK abre la actividad en una nueva tarea, mientras que FLAG_ACTIVITY_CLEAR_TOP limpia la pila por encima de una actividad ya existente en la tarea.
Declarar y recibir Intents implícitos con filtros
Si quieres que otras apps o el propio sistema puedan invocar componentes tuyos con intents implícitos, necesitas declarar filtros de intent en tu manifest.
Cada <intent-filter> indica qué combinación de acción, datos y categorías acepta el componente. El sistema solo entregará un intent implícito a tu Activity, Service o BroadcastReceiver si ese intent pasa al menos uno de los filtros configurados.
Estructura básica de un <intent-filter>
Dentro de un componente (por ejemplo, <activity>) puedes incluir uno o más filtros:
<action android:name="..." />→ acción aceptada.<category android:name="..." />→ categoría aceptada.<data ... />→ descripción del formato de URI y/o MIME aceptado.
Además, desde Android 12 es obligatorio establecer explícitamente android:exported="true|false" en cualquier componente que tenga filtros de intent, indicando si es accesible desde fuera de tu app. Si no lo haces, la app ni siquiera se instalará.
Ejemplo típico de actividad que recibe un ACTION_SEND de texto:
<activity android:name="ShareActivity" android:exported="false">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
Fíjate en que incluimos la categoría CATEGORY_DEFAULT. Los intents implícitos que se lanzan con startActivity() se tratan como si tuvieran esa categoría, así que si tu filtro no la declara, tu actividad no recibirá esos intents.
Combinar varios filtros y casos de uso
Un mismo componente puede tener varios filtros para manejar distintos escenarios, siempre que el código esté preparado para interpretarlos. Por ejemplo, una actividad de compartir multimedia podría tener:
- Un filtro para
ACTION_SENDcon texto plano. - Otro filtro para
ACTION_SENDyACTION_SEND_MULTIPLEcon tiposimage/*,video/*y un tipo específico comoapplication/vnd.google.panorama360+jpg.
De esta forma, la misma actividad puede encargarse de texto simple y contenido multimedia, pero diferenciando la lógica según la acción y el tipo MIME que reciba en el Intent.
Cómo Android resuelve un Intent implícito
Cuando el sistema recibe un intent implícito para iniciar una actividad, lo compara con los filtros declarados en los manifiestos de las apps instaladas. La resolución pasa por tres pruebas sucesivas: acción, categoría y datos. Si alguna falla para un filtro concreto, ese filtro se descarta.
Prueba de acción
El filtro puede declarar cero o más acciones con elementos <action>. Para que la prueba de acción pase, la acción del Intent debe coincidir con una de las declaradas.
Si el filtro no declara ninguna acción, ningún Intent pasará esta prueba. Si el Intent no especifica acción, pasa la prueba siempre que el filtro tenga al menos una acción.
Prueba de categoría
En categorías, la regla es la inversa: todas las categorías del Intent deben estar en el filtro, pero el filtro puede declarar más categorías de las que trae la intención. Si el Intent no tiene categorías, siempre pasa la prueba de categoría.
Recuerda que Android añade automáticamente CATEGORY_DEFAULT a todos los intents implícitos usados con startActivity() o startActivityForResult(), por lo que cualquier actividad que quiera recibirlos debe declarar esa categoría en su filtro.
Prueba de datos: URI y MIME
La parte de datos es la más completa, porque incluye tanto el URI como el tipo MIME. Cada <data> puede especificar:
- Esquema (
scheme), por ejemplo http, https, content, file, geo, tel… - Host, puerto y ruta del URI (con posibilidad de comodines en la ruta).
- Tipo de datos (
mimeType), por ejemploimage/*,video/mpeg, etc.
Algunas reglas clave:
- Si el Intent no tiene ni URI ni MIME, pasa solo si el filtro tampoco especifica ninguno.
- Si trae URI pero no MIME, pasa si el URI coincide con el patrón del filtro y este no tiene MIME.
- Si trae MIME pero no URI, pasa si el filtro declara ese MIME y no indica URI.
- Si trae URI y MIME, debe coincidir el tipo MIME con el del filtro y, además, o bien el URI coincide con el patrón del filtro, o el URI es content:/file: y el filtro solo especifica MIME sin URI.
Esto último refleja la expectativa de que quien puede manejar un cierto tipo de datos normalmente también pueda obtenerlos desde un ContentProvider o un archivo local, aunque el filtro no incluya explícitamente los esquemas content: o file:.
Uso avanzado de intents implícitos: ejemplos prácticos
Una de las gracias de los intents implícitos es que te permiten usar funcionalidades del sistema o de otras apps sin reinventar la rueda. Veamos algunos patrones muy comunes.
Abrir el navegador con una URL
Para abrir una web basta con crear un Intent con ACTION_VIEW y un URI http:// o https://. El sistema elegirá el navegador adecuado (o te dejará elegir si hay varios).
Este intent no dice “abre Chrome” o “abre Firefox”, dice simplemente: quiero ver esta URL. Cualquier app registrada como navegador puede responder.
Abrir la app de teléfono o llamar a un número
Existen dos acciones muy usadas con URIs tel::
ACTION_DIAL: abre la app de teléfono con el número marcado, pero no inicia la llamada.ACTION_CALL: marca y realiza directamente la llamada (requiere permisos de llamada).
En ambos casos el dato es una URI de la forma tel:123456789. ACTION_DIAL es menos intrusiva, ya que el usuario tiene que confirmar la llamada.
Mostrar contactos del dispositivo
Para listar los contactos puedes usar ACTION_VIEW con un URI content://contacts/people/. Ese URI representa la colección de contactos del dispositivo, y el Intent pedirá a cualquier app que los gestione que los muestre.
Realizar una búsqueda web
Con ACTION_WEB_SEARCH puedes lanzar una búsqueda en el navegador o en la app de búsqueda predeterminada. Además de la acción, sueles pasar la query como extra con SearchManager.QUERY.
Lanzar comandos de voz
Si quieres iniciar el reconocimiento o los comandos de voz del sistema, puedes usar un Intent con ACTION_VOICE_COMMAND. De nuevo, no dices qué app concreto se encarga, solo defines la intención de ejecutar un comando por voz.
Ejemplo didáctico: app con varios botones que disparan intents del sistema
En contextos educativos es muy típico crear una app con varios botones para practicar intents implícitos: abrir la web del instituto, lanzar el dialer sin número, llamar a un número concreto, abrir Maps en las coordenadas del centro, acceder a la cámara, enviar un email preconfigurado, enseñar contactos y buscar una ubicación en Maps. Cada botón crea un Intent con la acción y los datos apropiados, y con eso basta para reutilizar la funcionalidad de las apps ya instaladas.
En todos esos casos hay que recordar declarar en el AndroidManifest.xml los permisos necesarios (llamadas, cámara, localización, etc.) y, desde Android 6.0, solicitar permisos en tiempo de ejecución.
Seguridad y buenas prácticas con intents
A medida que Android ha ido subiendo de versión, las implicaciones de seguridad de los intents se han vuelto más estrictas. Hay varias cosas que debes cuidar si no quieres sorpresas.
Siempre intents explícitos para Services
Para servicios, la recomendación ya es norma: usa siempre intents explícitos y no declares filtros de intent en los servicios internos. Un servicio que pueda ser arrancado de forma ambigua por otras apps se convierte en un vector de ataque potencial.
Desde Android 5.0, si intentas hacer bindService() con un Intent implícito, el sistema lanza una excepción directa. Con startService(), aunque técnicamente sea posible en API antiguas, no es recomendable.
Evitar lanzamientos inseguros de intents anidados
Android 12 introduce una función de depuración para detectar lanzamientos inseguros de intents, especialmente cuando recibes un intent que contiene dentro otro intent como extra y lo lanzas tal cual sin validación.
Si tu app deserializa un Intent anidado de los extras y acto seguido lo usa en startActivity(), startService() o bindService(), el sistema puede considerarlo un incumplimiento de StrictMode (si lo tienes configurado con detectUnsafeIntentLaunch() o detectAll() en Android 12+).
Buenas prácticas para minimizar riesgos:
- Copia solo los extras que esperas y valida/limpia su contenido antes de pasarlos a un nuevo Intent.
- No exportes componentes innecesariamente: define
android:exported="false"siempre que sea posible, sobre todo si los vas a lanzar con intents internos. - Usa
PendingIntenten lugar de pasar intents anidados cuando quieras delegar futuras ejecuciones de código en otras apps.
Compatibilidad con Android 13 y match de acciones/categorías
En Android 13 y superior, para que una app pueda manejar un Intent de otra app, debe existir un <intent-filter> cuya acción y categorías coincidan con las del Intent. Si no se encuentra coincidencia, el sistema lanza ActivityNotFoundException y la app emisora debe gestionar esa situación.
Lo mismo se aplica en sentido inverso: si tu app recibe intents de fuera, solo se entregarán a componentes exportados con filtros que casen correctamente en acción y categorías, independientemente de la versión de destino de la app emisora.
PendingIntent: delegar la ejecución de un Intent
Un PendingIntent es un envoltorio alrededor de un Intent que permite a otra app (o al sistema) ejecutarlo con los permisos de tu proceso. Es esencial en notificaciones, widgets, alarmas y muchas integraciones del framework.
Casos de uso típicos:
- Notificaciones: para gestionar el click o acciones rápidas como responder a un mensaje.
- Widgets de escritorio: para responder a interacciones del usuario en el launcher.
- Alarmas programadas: usando
AlarmManagerpara disparar tu lógica en el futuro.
Cada PendingIntent está pensado para un tipo de componente: actividades, servicios o broadcast receivers. Por eso tienes métodos de fábrica como PendingIntent.getActivity(), getService() y getBroadcast(), que reciben un Context, el Intent base y unas flags.
Mutabilidad obligatoria desde Android 12
Si tu app se dirige a Android 12 o superior, cada PendingIntent debe marcarse como mutable o inmutable usando FLAG_MUTABLE o FLAG_IMMUTABLE. Si no lo haces, el sistema lanzará una IllegalArgumentException.
Lo recomendable es usar FLAG_IMMUTABLE siempre que no necesites modificar el Intent encapsulado. Un objeto inmutable protege frente a modificaciones por terceros; otra app no podrá cambiar qué se ejecuta exactamente.
Hay, sin embargo, escenarios donde necesitas un PendingIntent mutable:
- Respuestas directas en notificaciones (para que el sistema pueda inyectar datos de la respuesta).
- Notificaciones integradas con Android Auto o burbujas de conversación.
- Actualizaciones de localización con
requestLocationUpdates(), donde el sistema añade extras de ciclo de vida. - Alarmas repetitivas con
AlarmManager, donde se adjuntaEXTRA_ALARM_COUNT.
En esos casos, si necesitas mutabilidad, refuerza la seguridad usando un Intent explícito y rellenando el ComponentName con la clase destino concreta, de modo que siempre se invoque el mismo componente.
Buenas prácticas con PendingIntent
Para usar PendingIntent de forma segura:
- Asegúrate de que tu Intent base tiene configurados acción, paquete y componente (cuando proceda), sobre todo si el PendingIntent es mutable.
- Usa
FLAG_IMMUTABLEsiempre que sea posible, especialmente si tuminSdkVersiones 23 o superior. Si bajas de ahí, puedes condicionar el flag según versión. - Incluye el PendingIntent dentro de un Intent explícito cuando se lo pasas a otra app, en lugar de pasar intents anidados sin controlar.
Con estas precauciones, puedes permitir que el sistema o apps de terceros reactiven tu lógica en el futuro sin exponerte innecesariamente.
Dominando la creación de intents (explícitos e implícitos), el diseño de filtros en el manifiesto, la resolución por acción/datos/categorías y las nuevas exigencias de seguridad con intents anidados y PendingIntent, tu app puede navegar entre actividades, servicios y otras aplicaciones del sistema de forma fluida, potente y segura, aprovechando el ecosistema Android en lugar de pelearte con él.
