【问题标题】:NSdManager ResolveListener Error Code 3: Failure Already activeNSdManager ResolveListener 错误代码 3:失败已经激活
【发布时间】:2020-01-16 07:08:35
【问题描述】:

我在 Android 应用程序中使用 NsdManager 来发现由我开发的另一台设备发布的 NSD 服务。我只在 Android App 上进行服务发现(这方面不需要服务注册)。网络上同时发布了多个同类型服务的实例。

我开始使用 Google (https://developer.android.com/training/connect-devices-wirelessly/nsd) 提供的示例代码,但由于同时重复使用同一个解析器对象来解决多个服务解析,我遇到了致命错误。 然后我发现有几个人建议每次都创建一个新的解析器对象(比如Listener already in use (Service Discovery))。

我这样做了,致命错误被 Resolve Failure 错误代码 3 取代,这意味着解决过程处于活动状态。比以前好多了,但只有第一个服务被解决了,其余的都因为这个失败而被忽略了。

然后我发现有人建议对错误代码 3 进行特殊处理,方法是递归地重新发送解析请求,直到最终解决 (NSNetworkManager.ResolveListener messages Android)。

我在 Kotlin 中实现了这个解决方案,它有点工作,但我并不满意,因为:

  1. 我相信我正在创建许多额外的解析器对象 而且我不确定它们以后是否会被垃圾收集。
  2. 我在一个循环中重试了几次,可能会导致额外的和 对设备和网络造成不必要的负担。不确定我是否 应该在再次调用服务解析之前添加一个短暂的睡眠。
  3. 如果出现网络问题,程序可能会尝试千 多次解决相同的服务,而不是仅仅放弃 解决并等待再次发现该服务。

RxBonjour2 的人们提供了一个更复杂、更强大的解决方案,但它对我来说太复杂了:https://github.com/mannodermaus/RxBonjour/blob/2.x/rxbonjour-drivers/rxbonjour-driver-nsdmanager/src/main/kotlin/de/mannodermaus/rxbonjour/drivers/nsdmanager/NsdManagerDiscoveryEngine.kt

我对 Google 的官方示例没有正确处理这些问题感到沮丧。 nsd_chat 示例使用单个解析器对象,当在网络上以相同类型发布多个具有相同类型的服务时会失败。

您能提出更好的解决方案吗?或者对我下面的代码有什么改进?

import android.app.Application
import android.content.Context
import android.net.nsd.NsdManager
import android.net.nsd.NsdServiceInfo
import androidx.lifecycle.AndroidViewModel
import timber.log.Timber


class ViewModel(application: Application) : AndroidViewModel(application) {

    // Get application context
    private val myAppContext: Context = getApplication<Application>().applicationContext

    // Declare DNS-SD related variables for service discovery
    var nsdManager: NsdManager? = null
    private var discoveryListener: NsdManager.DiscoveryListener? = null

    // Constructor for the View Model that is run when the view model is created
    init {

        // Initialize DNS-SD service discovery
        nsdManager = myAppContext.getSystemService(Context.NSD_SERVICE) as NsdManager?

        initializeDiscoveryListener()

        // Start looking for available services in the network
        nsdManager?.discoverServices(NSD_SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener)

    }

    // Instantiate DNS-SD discovery listener
    // used to discover available Sonata audio servers on the same network
    private fun initializeDiscoveryListener() {

        // 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")
                when {
                    service.serviceType != NSD_SERVICE_TYPE ->
                        // Service type is not the one we are looking for
                        Timber.d("Unknown Service Type: ${service.serviceType}")
                    service.serviceName.contains(NSD_SERVICE_NAME) ->
                        // Both service type and service name are the ones we want
                        // Resolve the service to get all the details
                        startResolveService(service)
                    else ->
                        // Service type is ours but not the service name
                        // Log message but do nothing else
                        Timber.d("Unknown Service Name: ${service.serviceName}")
                }
            }

            override fun onServiceLost(service: NsdServiceInfo) {
                onNsdServiceLost(service)
            }

            override fun onDiscoveryStopped(serviceType: String) {
                Timber.i("Discovery stopped: $serviceType")
            }

            override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) {
                Timber.e("Start Discovery failed: Error code: $errorCode")
                nsdManager?.stopServiceDiscovery(this)
            }

            override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) {
                Timber.e("Stop Discovery failed: Error code: $errorCode")
                nsdManager?.stopServiceDiscovery(this)
            }
        }
    }

    fun startResolveService(service: NsdServiceInfo) {

        val newResolveListener =  object : NsdManager.ResolveListener {

            override fun onResolveFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
                // Called when the resolve fails. Use the error code to determine action.
                when (errorCode) {
                    NsdManager.FAILURE_ALREADY_ACTIVE -> {
                        // Resolver was busy
                        Timber.d("Resolve failed: $serviceInfo - Already active")
                        // Just try again...
                        startResolveService(serviceInfo)
                    }
                    else ->
                        Timber.e("Resolve failed: $serviceInfo - Error code: $errorCode")
                }
            }

            override fun onServiceResolved(serviceInfo: NsdServiceInfo) {
                onNsdServiceResolved(serviceInfo)
            }
        }

        nsdManager?.resolveService(service, newResolveListener)
    }

    companion object {

        // We'll only search for NDS services of this type
        const val NSD_SERVICE_TYPE: String = "_servicetype._tcp."
        // and whose names start like this
        const val NSD_SERVICE_NAME: String = "ServiceName-"
    }

    override fun onCleared() {
        try {
            nsdManager?.stopServiceDiscovery(discoveryListener)
        } catch (ignored: Exception) {
            // "Service discovery not active on discoveryListener",
            // thrown if starting the service discovery was unsuccessful earlier
        }
        Timber.d("onCleared called")
        super.onCleared()
    }

    fun onNsdServiceResolved(serviceInfo: NsdServiceInfo) {
        // Logic to handle a new service
        Timber.d("Resolve Succeeded: $serviceInfo")
    }

    fun onNsdServiceLost(service: NsdServiceInfo) {
        // Logic to handle when the network service is no longer available
        Timber.d("Service lost: $service")
    }

}

【问题讨论】:

    标签: android bonjour zeroconf nsd nsdmanager


    【解决方案1】:

    我通过以下方式解决了问题:

    1. 创建线程安全队列来存储待解决的服务
    2. 使用线程安全列表存储已解析服务的列表
    3. 使用原子布尔标志查看 ResolveListener 何时忙碌

    为了使解决方案更通用,我构建了一个 NdsHelper 抽象类。它有 2 个必须重写的函数:onNsdServiceResolved(NsdServiceInfo) 和 onNsdServiceLost(NsdServiceInfo)。

    我使用 Timber 来记录消息,但您可以用标准的 Log 函数替换它们。

    这是 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 = "_myservicetype._tcp."
            // Services' Names must start with this
            const val NSD_SERVICE_NAME: String = "MyServiceName-"
        }
    
        // 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() {
    
            // 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
                    var 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) {
                        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.i("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() {
            resolveListener =  object : NsdManager.ResolveListener {
    
                override fun onServiceResolved(service: NsdServiceInfo) {
                    Timber.d("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) {
                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)
    }
    

    这就是如何从 ViewModel 使用它(或者从一个活动或片段,如果你改变从哪里调用不同的辅助方法):

    import android.app.Application
    import android.content.Context
    import android.content.Intent
    import android.net.ConnectivityManager
    import android.net.nsd.NsdServiceInfo
    import androidx.lifecycle.AndroidViewModel
    import timber.log.Timber
    import java.util.*
    
    
    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
    
                // Put your custom logic here!!!
    
            }
    
            override fun onNsdServiceLost(service: NsdServiceInfo) {
                // A network service is no longer available
    
                // Put your custom logic here!!!
    
            }
        }
    
        // Block that is run when the view model is created
        init {
    
            // 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() {
            nsdHelper?.stopDiscovery()
            Timber.d("onCleared called")
            super.onCleared()
        }
    
    }
    

    【讨论】:

    • 不知道为什么你被否决了 - 我遇到了同样的问题并剪切并粘贴了你的解决方案,效果很好。谢谢
    • 是的,它现在对我有用。通过您的解决,我改进了我的服务。非常感谢。你的生命更安全)
    • 非常感谢。很好的解决方案。节省了我的时间并帮助了我很多。我觉得有些场景应该由 NsdManager 自己考虑和处理。
    【解决方案2】:

    我也遇到了同样的问题...而且我有时讨厌 Android API...

    无论如何,我的解决方案还远非完美,但至少要简单一些。

    我基本上使用java.util.concurrent.Semaphore 来阻止任何进一步的解决方案,直到当前解决方案完成。

    希望这对某人有所帮助。干杯!

    val semaphore = Semaphore(1)
    
    nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, this)
    
    override fun onServiceFound(service: NsdServiceInfo) {
      //
      // Do some fancy logic to filter out Services you don't need...
      //
      thread {
        semaphore.acquire()
        nsdManager.resolveService(service, this)
      }
    }
    
    override fun onResolveFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
      //
      // Handle errors...
      //
      semaphore.release()
    }
    
    override fun onServiceResolved(serviceInfo: NsdServiceInfo) {
      //
      // Service is resolved, do something with it...
      //
      semaphore.release()
    }
    
    

    【讨论】:

      【解决方案3】:

      我想出了一个使用 rxjava 的更简洁的解决方案

      注意:MdnsResolveListenerNsdManager.ResolveListener 的实例,MdnsDiscoveryListenerNsdManager.DiscoveryListener 的实例

      resolveSubject
          .observeOn(Schedulers.single())
          .toFlowable(BackpressureStrategy.BUFFER)
          .flatMapCompletable {
              val completable = CompletableFuture<Unit>()
      
              nsdManager.resolveService(it, MdnsResolveListener {
                  completable.complete(null)
                  handleMdnsEvent(it)
              })
      
              Completable.fromFuture(completable)
          }.subscribe()
      
      

      这里有一个更完整的示例,完成后记得释放资源:

      class ExampleHandler(private val nsdManager: NsdManager) {
          fun onListen(type: String) {
              val resolveSubject = BehaviorSubject.create<NsdServiceInfo>()
      
              resolveSubject
                  .observeOn(Schedulers.single())
                  .toFlowable(BackpressureStrategy.BUFFER)
                  .flatMapCompletable {
                      val completable = CompletableFuture<Unit>()
                      this.nsdManager.resolveService(it, MdnsResolveListener {
                          completable.complete(null)
                          handleMdnsEvent(it)
                      })
                      Completable.fromFuture(completable)
                  }.subscribe()
      
              val discoveryListener = MdnsDiscoveryListener(
                  this.nsdManager,
                  onResolveRequest = { resolveSubject.onNext(it) },
                  onEvent = { handleMdnsEvent(it) }
              )
      
              this.nsdManager.discoverServices(type, NsdManager.PROTOCOL_DNS_SD, discoveryListener)
          }
      
          private fun handleMdnsEvent(event: MdnsClientEvent) {
              TODO()
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-01-25
        • 2022-10-21
        • 2014-08-21
        • 2021-01-19
        • 2019-04-15
        • 2019-06-28
        • 2014-08-30
        • 1970-01-01
        相关资源
        最近更新 更多