A descoberta de serviços de rede (NSD, na sigla em inglês) permite que seu app acesse serviços fornecidos por outros dispositivos em uma rede local. Os dispositivos compatíveis com a NSD incluem impressoras, webcams, servidores HTTPS e outros dispositivos móveis.
A NSD implementa o mecanismo de descoberta de serviços baseada em DNS, que permite que seu app solicite serviços especificando um tipo de serviço e o nome de uma instância de dispositivo que oferece o tipo de serviço desejado. O DNS-SD é compatível com o Android e outras plataformas móveis.
Adicionar a NSD ao seu app permite que os usuários identifiquem outros dispositivos na rede local que são compatíveis com os serviços solicitados pelo app. Isso é útil para vários de aplicativos ponto a ponto, como compartilhamento de arquivos ou jogos multijogador. As APIs NSD do Android simplificam o esforço necessário para a implementação desses recursos.
Esta lição mostra como criar um aplicativo que pode transmitir o nome e as informações de conexão dele para a rede local e procurar informações de outros aplicativos que fazem o mesmo. Além disso, a lição mostra como se conectar ao mesmo aplicativo em execução em outro dispositivo.
Registrar seu serviço na rede
Observação: esta etapa é opcional. Se você não se importa em transmitir os serviços do seu app pela rede local, pule para a próxima seção, Descobrir serviços na rede.
Para registrar seu serviço na rede local, crie um objeto NsdServiceInfo
. Esse objeto traz as informações que outros dispositivos na rede usam ao decidir se conectar ao seu serviço.
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); ... }
Esse snippet de código define o nome do serviço como "NsdChat". O nome do serviço é o nome da instância, que fica visível para outros dispositivos na rede. O nome fica visível para qualquer dispositivo na rede que esteja usando a NSD para procurar serviços locais. Lembre-se de que cada serviço na rede precisa ter um nome exclusivo e que o Android gerencia automaticamente a resolução de conflitos. Se dois dispositivos na rede tiverem o aplicativo NsdChat instalado, um deles mudará o nome do serviço automaticamente para algo como “NsdChat(1)”.
O segundo parâmetro define o tipo de serviço e especifica o protocolo e a camada de transporte que o aplicativo usa. A sintaxe é "_<protocol>._<transportlayer>". No snippet de código, o serviço usa o protocolo HTTP executado via TCP. Um aplicativo que ofereça um serviço de impressora (por exemplo, uma impressora em rede) definirá o tipo de serviço como "_ipp._tcp".
Observação: a Autoridade internacional para atribuição de números (IANA, na sigla em inglês) gerencia uma lista centralizada e oficial de tipos de serviço usados por protocolos de descoberta de serviços, como NSD e Bonjour. É possível fazer o download dessas informações pela lista da IANA de nomes de serviços e números de porta (link em inglês). Se você pretende usar um novo tipo de serviço, faça a reserva com o preenchimento do Formulário de registro de portas e serviço da IANA (link em inglês).
Ao configurar a porta do seu serviço, evite codificá-la, porque isso causará conflitos com outros aplicativos. Por exemplo, se seu aplicativo sempre usar a porta 1337, ele poderá entrar em conflito com outros aplicativos instalados que usam a mesma porta. Em vez disso, use a próxima porta disponível do dispositivo. Como essas informações são fornecidas a outros apps por uma transmissão de serviço, não é necessário que a porta usada pelo seu aplicativo seja conhecida pelos outros apps no momento da compilação. Em vez disso, os aplicativos podem acessar essas informações a partir da transmissão do serviço imediatamente antes de se conectarem a ele.
Se estiver trabalhando com soquetes, veja como inicializar um soquete para qualquer porta disponível simplesmente definindo-o como 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(); ... }
Agora que você definiu o objeto NsdServiceInfo
, é preciso implementar a interface RegistrationListener
. Essa interface contém callbacks usados pelo Android para alertar seu aplicativo sobre o êxito ou a falha do registro e cancelamento de registro no serviço.
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. } }; }
Agora você tem tudo o que precisa para registrar seu serviço. Chame o método registerService()
.
Observe que esse método é assíncrono. Portanto, qualquer código que precise ser executado depois que o serviço for registrado precisará estar no 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); }
Descobrir serviços na rede
A rede está repleta de vida, das impressoras ferozes e webcams comportadas às batalhas brutais e intensas dos jogadores de jogo da velha. O segredo para permitir que seu aplicativo veja esse ecossistema vibrante de funcionalidades é a descoberta de serviços. Seu app precisa detectar as transmissões de serviço na rede para ver quais serviços estão disponíveis e filtrar qualquer item com que o aplicativo não possa trabalhar.
A descoberta de serviços, assim como o registro de serviço, tem duas etapas: criação de um listener de descoberta com os callbacks relevantes e uma única chamada de API assíncrona para discoverServices()
.
Primeiro, instancie uma classe anônima que implemente NsdManager.DiscoveryListener
. O snippet a seguir mostra um exemplo simples:
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); } }; }
A API NSD usa os métodos nessa interface para informar seu aplicativo quando a descoberta é iniciada, quando ela falha e quando os serviços são encontrados e perdidos (ou seja, não estão mais disponíveis). Observe que o snippet faz várias verificações quando um serviço é encontrado.
- O nome do serviço encontrado é comparado com o nome do serviço local para determinar se o dispositivo detectou uma transmissão própria (o que é válido).
- O tipo de serviço é analisado para verificar se é um tipo ao qual seu aplicativo pode se conectar.
- O nome do serviço é analisado para verificar a conexão com o aplicativo correto.
Verificar o nome do serviço nem sempre é necessário e só é relevante se você quiser se conectar a um aplicativo específico. Por exemplo, o aplicativo pode apenas querer se conectar a instâncias em execução em outros dispositivos. No entanto, se o aplicativo quiser se conectar a uma impressora em rede, será suficiente ver que o tipo de serviço é "_ipp._tcp".
Depois de configurar o listener, chame discoverServices()
, transmitindo o tipo de serviço que o aplicativo precisa procurar, o protocolo de descoberta a ser usado e o listener que você acabou de criar.
Kotlin
nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener)
Java
nsdManager.discoverServices( SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener);
Conectar-se a serviços na rede
Quando o aplicativo encontra um serviço na rede para se conectar, ele precisa primeiro determinar as informações de conexão para esse serviço, usando o método resolveService()
.
Implemente um NsdManager.ResolveListener
para transmitir o método e use-o para receber um NsdServiceInfo
contendo as informações de conexão.
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(); } }; }
Depois que o serviço for resolvido, seu aplicativo receberá informações de serviço detalhadas, incluindo um endereço IP e um número de porta. Isso é tudo que você precisa para criar sua própria conexão de rede com o serviço.
Cancelar o registro do seu serviço ao fechar o aplicativo
É importante ativar e desativar a funcionalidade de NSD conforme for adequado durante o ciclo de vida do aplicativo. Cancelar o registro do seu aplicativo ao fechá-lo ajuda a impedir que outros aplicativos pensem que ele ainda está ativo e tentem se conectar a ele. Além disso, a descoberta de serviços é uma operação cara e precisa ser interrompida quando a atividade principal é pausada, e reativada quando a atividade é retomada. Modifique os métodos de ciclo de vida da sua atividade principal e insira o código para iniciar e interromper a transmissão e a descoberta de serviços conforme for adequado.
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); }