2

在我的 Android 应用程序中,我必须使用 avahi 0.6.32(Linux 的 Bonjour/zeroconf 守护程序)显示由另一台机器(RPi 3B con Raspbian Stretch)发布的网络上可用服务的列表。我使用 NsdManager 在 Android 手机上获取列表。但是,在测试期间,我遇到了一个奇怪的行为:当我在手机中关闭并重新打开 WiFi 时,大多数情况下服务会被发现,然后立即丢失然后重新发现(而不是只发现一次)并且所有的不到一秒钟。

这会导致服务列表短暂出现在屏幕上,然后消失,最后几乎立即重新出现,但仍然非常明显。它强制服务被发现和解决两次。由于我希望有很多电话连接到同一个 LAN 中的多个服务,因此我想避免网络过载。

我不确定我做错了什么,或者这只是 NsdManager 在 Android 上的工作方式。为了减少可能的问题来源,我注释掉了解决服务的行(只留下日志消息),但问题仍然存在(超过一半的时间)。

我该如何解决?

从 Logcat 中提取的示例:

2019-09-26 04:33:50.262 27300-27420/com.example.myapp D/NsdHelper$initializeDiscoveryListener:服务发现成功:名称:MyService-1490247,类型:_mytype._tcp.,主机:null,端口:0, txt记录:

2019-09-26 04:33:50.879 27300-27420/com.example.myapp D/NsdHelper$initializeDiscoveryListener:服务丢失:名称:MyService-1490247,类型:_mytype._tcp.,主机:null,端口:0,txtRecord :

2019-09-26 04:33:50.970 27300-27420/com.example.myapp D/NsdHelper$initializeDiscoveryListener:服务发现成功:名称:MyService-1490247,类型:_mytype._tcp.,主机:null,端口:0, txt记录:

我正在使用 Android O 在三星 Note 8 上进行测试。我尝试了 2 个不同的 WiFi 路由器,并且行为是相同的。

我正在使用以下 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 = "_mytype._tcp."
            // Services' Names must start with this
            const val NSD_SERVICE_NAME: String = "MyService-"
        }

        // 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() {

            Timber.d("Initialize DiscoveryListener")

            // 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
                    synchronized(pendingNsdServices) {
                        val 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) {
                        val 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.d("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() {

            Timber.d("Initialize ResolveListener")

            resolveListener =  object : NsdManager.ResolveListener {

                override fun onServiceResolved(service: NsdServiceInfo) {
                    Timber.d("Service 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) {
                Timber.d("stopDiscovery() called")
                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)
    }

在 View Model 的 init 块上,我启动服务发现:

    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
                // Update list of available services
                updateServicesList()
            }

            override fun onNsdServiceLost(service: NsdServiceInfo) {
                // A network service is no longer available

                // Update list of available services
                updateServicesList()

            }
        }

        // Block that is run when the view model is created
        init {
            Timber.d("init block called")

            // 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() {
            Timber.d("onCleared called")
            nsdHelper?.stopDiscovery()
            super.onCleared()
        }

        private fun updateServicesList() {
            // Put the logic here to show the services on screen
            return
        }
    }

注意:Timber 是一个日志工具,几乎可以直接替代标准 Log 命令,但更易于使用。

4

0 回答 0