我开始使用Google提供的示例代码(https://developer.android.com/training/connect-devices-wirelessly/nsd),但由于同时为多个服务解析重用相同的解析器对象而导致致命错误。 然后我发现有几个人建议每次创建一个新的解析器对象(如Listener already in use (Service Discovery))。
我这样做,致命错误被Resolve Failure错误代码3所替换,这意味着解析过程是活动的。比以前好,但仅第一个服务得到了解决,其余的由于此故障被忽略。
然后我发现有人建议通过递归重新发送解析请求来特别处理Error Code 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
我感到沮丧的是,谷歌的官方示例没有正确处理这些问题。nsd_chat示例使用单个resolver对象,当发布了多个具有相同类型的服务时,就会失败。
你能建议更好的解决方案吗?或者对我的代码进行任何改进?
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")
}
}