В моем приложении для Android я должен показать список доступных служб в сети, опубликованных на другой машине (RPi 3B и Raspbian Stretch) с использованием avahi 0.6.32 (демон Bonjour / zeroconf для Linux).Я получаю список на телефоне Android с помощью NsdManager.Но во время тестирования я получаю странное поведение: когда я выключаю и снова включаю WiFi в телефоне, в большинстве случаев службы обнаруживаются, затем сразу теряются, а затем вновь открываются (вместо того, чтобы просто обнаруживаться один раз) и всеэто менее чем за секунду.
Это приводит к тому, что список служб кратковременно появляется на экране, затем исчезает и, наконец, появляется почти сразу, но это все еще очень заметно.И это заставляет службы быть обнаруженными и решенными дважды.Поскольку я ожидаю подключения множества телефонов к нескольким службам в одной локальной сети, я хочу избежать перегрузки сети.
Я не уверен, что я делаю что-то не так или просто NsdManager работаетна андроид.Чтобы уменьшить возможные источники проблемы, я закомментировал строки, которые разрешают службы (оставляя только сообщения журнала), но проблема сохранялась (более половины случаев).
Как мне решить эту проблему?
Пример извлечения из Logcat:
2019-09-26 04: 33: 50.262 27300-27420 / com.example.myapp D / NsdHelper $ initializeDiscoveryListener: успешное обнаружение службы: имя: MyService-1490247, тип: _mytype._tcp., хост: ноль, порт: 0, txtRecord:
2019-09-26 04: 33: 50.87927300-27420 / com.example.myapp D / NsdHelper $ initializeDiscoveryListener: Служба потеряна: имя: MyService-1490247, тип: _mytype._tcp., Хост: ноль, порт: 0, txtRecord:
2019-09-26 04: 33: 50.970 27300-27420 / com.example.myapp D / NsdHelper $ initializeDiscoveryListener: успех обнаружения службы: имя: MyService-1490247, тип: _mytype._tcp., Хост: ноль, порт: 0, txtRecord:
Я тестирую на Samsung Note 8 с Android O. Я пробовал с двумя разными WiFi-роутерами и поведение одинаковое.
Я использую следующий класс NsdHelper (в Kotlin):
import android.content.Context
import android.net.nsd.NsdManager
import android.net.nsd.NsdServiceInfo
import timber.log.Timber
import java.util.*
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.collections.ArrayList
abstract class NsdHelper(val context: Context) {
// Declare DNS-SD related variables for service discovery
val nsdManager: NsdManager? = context.getSystemService(Context.NSD_SERVICE) as NsdManager?
private var discoveryListener: NsdManager.DiscoveryListener? = null
private var resolveListener: NsdManager.ResolveListener? = null
private var resolveListenerBusy = AtomicBoolean(false)
private var pendingNsdServices = ConcurrentLinkedQueue<NsdServiceInfo>()
var resolvedNsdServices: MutableList<NsdServiceInfo> =
Collections.synchronizedList(ArrayList<NsdServiceInfo>())
companion object {
// Type of services to look for
const val NSD_SERVICE_TYPE: String = "_mytype._tcp."
// Services' Names must start with this
const val NSD_SERVICE_NAME: String = "MyService-"
}
// Initialize Listeners
fun initializeNsd() {
// Initialize only resolve listener
initializeResolveListener()
}
// Instantiate DNS-SD discovery listener
// used to discover available Sonata audio servers on the same network
private fun initializeDiscoveryListener() {
Timber.d("Initialize DiscoveryListener")
// Instantiate a new DiscoveryListener
discoveryListener = object : NsdManager.DiscoveryListener {
override fun onDiscoveryStarted(regType: String) {
// Called as soon as service discovery begins.
Timber.d("Service discovery started: $regType")
}
override fun onServiceFound(service: NsdServiceInfo) {
// A service was found! Do something with it
Timber.d("Service discovery success: $service")
if ( service.serviceType == NSD_SERVICE_TYPE &&
service.serviceName.startsWith(NSD_SERVICE_NAME) ) {
// Both service type and service name are the ones we want
// If the resolver is free, resolve the service to get all the details
if (resolveListenerBusy.compareAndSet(false, true)) {
nsdManager?.resolveService(service, resolveListener)
} else {
// Resolver was busy. Add the service to the list of pending services
pendingNsdServices.add(service)
}
} else {
// Not our service. Log message but do nothing else
Timber.d("Not our Service - Name: ${service.serviceName}, Type: ${service.serviceType}")
}
}
override fun onServiceLost(service: NsdServiceInfo) {
Timber.d("Service lost: $service")
// If the lost service was in the queue of pending services, remove it
synchronized(pendingNsdServices) {
val iterator = pendingNsdServices.iterator()
while (iterator.hasNext()) {
if (iterator.next().serviceName == service.serviceName) iterator.remove()
}
}
// If the lost service was in the list of resolved services, remove it
synchronized(resolvedNsdServices) {
val iterator = resolvedNsdServices.iterator()
while (iterator.hasNext()) {
if (iterator.next().serviceName == service.serviceName) iterator.remove()
}
}
// Do the rest of the processing for the lost service
onNsdServiceLost(service)
}
override fun onDiscoveryStopped(serviceType: String) {
Timber.d("Discovery stopped: $serviceType")
}
override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) {
Timber.e("Start Discovery failed: Error code: $errorCode")
stopDiscovery()
}
override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) {
Timber.e("Stop Discovery failed: Error code: $errorCode")
nsdManager?.stopServiceDiscovery(this)
}
}
}
// Instantiate DNS-SD resolve listener to get extra information about the service
private fun initializeResolveListener() {
Timber.d("Initialize ResolveListener")
resolveListener = object : NsdManager.ResolveListener {
override fun onServiceResolved(service: NsdServiceInfo) {
Timber.d("Service Resolve Succeeded: $service")
// Register the newly resolved service into our list of resolved services
resolvedNsdServices.add(service)
// Process the newly resolved service
onNsdServiceResolved(service)
// Process the next service waiting to be resolved
resolveNextInQueue()
}
override fun onResolveFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
// Called when the resolve fails. Use the error code to debug.
Timber.e("Resolve failed: $serviceInfo - Error code: $errorCode")
// Process the next service waiting to be resolved
resolveNextInQueue()
}
}
}
// Start discovering services on the network
fun discoverServices() {
// Cancel any existing discovery request
stopDiscovery()
initializeDiscoveryListener()
// Start looking for available audio channels in the network
nsdManager?.discoverServices(NSD_SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener)
}
// Stop DNS-SD service discovery
fun stopDiscovery() {
if (discoveryListener != null) {
Timber.d("stopDiscovery() called")
try {
nsdManager?.stopServiceDiscovery(discoveryListener)
} finally {
}
discoveryListener = null
}
}
// Resolve next NSD service pending resolution
private fun resolveNextInQueue() {
// Get the next NSD service waiting to be resolved from the queue
val nextNsdService = pendingNsdServices.poll()
if (nextNsdService != null) {
// There was one. Send to be resolved.
nsdManager?.resolveService(nextNsdService, resolveListener)
} else {
// There was no pending service. Release the flag
resolveListenerBusy.set(false)
}
}
// Function to be overriden with custom logic for new service resolved
abstract fun onNsdServiceResolved(service: NsdServiceInfo)
// Function to be overriden with custom logic for service lost
abstract fun onNsdServiceLost(service: NsdServiceInfo)
}
В блоке инициализации модели представления я запускаю обнаружение службы:
class MyViewModel(application: Application) : AndroidViewModel(application) {
// Get application context
private val myAppContext: Context = getApplication<Application>().applicationContext
// Declare NsdHelper object for service discovery
private val nsdHelper: NsdHelper? = object : NsdHelper(myAppContext) {
override fun onNsdServiceResolved(service: NsdServiceInfo) {
// A new network service is available
// Update list of available services
updateServicesList()
}
override fun onNsdServiceLost(service: NsdServiceInfo) {
// A network service is no longer available
// Update list of available services
updateServicesList()
}
}
// Block that is run when the view model is created
init {
Timber.d("init block called")
// Initialize DNS-SD service discovery
nsdHelper?.initializeNsd()
// Start looking for available audio channels in the network
nsdHelper?.discoverServices()
}
// Called when the view model is destroyed
override fun onCleared() {
Timber.d("onCleared called")
nsdHelper?.stopDiscovery()
super.onCleared()
}
private fun updateServicesList() {
// Put the logic here to show the services on screen
return
}
}
Примечание: Timber - это утилита ведения журнала, почти прямая замена стандартным командам журнала, но более простая в использовании.