Utilizza il rilevamento dei servizi di rete

La funzionalità Network Service Discovery (NSD) consente alla tua app di accedere ai servizi forniti da altri dispositivi su una rete locale. I dispositivi che supportano gli NSD includono stampanti, webcam, server HTTPS e altri dispositivi mobili.

L'NSD implementa il meccanismo Service Discovery (DNS-SD) basato su DNS, che consente all'app di richiedere servizi specificando un tipo di servizio e il nome di un'istanza del dispositivo che fornisce il tipo di servizio desiderato. DNS-SD è supportato sia su Android che su altre piattaforme per dispositivi mobili.

L'aggiunta di NSD alla tua app consente agli utenti di identificare altri dispositivi sulla rete locale che supportano i servizi richiesti dalla tua app. Questo è utile per una serie di applicazioni peer-to-peer, come la condivisione di file o i giochi multi-player. Le API NSD di Android semplificano lo sforzo necessario per implementare tali funzionalità.

Questa lezione spiega come creare un'applicazione in grado di trasmettere il suo nome e le informazioni sulla connessione alla rete locale e cercare informazioni da altre applicazioni che fanno lo stesso. Infine, questa lezione ti mostra come connetterti alla stessa applicazione in esecuzione su un altro dispositivo.

Registra il servizio sulla rete

Nota : questo passaggio è facoltativo. Se non ti interessa trasmettere i servizi della tua app sulla rete locale, puoi passare alla sezione successiva, Scopri i servizi sulla rete.

Per registrare il servizio sulla rete locale, devi prima creare un oggetto NsdServiceInfo. Questo oggetto fornisce le informazioni utilizzate da altri dispositivi sulla rete per decidere se connettersi o meno al tuo servizio.

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);
    ...
}

Questo snippet di codice imposta il nome del servizio su "NsdChat". Il nome del servizio è il nome dell'istanza: è il nome visibile ad altri dispositivi sulla rete. Il nome è visibile a qualsiasi dispositivo sulla rete che utilizza NSD per cercare servizi locali. Tieni presente che il nome deve essere univoco per qualsiasi servizio sulla rete e che Android gestirà automaticamente la risoluzione dei conflitti. Se l'applicazione NsdChat è installata su due dispositivi sulla rete, uno di questi modifica automaticamente il nome del servizio, ad esempio "NsdChat(1)".

Il secondo parametro imposta il tipo di servizio, specifica il protocollo e il livello di trasporto utilizzati dall'applicazione. La sintassi è "_<protocollo>._<transportlayer>". Nello snippet di codice, il servizio utilizza il protocollo HTTP in esecuzione su TCP. Un'applicazione che offre un servizio di stampa (ad esempio una stampante di rete) imposterebbe il tipo di servizio su "_ipp._tcp".

Nota: l'International Assigned Numbers Authority (IANA) gestisce un elenco centralizzato e autorevole dei tipi di servizi utilizzati dai protocolli di rilevamento dei servizi come NSD e Bonjour. Puoi scaricare l'elenco dall'elenco IANA di nomi di servizi e numeri di porta. Se intendi utilizzare un nuovo tipo di servizio, devi prenotarlo compilando il modulo di registrazione per le porte e i servizi IANA.

Quando imposti la porta per il tuo servizio, evita di impostare la porta come hardcoded perché potrebbe essere in conflitto con altre applicazioni. Ad esempio, supponendo che la tua applicazione utilizzi sempre la porta 1337, l'applicazione potrebbe causare un conflitto con altre applicazioni installate che utilizzano la stessa porta. Usa invece la successiva porta disponibile del dispositivo. Poiché queste informazioni vengono fornite ad altre app da una trasmissione di servizio, non è necessario che la porta utilizzata dall'applicazione sia nota ad altre applicazioni in fase di compilazione. Le applicazioni possono invece ottenere queste informazioni dalla trasmissione del servizio, immediatamente prima della connessione al servizio.

Se utilizzi i socket, ecco come puoi inizializzare un socket su qualsiasi porta disponibile semplicemente impostandola su 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();
    ...
}

Ora che hai definito l'oggetto NsdServiceInfo, devi implementare l'interfaccia RegistrationListener. Questa interfaccia contiene callback utilizzati da Android per avvisare l'applicazione dell'esito positivo o negativo della registrazione al servizio e dell'annullamento della registrazione.

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.
        }
    };
}

Ora hai tutti gli elementi per registrare il tuo servizio. Chiama il metodo registerService().

Tieni presente che questo metodo è asincrono, quindi qualsiasi codice che deve essere eseguito dopo la registrazione del servizio deve essere nel metodo 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);
}

Scopri i servizi sulla rete

La rete pullula di vita, dalle bestioline stampanti, alle docili webcam di rete, alle brutali battaglie feroci dei giocatori di tris nelle vicinanze. La chiave per consentire alla tua applicazione di vedere questo vivace ecosistema di funzionalità è la scoperta dei servizi. La tua applicazione deve ascoltare le trasmissioni di servizi sulla rete per vedere quali servizi sono disponibili ed escludere tutto ciò con cui l'applicazione non può funzionare.

La funzionalità di rilevamento dei servizi, ad esempio la registrazione di un servizio, prevede due passaggi: configurazione di un listener di rilevamento con i callback pertinenti ed esecuzione di una singola chiamata API asincrone a discoverServices().

Innanzitutto, crea un'istanza di una classe anonima che implementi NsdManager.DiscoveryListener. Il seguente snippet mostra un esempio semplice:

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);
        }
    };
}

L'API NSD utilizza i metodi presenti in questa interfaccia per informare l'applicazione quando il rilevamento viene avviato, quando non riesce e quando i servizi vengono trovati e persi (persi significa "non è più disponibile"). Nota che questo snippet fa diversi controlli quando viene trovato un servizio.

  1. Il nome del servizio trovato viene confrontato con il nome del servizio locale per determinare se il dispositivo ha appena ripreso la propria trasmissione (che è valida).
  2. Il tipo di servizio viene controllato per verificare che sia un tipo di servizio a cui l'applicazione può connettersi.
  3. Il nome del servizio viene controllato per verificare la connessione all'applicazione corretta.

Controllare il nome del servizio non è sempre necessario ed è pertinente solo se vuoi connetterti a un'applicazione specifica. Ad esempio, l'applicazione potrebbe voler connettersi solo alle sue istanze in esecuzione su altri dispositivi. Tuttavia, se l'applicazione vuole connettersi a una stampante di rete, è sufficiente vedere che il tipo di servizio è "_ipp._tcp".

Dopo aver configurato il listener, chiama discoverServices(), trasmetti il tipo di servizio che l'applicazione dovrebbe cercare, il protocollo di rilevamento da utilizzare e l'ascoltatore appena creato.

Kotlin

nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener)

Java

nsdManager.discoverServices(
        SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener);

Connettersi a servizi sulla rete

Quando l'applicazione trova un servizio sulla rete a cui connettersi, deve prima determinare le informazioni di connessione per quel servizio utilizzando il metodo resolveService(). Implementa un NsdManager.ResolveListener da passare a questo metodo e utilizzalo per ottenere un NsdServiceInfo contenente le informazioni sulla connessione.

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 volta risolto il servizio, l'applicazione riceve informazioni dettagliate sul servizio, tra cui un indirizzo IP e un numero di porta. Questo è tutto ciò che ti serve per creare la tua connessione di rete al servizio.

Annulla la registrazione del servizio alla chiusura dell'applicazione

È importante abilitare e disabilitare la funzionalità NSD in base alle esigenze durante il ciclo di vita dell'applicazione. L'annullamento della registrazione dell'applicazione quando viene chiusa consente ad altre applicazioni di non pensare che sia ancora attiva e di tentare di connettersi. Inoltre, il rilevamento dei servizi è un'operazione costosa e deve essere interrotto quando l'attività padre è in pausa e riattivata quando l'attività viene ripristinata. Sostituisci i metodi del ciclo di vita dell'attività principale e inserisci il codice per avviare e interrompere la trasmissione e il rilevamento del servizio in base alle esigenze.

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);
    }