Cómo usar la detección del servicio de red

La detección de servicios de red (NSD) le otorga a tu app acceso a servicios que otras 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 permite que tu app solicite servicios especificando un tipo de servicio y el nombre de una instancia de dispositivo que proporciona el tipo de servicio deseado. El DNS-SD es es compatible con Android y otras plataformas móviles.

Si agregas NSD a tu app, los usuarios podrán identificar otros dispositivos en la red local compatible con los servicios que tu app solicita. Esto es útil para un una variedad de aplicaciones entre pares, como el uso compartido de archivos o el modo videojuegos. Las APIs NSD de Android simplifican el esfuerzo que se necesita para tu implementación estas funciones.

En esta lección, se muestra cómo compilar una aplicación que pueda transmitir su el nombre y la información de conexión a la red local y buscar información de otras aplicaciones que hacen lo mismo. Por último, esta lección te muestra cómo para conectarse a la misma aplicación que se ejecuta en otro dispositivo.

Cómo registrar tu servicio en la red

Nota: Este paso es opcional. Si No te interesa transmitir los servicios de tu app en la red local puedes pasar directamente a la sección siguiente, Cómo descubrir servicios en la red.

Para registrar tu servicio en la red local, primero crea un objeto NsdServiceInfo, Este objeto proporciona la información que otros dispositivos de la red usan cuando deciden conectarse a tu 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, que es el nombre visible para otros dispositivos de la red. Cualquier dispositivo de la red que use NSD para buscar el nombre podrá verlo servicios locales. Ten en cuenta que el nombre debe ser único para todos los servicios red, y Android controla automáticamente la resolución de conflictos. Si dos dispositivos de la red tienen instalada la aplicación NsdChat, uno de cambia el nombre del servicio automáticamente, por ejemplo, “NsdChat (1)".

El segundo parámetro establece el tipo de servicio, especifica qué protocolo y capa que usa la aplicación. La sintaxis es "_<protocol>._<transportlayer>". En la fragmento de código, el servicio usa el protocolo HTTP que se ejecuta a través de TCP. Una aplicación ofrecer un servicio de impresora (por ejemplo, una impresora de red) establecería la de servicio como “_ipp._tcp”.

Nota: Los números internacionales asignados Authority (IANA) administra una canalización lista autorizada de tipos de servicios que usan los protocolos de descubrimiento de servicios, como NSD y Bonjour. Puedes descargar la lista en el Lista de IANA de nombres de servicios y números de puerto. Si deseas usar un nuevo tipo de servicio, completa este paso para reservarlo los Puertos y servicios de IANA formulario de registro.

Cuando configures el puerto para tu servicio, evita codificarlo de esta forma: conflictos con otras aplicaciones. Por ejemplo, si suponemos que tu aplicación siempre use el puerto 1337 lo pone en conflicto con y otras aplicaciones instaladas que usan el mismo puerto. En cambio, usa el nombre de usuario siguiente puerto disponible. Debido a que esta información se proporciona a otras aplicaciones a través de un transmisión de servicio, no es necesario que el puerto que usa tu aplicación conocidos por otras aplicaciones en el tiempo de compilación. En cambio, las aplicaciones pueden obtener esta información de la transmisión del servicio, justo antes de conectarte a tu servicio.

Si trabajas con sockets, aquí se muestra cómo puedes inicializar un socket 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 contiene devoluciones de llamada que Android usa para alertar a tu aplicación sobre el éxito o fracaso del registro o la cancelación del servicio.

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 haya registrado el servicio, debes 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 bestias impresoras de la red hasta la dóciles cámaras web, hasta las brutales y feroces batallas de los tres en línea jugadores. La clave para que tu aplicación vea este dinámico ecosistema de es el descubrimiento de servicios. Tu aplicación necesita detectar servicios para ver qué servicios están disponibles y filtrar cualquier cosa con la que la aplicación no pueda funcionar.

El descubrimiento de servicios, como el registro de servicios, tiene dos pasos: configurar un objeto de escucha de descubrimiento con las devoluciones de llamada correspondientes y hacer un único objeto Llamada a la API a discoverServices().

Primero, crea una instancia de una clase anónima que implemente NsdManager.DiscoveryListener. El siguiente fragmento muestra un ejemplo sencillo:

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 usa los métodos de esta interfaz para informar a tu aplicación cuando detecta cuando se inicia, cuándo falla y cuando se encuentran y se pierden servicios (la pérdida significa "se ya no está disponible"). Ten en cuenta que este fragmento realiza cuando se encuentra un servicio.

  1. El nombre del servicio encontrado se compara con el del servicio nombre del servicio local para determinar si el dispositivo acaba de retirar el suyo transmisión (que es válida).
  2. Se verifica el tipo de servicio. Para comprobar que es un tipo de servicio que a la que se puede conectar la aplicación.
  3. Se verifica el nombre del servicio para comprobar la conexión con el y mantener la integridad de su aplicación.

No siempre es necesario comprobar el nombre del servicio. Solo es relevante si que quieres conectar a una aplicación específica. Por ejemplo, la aplicación podría solo quiere conectarse a instancias de sí mismo que se ejecuten en otros dispositivos. Sin embargo, si el aplicación quiera conectarse a una impresora de red, basta con ver que el tipo de servicio es “_ipp._tcp”.

Después de configurar el objeto de escucha, llama a discoverServices() y pasa el tipo de servicio. que debe buscar tu aplicación, el protocolo de descubrimiento que se debe usar y la de objetos 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 encuentra en la red un servicio al cual conectarse, primero debes determinar la información de conexión para ese servicio, mediante el resolveService(). Implementa un NsdManager.ResolveListener para pasar a esto. 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 el servicio se resuelve, tu aplicación recibe información del servicio, como una dirección IP y un número de puerto. Esto es todo debes 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 NSD según corresponda durante la fase en el ciclo de vida del AA. Cancelar el registro de tu aplicación cuando se cierra ayuda a evitar otras aplicaciones piensen que aún está activo e intentar conectarse a que la modifica. Además, el descubrimiento de servicios es una operación costosa y debería detenerse cuando se detiene la actividad principal y se vuelve a habilitar cuando se desactiva y se reanudó. Anula los métodos de ciclo de vida de tu Activity principal y, luego, inserta 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);
    }