La función de detección de servicios de red (NSD) le otorga a tu app acceso a los servicios que otros dispositivos proporcionan en una red local. Los dispositivos compatibles con NSD incluyen impresoras, cámaras web, servidores HTTPS y otros dispositivos móviles.
NSD implementa el mecanismo de descubrimiento de servicios basado en DNS (DNS-SD), que le permite a tu app solicitar servicios especificando el tipo de este y el nombre de una instancia de dispositivo que proporcione el tipo de servicio deseado. DNS-SD es compatible en Android y en otras plataformas móviles.
Si agregas NSD a tu app, los usuarios podrán identificar otros dispositivos de la red local que admitan los servicios que tu app solicita. Esto es útil para una variedad de aplicaciones entre pares, como el intercambio de archivos o los juegos de varios jugadores. Las API NSD de Android simplifican el esfuerzo que se requiere para implementar estas funciones.
En esta lección, se muestra cómo crear una app que pueda transmitir su nombre e información de conexión a la red local y analizar información de otras apps que hagan lo mismo. Por último, en esta lección se muestra cómo conectarse a la misma app que se ejecuta en otro dispositivo.
Cómo registrar tu servicio en la red
Nota: Este paso es opcional. Si no quieres transmitir los servicios de tu app en la red local, puedes pasar a la siguiente sección, Cómo descubrir servicios en la red.
Para registrar tu servicio en la red local, primero crea un objeto NsdServiceInfo
, que proporciona la información que usan otros dispositivos de la red cuando deciden conectarse al servicio.
Kotlin
fun registerService(port: Int) { // Create the NsdServiceInfo object, and populate it. val serviceInfo = NsdServiceInfo().apply { // The name is subject to change based on conflicts // with other services advertised on the same network. serviceName = "NsdChat" serviceType = "_nsdchat._tcp" setPort(port) ... } }
Java
public void registerService(int port) { // Create the NsdServiceInfo object, and populate it. NsdServiceInfo serviceInfo = new NsdServiceInfo(); // The name is subject to change based on conflicts // with other services advertised on the same network. serviceInfo.setServiceName("NsdChat"); serviceInfo.setServiceType("_nsdchat._tcp"); serviceInfo.setPort(port); ... }
Este fragmento de código establece el nombre del servicio como "NsdChat". El nombre del servicio es el nombre de la instancia y el nombre visible para los otros dispositivos de la red. Cualquier dispositivo de la red que use NSD para buscar servicios locales puede ver este nombre. Ten en cuenta que el nombre debe ser único para cualquier servicio de la red, y que Android maneja automáticamente la resolución de conflictos. Si dos dispositivos de la red tienen la app NsdChat instalada, uno de ellos cambia de forma automática el nombre del servicio, por ejemplo, por "NsdChat (1)".
El segundo parámetro establece el tipo de servicio y especifica qué protocolo y capa de transporte usa la aplicación. La sintaxis es "_<protocol>._<transportlayer>". En el fragmento de código, el servicio usa el protocolo HTTP que se ejecuta en TCP. Una aplicación que ofrece un servicio de impresora (por ejemplo, una impresora de red) establecería el tipo de servicio en "_ipp._tcp".
Nota: La Internet Assigned Numbers Authority (IANA) administra una lista centralizada y autorizada de los tipos de servicios que utilizan los protocolos de detección de servicios, como NSD y Bonjour. Puedes descargar la lista de nombres de servicios y números de puertos de IANA. Si deseas usar un nuevo tipo de servicio, debes llenar el formulario de registro de puertos y servicios de IANA para reservarlo.
Cuando configures el puerto para tu servicio, evita codificarlo, ya que esto afecta otras apps. Por ejemplo, si asumes que tu app siempre usa el puerto 1337, se genera un conflicto potencial con otras apps instaladas que usan el mismo puerto. En ese caso, debes usar el próximo puerto disponible del dispositivo. Debido a que esta información se proporciona a otras apps mediante una transmisión de servicio, no es necesario que otras apps conozcan, durante la compilación, el puerto que usa tu app. En cambio, las apps pueden obtener esta información de la transmisión de tu servicio, justo antes de conectarse a este.
Si trabajas con sockets, puedes inicializar uno en cualquier puerto disponible con solo establecerlo en 0.
Kotlin
fun initializeServerSocket() { // Initialize a server socket on the next available port. serverSocket = ServerSocket(0).also { socket -> // Store the chosen port. mLocalPort = socket.localPort ... } }
Java
public void initializeServerSocket() { // Initialize a server socket on the next available port. serverSocket = new ServerSocket(0); // Store the chosen port. localPort = serverSocket.getLocalPort(); ... }
Ahora que definiste el objeto NsdServiceInfo
, debes implementar la interfaz RegistrationListener
. Esta interfaz contiene devoluciones de llamada que Android usa para avisarle a tu aplicación si el registro o la anulación de este se realizaron correctamente o no.
Kotlin
private val registrationListener = object : NsdManager.RegistrationListener { override fun onServiceRegistered(NsdServiceInfo: NsdServiceInfo) { // Save the service name. Android may have changed it in order to // resolve a conflict, so update the name you initially requested // with the name Android actually used. mServiceName = NsdServiceInfo.serviceName } override fun onRegistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) { // Registration failed! Put debugging code here to determine why. } override fun onServiceUnregistered(arg0: NsdServiceInfo) { // Service has been unregistered. This only happens when you call // NsdManager.unregisterService() and pass in this listener. } override fun onUnregistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) { // Unregistration failed. Put debugging code here to determine why. } }
Java
public void initializeRegistrationListener() { registrationListener = new NsdManager.RegistrationListener() { @Override public void onServiceRegistered(NsdServiceInfo NsdServiceInfo) { // Save the service name. Android may have changed it in order to // resolve a conflict, so update the name you initially requested // with the name Android actually used. serviceName = NsdServiceInfo.getServiceName(); } @Override public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) { // Registration failed! Put debugging code here to determine why. } @Override public void onServiceUnregistered(NsdServiceInfo arg0) { // Service has been unregistered. This only happens when you call // NsdManager.unregisterService() and pass in this listener. } @Override public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) { // Unregistration failed. Put debugging code here to determine why. } }; }
Ahora tienes todas las piezas para registrar tu servicio. Llama al método registerService()
.
Ten en cuenta que este método es asíncrono, por lo que cualquier código que deba ejecutarse después de que se registre el servicio debe ir en el método onServiceRegistered()
.
Kotlin
fun registerService(port: Int) { // Create the NsdServiceInfo object, and populate it. val serviceInfo = NsdServiceInfo().apply { // The name is subject to change based on conflicts // with other services advertised on the same network. serviceName = "NsdChat" serviceType = "_nsdchat._tcp" setPort(port) } nsdManager = (getSystemService(Context.NSD_SERVICE) as NsdManager).apply { registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, registrationListener) } }
Java
public void registerService(int port) { NsdServiceInfo serviceInfo = new NsdServiceInfo(); serviceInfo.setServiceName("NsdChat"); serviceInfo.setServiceType("_http._tcp."); serviceInfo.setPort(port); nsdManager = Context.getSystemService(Context.NSD_SERVICE); nsdManager.registerService( serviceInfo, NsdManager.PROTOCOL_DNS_SD, registrationListener); }
Cómo descubrir servicios en la red
La red está llena de vida, desde las impresoras bestiales y las dóciles cámaras web, hasta las brutales y feroces batallas de los jugadores de tres en línea cercanos. La clave para permitir que tu app detecte este vibrante ecosistema de funcionalidad es el descubrimiento de servicios. Tu aplicación necesita escuchar transmisiones de servicios en la red para saber qué servicios están disponibles y filtrar cualquier obstáculo que impida su funcionamiento.
La detección de servicios, como el registro de servicios, tiene dos pasos: configurar un objeto de escucha de descubrimiento con las devoluciones de llamada relevantes y realizar una única llamada de la API asíncrona a discoverServices()
.
Primero, crea una instancia de una clase anónima que implemente NsdManager.DiscoveryListener
. En el siguiente fragmento, se muestra un ejemplo simple:
Kotlin
// Instantiate a new DiscoveryListener private val discoveryListener = object : NsdManager.DiscoveryListener { // Called as soon as service discovery begins. override fun onDiscoveryStarted(regType: String) { Log.d(TAG, "Service discovery started") } override fun onServiceFound(service: NsdServiceInfo) { // A service was found! Do something with it. Log.d(TAG, "Service discovery success$service") when { service.serviceType != SERVICE_TYPE -> // Service type is the string containing the protocol and // transport layer for this service. Log.d(TAG, "Unknown Service Type: ${service.serviceType}") service.serviceName == mServiceName -> // The name of the service tells the user what they'd be // connecting to. It could be "Bob's Chat App". Log.d(TAG, "Same machine: $mServiceName") service.serviceName.contains("NsdChat") -> nsdManager.resolveService(service, resolveListener) } } override fun onServiceLost(service: NsdServiceInfo) { // When the network service is no longer available. // Internal bookkeeping code goes here. Log.e(TAG, "service lost: $service") } override fun onDiscoveryStopped(serviceType: String) { Log.i(TAG, "Discovery stopped: $serviceType") } override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) { Log.e(TAG, "Discovery failed: Error code:$errorCode") nsdManager.stopServiceDiscovery(this) } override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) { Log.e(TAG, "Discovery failed: Error code:$errorCode") nsdManager.stopServiceDiscovery(this) } }
Java
public void initializeDiscoveryListener() { // Instantiate a new DiscoveryListener discoveryListener = new NsdManager.DiscoveryListener() { // Called as soon as service discovery begins. @Override public void onDiscoveryStarted(String regType) { Log.d(TAG, "Service discovery started"); } @Override public void onServiceFound(NsdServiceInfo service) { // A service was found! Do something with it. Log.d(TAG, "Service discovery success" + service); if (!service.getServiceType().equals(SERVICE_TYPE)) { // Service type is the string containing the protocol and // transport layer for this service. Log.d(TAG, "Unknown Service Type: " + service.getServiceType()); } else if (service.getServiceName().equals(serviceName)) { // The name of the service tells the user what they'd be // connecting to. It could be "Bob's Chat App". Log.d(TAG, "Same machine: " + serviceName); } else if (service.getServiceName().contains("NsdChat")){ nsdManager.resolveService(service, resolveListener); } } @Override public void onServiceLost(NsdServiceInfo service) { // When the network service is no longer available. // Internal bookkeeping code goes here. Log.e(TAG, "service lost: " + service); } @Override public void onDiscoveryStopped(String serviceType) { Log.i(TAG, "Discovery stopped: " + serviceType); } @Override public void onStartDiscoveryFailed(String serviceType, int errorCode) { Log.e(TAG, "Discovery failed: Error code:" + errorCode); nsdManager.stopServiceDiscovery(this); } @Override public void onStopDiscoveryFailed(String serviceType, int errorCode) { Log.e(TAG, "Discovery failed: Error code:" + errorCode); nsdManager.stopServiceDiscovery(this); } }; }
La API de NSD utiliza los métodos de esta interfaz para informar a tu app cuándo se inicia el descubrimiento, cuándo falla y cuándo se encuentran y se pierden servicios (la pérdida significa que "ya no está disponible"). Ten en cuenta que este fragmento realiza varias comprobaciones cuando se encuentra un servicio.
- Se compara el nombre del servicio encontrado con el nombre del servicio local para determinar si el dispositivo acaba de recibir su propia transmisión (que es válida).
- Se verifica el tipo de servicio para comprobar que sea un tipo de servicio al que se pueda conectar tu app.
- Se verifica el nombre del servicio para comprobar la conexión a la app correcta.
No siempre es necesario verificar el nombre del servicio, y solo es relevante si deseas conectarte a una app específica. Por ejemplo, es posible que la app solo quiera conectarse a instancias de sí misma que se ejecuten en otros dispositivos. Sin embargo, si la aplicación desea conectarse a una impresora de red, es suficiente con verificar que el tipo de servicio sea "_ipp._tcp".
Después de configurar el objeto de escucha, llama a discoverServices()
, pasando el tipo de servicio que debe buscar tu aplicación, el protocolo de descubrimiento que usarás y el objeto de escucha que acabas de crear.
Kotlin
nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener)
Java
nsdManager.discoverServices( SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener);
Cómo conectarse a servicios de la red
Cuando tu aplicación encuentre un servicio en la red a fin de conectarse, primero debe determinar la información de conexión para ese servicio mediante el método resolveService()
.
Implementa un NsdManager.ResolveListener
a fin pasar a este método y úsalo para obtener un NsdServiceInfo
que contenga la información de conexión.
Kotlin
private val resolveListener = object : NsdManager.ResolveListener { override fun onResolveFailed(serviceInfo: NsdServiceInfo, errorCode: Int) { // Called when the resolve fails. Use the error code to debug. Log.e(TAG, "Resolve failed: $errorCode") } override fun onServiceResolved(serviceInfo: NsdServiceInfo) { Log.e(TAG, "Resolve Succeeded. $serviceInfo") if (serviceInfo.serviceName == mServiceName) { Log.d(TAG, "Same IP.") return } mService = serviceInfo val port: Int = serviceInfo.port val host: InetAddress = serviceInfo.host } }
Java
public void initializeResolveListener() { resolveListener = new NsdManager.ResolveListener() { @Override public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) { // Called when the resolve fails. Use the error code to debug. Log.e(TAG, "Resolve failed: " + errorCode); } @Override public void onServiceResolved(NsdServiceInfo serviceInfo) { Log.e(TAG, "Resolve Succeeded. " + serviceInfo); if (serviceInfo.getServiceName().equals(serviceName)) { Log.d(TAG, "Same IP."); return; } mService = serviceInfo; int port = mService.getPort(); InetAddress host = mService.getHost(); } }; }
Una vez que se resuelve el servicio, tu app recibe información detallada sobre el servicio, incluida una dirección IP y un número de puerto. Esto es todo lo que necesitas para crear tu propia conexión de red al servicio.
Cómo anular el registro de tu servicio cuando se cierra la app
Es importante habilitar e inhabilitar la funcionalidad NSD según corresponda durante el ciclo de vida de la app. Si cancelas el registro de tu app cuando se cierra, puedes evitar que otras apps piensen que aún está activa y que intenten conectarse con ella. Además, el descubrimiento de servicios es una operación costosa, por lo que se debería detener cuando la actividad principal está en pausa y volver a habilitarse cuando esta se reanuda. Anula los métodos del ciclo de vida de tu actividad principal e inserta el código para iniciar y detener la transmisión y el descubrimiento de servicios, según corresponda.
Kotlin
//In your application's Activity override fun onPause() { nsdHelper?.tearDown() super.onPause() } override fun onResume() { super.onResume() nsdHelper?.apply { registerService(connection.localPort) discoverServices() } } override fun onDestroy() { nsdHelper?.tearDown() connection.tearDown() super.onDestroy() } // NsdHelper's tearDown method fun tearDown() { nsdManager.apply { unregisterService(registrationListener) stopServiceDiscovery(discoveryListener) } }
Java
//In your application's Activity @Override protected void onPause() { if (nsdHelper != null) { nsdHelper.tearDown(); } super.onPause(); } @Override protected void onResume() { super.onResume(); if (nsdHelper != null) { nsdHelper.registerService(connection.getLocalPort()); nsdHelper.discoverServices(); } } @Override protected void onDestroy() { nsdHelper.tearDown(); connection.tearDown(); super.onDestroy(); } // NsdHelper's tearDown method public void tearDown() { nsdManager.unregisterService(registrationListener); nsdManager.stopServiceDiscovery(discoveryListener); }