我在 Android 应用程序中使用 NsdManager 来发现由我开发的另一台设备发布的 NSD 服务。我只在 Android App 上进行服务发现(这方面不需要服务注册)。网络上同时发布了多个同类型服务的实例。
我开始使用 Google 提供的示例代码(https://developer.android.com/training/connect-devices-wireless/nsd),但由于同时重复使用同一个解析器对象不止一个,我遇到了致命错误服务解决。然后我发现有几个人建议每次都创建一个新的解析器对象(比如在Listener already in use (Service Discovery) 中)。
我这样做了,致命错误被 Resolve Failure 错误代码 3 取代,这意味着解决过程处于活动状态。比以前好多了,但只有第一个服务被解决了,其余的都因为这个失败而被忽略了。
然后我发现有人建议通过递归地重新发送解析请求直到最终解决(NSNetworkManager.ResolveListener 消息 Android)来对错误代码 3 进行特殊处理。
我在 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")
}
}