NSdManager ResolveListener 错误代码 3:失败已经激活
NSdManager ResolveListener Error Code 3: Failure Already active
我在 Android 应用程序中使用 NsdManager 来发现由我开发的另一台设备发布的 NSD 服务。我只在 Android App 上做服务发现(这边不需要服务注册)。网络上同时发布了多个同类型服务实例。
我开始使用 Google (https://developer.android.com/training/connect-devices-wirelessly/nsd) 提供的示例代码,但由于同时为多个服务解析重复使用同一个解析器对象,我遇到了致命错误。
然后我发现有几个人建议每次都创建一个新的解析器对象(比如 Listener already in use (Service Discovery))。
我这样做了,致命错误被替换为解决失败错误代码 3,这意味着解决过程处于活动状态。比以前好多了,但只解决了第一个服务,由于这次失败,其余的都被忽略了。
然后我发现有人建议对错误代码 3 进行特殊处理,方法是递归地重新发送解析请求,直到它最终被解析 (NSNetworkManager.ResolveListener messages Android)。
我在 Kotlin 中实现了这个解决方案,它有点管用,但我不是很满意,因为:
- 我相信我正在创建很多额外的 Resolver 对象
而且我不确定它们以后是否会被垃圾回收。
- 我正在循环重试几次,可能会导致额外的和
对设备和网络造成不必要的负担。不确定我是否
在再次调用服务解析之前应该添加一个短暂的睡眠。
- 如果网络有问题,程序可能会尝试千次
次解决相同的服务,而不是仅仅放弃
解决并等待再次发现该服务。
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")
}
}
我通过以下方式解决了这个问题:
- 正在创建一个线程安全队列来存储待解决的服务
- 使用线程安全列表存储已解析服务的列表
- 使用原子布尔标志查看 ResolveListener 何时忙碌
为了使解决方案更通用,我构建了一个 NdsHelper 抽象 class。它有 2 个必须重写的函数:onNsdServiceResolved(NsdServiceInfo) 和 onNsdServiceLost(NsdServiceInfo)。
我正在使用 Timber 来记录消息,但您可以使用标准日志功能替换它们。
这是 NsdHelper class(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(或者从 activity 或片段,如果你改变从哪里调用不同的辅助方法):
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()
}
}
我只是 运行 遇到了同样的问题...天哪,有时我多么讨厌 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()
}
我想出了一个使用 rxjava 的更简洁的解决方案
注:MdnsResolveListener
是NsdManager.ResolveListener
的实例,MdnsDiscoveryListener
是NsdManager.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()
}
}
我在 Android 应用程序中使用 NsdManager 来发现由我开发的另一台设备发布的 NSD 服务。我只在 Android App 上做服务发现(这边不需要服务注册)。网络上同时发布了多个同类型服务实例。
我开始使用 Google (https://developer.android.com/training/connect-devices-wirelessly/nsd) 提供的示例代码,但由于同时为多个服务解析重复使用同一个解析器对象,我遇到了致命错误。 然后我发现有几个人建议每次都创建一个新的解析器对象(比如 Listener already in use (Service Discovery))。
我这样做了,致命错误被替换为解决失败错误代码 3,这意味着解决过程处于活动状态。比以前好多了,但只解决了第一个服务,由于这次失败,其余的都被忽略了。
然后我发现有人建议对错误代码 3 进行特殊处理,方法是递归地重新发送解析请求,直到它最终被解析 (NSNetworkManager.ResolveListener messages Android)。
我在 Kotlin 中实现了这个解决方案,它有点管用,但我不是很满意,因为:
- 我相信我正在创建很多额外的 Resolver 对象 而且我不确定它们以后是否会被垃圾回收。
- 我正在循环重试几次,可能会导致额外的和 对设备和网络造成不必要的负担。不确定我是否 在再次调用服务解析之前应该添加一个短暂的睡眠。
- 如果网络有问题,程序可能会尝试千次 次解决相同的服务,而不是仅仅放弃 解决并等待再次发现该服务。
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")
}
}
我通过以下方式解决了这个问题:
- 正在创建一个线程安全队列来存储待解决的服务
- 使用线程安全列表存储已解析服务的列表
- 使用原子布尔标志查看 ResolveListener 何时忙碌
为了使解决方案更通用,我构建了一个 NdsHelper 抽象 class。它有 2 个必须重写的函数:onNsdServiceResolved(NsdServiceInfo) 和 onNsdServiceLost(NsdServiceInfo)。
我正在使用 Timber 来记录消息,但您可以使用标准日志功能替换它们。
这是 NsdHelper class(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(或者从 activity 或片段,如果你改变从哪里调用不同的辅助方法):
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()
}
}
我只是 运行 遇到了同样的问题...天哪,有时我多么讨厌 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()
}
我想出了一个使用 rxjava 的更简洁的解决方案
注:MdnsResolveListener
是NsdManager.ResolveListener
的实例,MdnsDiscoveryListener
是NsdManager.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()
}
}