首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >NSdManager ResolveListener错误代码3:已激活的故障

NSdManager ResolveListener错误代码3:已激活的故障
EN

Stack Overflow用户
提问于 2019-09-14 23:31:08
回答 3查看 2.4K关注 0票数 9

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

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

我这样做了,致命的错误被一个“解决失败”错误代码3所取代,这意味着“解决”进程是活动的。比以前更好,但是只有第一个服务被解决了,其余的由于这个失败而被忽略。

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

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

  1. 我相信我正在创建许多额外的Resolver对象,并且我不确定它们以后是否被垃圾收集。
  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

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

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

代码语言:javascript
复制
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")
    }

}
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2019-09-18 17:35:29

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

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

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

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

这是NsdHelper类(Kotlin代码):

代码语言:javascript
复制
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 (或活动或片段,如果从何处调用不同的助手方法)使用它:

代码语言:javascript
复制
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()
    }

}
票数 8
EN

Stack Overflow用户

发布于 2021-07-17 20:45:24

我只是遇到同样的问题..。有时候我是多么讨厌Android API..。

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

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

希望这能帮上忙。干杯!

代码语言:javascript
复制
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()
}
票数 4
EN

Stack Overflow用户

发布于 2021-09-12 10:57:35

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

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

代码语言:javascript
复制
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()

下面是一个更完整的示例,只需记住在完成之后释放资源:

代码语言:javascript
复制
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()
    }
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/57940021

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档