fix(scale): 修复主设备无法发现子设备及IP显示未知的问题

- UdpBroadcastReceiver 补充 socket.broadcast=true,修复广播包被内核丢弃
- MdnsDiscoveryManager 改为串行解析队列,修复多设备并发解析报 FAILURE_ALREADY_ACTIVE
- ScaleWebSocketServer 推送数据时动态填充本机 IP,解决子设备 IP 始终显示未知
This commit is contained in:
2026-04-13 09:00:51 +08:00
parent c72d30e88a
commit 9a77733d24
4 changed files with 49 additions and 6 deletions
@@ -4,10 +4,14 @@ import android.content.Context
import android.net.nsd.NsdManager import android.net.nsd.NsdManager
import android.net.nsd.NsdServiceInfo import android.net.nsd.NsdServiceInfo
import android.util.Log import android.util.Log
import java.util.LinkedList
/** /**
* mDNS 设备发现管理器(仅主设备运行) * mDNS 设备发现管理器(仅主设备运行)
* 持续扫描局域网,发现或丢失 DishMatch 子设备时通过回调通知连接管理器 * 持续扫描局域网,发现或丢失 DishMatch 子设备时通过回调通知连接管理器
*
* 注意:Android NsdManager.resolveService 不支持并发调用,多台子设备同时被发现时
* 会报 FAILURE_ALREADY_ACTIVE(3) 导致解析失败。此处使用串行队列逐一解析。
*/ */
class MdnsDiscoveryManager(private val context: Context) { class MdnsDiscoveryManager(private val context: Context) {
@@ -22,6 +26,11 @@ class MdnsDiscoveryManager(private val context: Context) {
private var discoveryListener: NsdManager.DiscoveryListener? = null private var discoveryListener: NsdManager.DiscoveryListener? = null
private var isDiscovering = false private var isDiscovering = false
/** 待解析队列,串行处理避免并发解析失败 */
private val resolveQueue = LinkedList<NsdServiceInfo>()
@Volatile
private var isResolving = false
/** /**
* 发现新设备时的回调:(deviceId, host, port) * 发现新设备时的回调:(deviceId, host, port)
*/ */
@@ -62,7 +71,7 @@ class MdnsDiscoveryManager(private val context: Context) {
// 只处理 DishMatch 服务 // 只处理 DishMatch 服务
if (!service.serviceName.startsWith("DishMatch-")) return if (!service.serviceName.startsWith("DishMatch-")) return
Log.d(TAG, "发现设备: ${service.serviceName}") Log.d(TAG, "发现设备: ${service.serviceName}")
resolveService(service) enqueueResolve(service)
} }
override fun onServiceLost(service: NsdServiceInfo) { override fun onServiceLost(service: NsdServiceInfo) {
@@ -87,18 +96,45 @@ class MdnsDiscoveryManager(private val context: Context) {
nsdManager.resolveService(service, object : NsdManager.ResolveListener { nsdManager.resolveService(service, object : NsdManager.ResolveListener {
override fun onResolveFailed(info: NsdServiceInfo, errorCode: Int) { override fun onResolveFailed(info: NsdServiceInfo, errorCode: Int) {
Log.e(TAG, "解析设备地址失败: ${info.serviceName}, errorCode=$errorCode") Log.e(TAG, "解析设备地址失败: ${info.serviceName}, errorCode=$errorCode")
// 解析结束(失败),继续处理队列中下一个
processNextResolve()
} }
override fun onServiceResolved(info: NsdServiceInfo) { override fun onServiceResolved(info: NsdServiceInfo) {
val deviceId = info.serviceName.removePrefix("DishMatch-") val deviceId = info.serviceName.removePrefix("DishMatch-")
val host = info.host?.hostAddress ?: return val host = info.host?.hostAddress ?: run {
processNextResolve()
return
}
val port = info.port val port = info.port
Log.d(TAG, "设备地址已解析: $deviceId -> $host:$port") Log.d(TAG, "设备地址已解析: $deviceId -> $host:$port")
onDeviceFound?.invoke(deviceId, host, port) onDeviceFound?.invoke(deviceId, host, port)
// 解析结束(成功),继续处理队列中下一个
processNextResolve()
} }
}) })
} }
/** 将待解析服务加入队列,若当前无解析任务则立即开始 */
@Synchronized
private fun enqueueResolve(service: NsdServiceInfo) {
resolveQueue.add(service)
if (!isResolving) {
processNextResolve()
}
}
/** 从队列取出下一个服务进行解析 */
@Synchronized
private fun processNextResolve() {
val next = resolveQueue.poll() ?: run {
isResolving = false
return
}
isResolving = true
resolveService(next)
}
/** /**
* 停止扫描,在应用退出时调用 * 停止扫描,在应用退出时调用
*/ */
@@ -112,5 +148,9 @@ class MdnsDiscoveryManager(private val context: Context) {
} }
} }
discoveryListener = null discoveryListener = null
synchronized(this) {
resolveQueue.clear()
isResolving = false
}
} }
} }
@@ -63,7 +63,7 @@ object ScaleServiceManager {
Log.d(TAG, "启动秤服务, deviceId=$deviceId, role=$role") Log.d(TAG, "启动秤服务, deviceId=$deviceId, role=$role")
// 所有设备:启动 WebSocket 服务端 + mDNS 注册 // 所有设备:启动 WebSocket 服务端 + mDNS 注册
wsServer = ScaleWebSocketServer(deviceId).also { it.start() } wsServer = ScaleWebSocketServer(deviceId, context).also { it.start() }
mdnsRegister = MdnsRegisterManager(context).also { it.register(deviceId) } mdnsRegister = MdnsRegisterManager(context).also { it.register(deviceId) }
if (role == DeviceRole.MASTER) { if (role == DeviceRole.MASTER) {
@@ -1,7 +1,9 @@
package com.shuwei.dish.match.scale package com.shuwei.dish.match.scale
import android.content.Context
import android.util.Log import android.util.Log
import com.google.gson.Gson import com.google.gson.Gson
import com.shuwei.dish.match.utils.NetworkUtil
import com.shuwei.dish.match.utils.WeightUtil import com.shuwei.dish.match.utils.WeightUtil
import org.java_websocket.WebSocket import org.java_websocket.WebSocket
import org.java_websocket.handshake.ClientHandshake import org.java_websocket.handshake.ClientHandshake
@@ -12,7 +14,7 @@ import java.net.InetSocketAddress
* 秤数据 WebSocket 服务端(主设备和子设备均运行) * 秤数据 WebSocket 服务端(主设备和子设备均运行)
* 监听 WeightUtil 回调,将本机秤数据实时推送给所有已连接的主设备客户端 * 监听 WeightUtil 回调,将本机秤数据实时推送给所有已连接的主设备客户端
*/ */
class ScaleWebSocketServer(private val deviceId: String) { class ScaleWebSocketServer(private val deviceId: String, private val context: Context) {
companion object { companion object {
private const val TAG = "ScaleWebSocketServer" private const val TAG = "ScaleWebSocketServer"
@@ -53,7 +55,8 @@ class ScaleWebSocketServer(private val deviceId: String) {
address = address, address = address,
weight = weight, weight = weight,
state = state, state = state,
ts = now ts = now,
ip = NetworkUtil.getLocalIpAddress(context)
) )
latestData[address] = data latestData[address] = data
broadcast(gson.toJson(data)) broadcast(gson.toJson(data))
@@ -47,7 +47,7 @@ class UdpBroadcastReceiver {
private fun listenLoop() { private fun listenLoop() {
try { try {
socket = DatagramSocket(UdpBroadcastSender.BROADCAST_PORT) socket = DatagramSocket(UdpBroadcastSender.BROADCAST_PORT).also { it.broadcast = true }
val buf = ByteArray(BUFFER_SIZE) val buf = ByteArray(BUFFER_SIZE)
while (running) { while (running) {
val packet = DatagramPacket(buf, buf.size) val packet = DatagramPacket(buf, buf.size)