feat(scale): 新增 UDP 广播兜底发现机制并恢复开机自启原始方案
- 新增 UdpBroadcastSender/UdpBroadcastReceiver,子设备定时广播,主设备监听,与 mDNS 互为备份 - 恢复 BootReceiver 为直接启动 Activity 的原始方式,删除 BootService - 移除 InitActivity 开机自启相关 Window flags - 移除 PyTorch 依赖,缩减安装包体积 - 删除仓库中的 release apk 和 pt 模型文件
This commit is contained in:
@@ -31,9 +31,11 @@ object ScaleServiceManager {
|
||||
|
||||
private var mdnsRegister: MdnsRegisterManager? = null
|
||||
private var wsServer: ScaleWebSocketServer? = null
|
||||
private var udpSender: UdpBroadcastSender? = null
|
||||
|
||||
// 以下仅主设备使用
|
||||
private var mdnsDiscovery: MdnsDiscoveryManager? = null
|
||||
private var udpReceiver: UdpBroadcastReceiver? = null
|
||||
private var wsClient: ScaleWebSocketClient? = null
|
||||
private var aggregator: ScaleDataAggregator? = null
|
||||
|
||||
@@ -66,6 +68,10 @@ object ScaleServiceManager {
|
||||
|
||||
if (role == DeviceRole.MASTER) {
|
||||
startMasterServices(context, deviceId)
|
||||
} else {
|
||||
// 子设备:启动 UDP 广播,作为 mDNS 的兜底发现机制
|
||||
val localIp = getLocalIp(context)
|
||||
udpSender = UdpBroadcastSender(deviceId, localIp).also { it.start() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,24 +99,35 @@ object ScaleServiceManager {
|
||||
}
|
||||
wsClient = client
|
||||
|
||||
// 统一的设备发现处理:mDNS 和 UDP 共用同一逻辑
|
||||
fun onDeviceFound(remoteId: String, host: String, port: Int) {
|
||||
if (remoteId == deviceId) return
|
||||
Log.d(TAG, "发现子设备 $remoteId ($host:$port), 建立连接")
|
||||
aggregator?.setDeviceIp(remoteId, host)
|
||||
client.connect(remoteId, host, port)
|
||||
}
|
||||
|
||||
// mDNS 发现:发现子设备 → 建立 WebSocket 连接;设备离线 → 断开连接并清除数据
|
||||
mdnsDiscovery = MdnsDiscoveryManager(context).also { discovery ->
|
||||
discovery.onDeviceFound = { remoteId, host, port ->
|
||||
// 不连接自身
|
||||
if (remoteId != deviceId) {
|
||||
Log.d(TAG, "发现子设备 $remoteId, 建立连接")
|
||||
// 记录子设备 IP,供 UI 展示使用
|
||||
aggregator?.setDeviceIp(remoteId, host)
|
||||
client.connect(remoteId, host, port)
|
||||
}
|
||||
onDeviceFound(remoteId, host, port)
|
||||
}
|
||||
discovery.onDeviceLost = { remoteId ->
|
||||
Log.d(TAG, "子设备离线 $remoteId, 断开连接并清除数据")
|
||||
client.disconnect(remoteId)
|
||||
aggregator?.removeDevice(remoteId)
|
||||
udpReceiver?.removeDevice(remoteId)
|
||||
}
|
||||
discovery.startDiscovery()
|
||||
}
|
||||
|
||||
// UDP 广播接收:作为 mDNS 的兜底,发现 mDNS 未能触发的子设备
|
||||
udpReceiver = UdpBroadcastReceiver().also { receiver ->
|
||||
receiver.onDeviceFound = { remoteId, host, port ->
|
||||
onDeviceFound(remoteId, host, port)
|
||||
}
|
||||
receiver.start()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,12 +135,16 @@ object ScaleServiceManager {
|
||||
*/
|
||||
fun stop() {
|
||||
mdnsDiscovery?.stopDiscovery()
|
||||
udpReceiver?.stop()
|
||||
udpSender?.stop()
|
||||
wsClient?.shutdown()
|
||||
aggregator?.stop()
|
||||
wsServer?.stop()
|
||||
mdnsRegister?.unregister()
|
||||
|
||||
mdnsDiscovery = null
|
||||
udpReceiver = null
|
||||
udpSender = null
|
||||
wsClient = null
|
||||
aggregator = null
|
||||
wsServer = null
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.shuwei.dish.match.scale
|
||||
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import java.net.DatagramPacket
|
||||
import java.net.DatagramSocket
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
/**
|
||||
* UDP 广播接收器(主设备运行)
|
||||
* 监听子设备发来的 UDP 广播包,解析后通过回调通知连接管理器
|
||||
* 与 mDNS 互为备份,任意一条路发现子设备均可建立 WebSocket 连接
|
||||
*/
|
||||
class UdpBroadcastReceiver {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "UdpBroadcastReceiver"
|
||||
private const val BUFFER_SIZE = 1024
|
||||
}
|
||||
|
||||
private val gson = Gson()
|
||||
private val executor = Executors.newSingleThreadExecutor()
|
||||
|
||||
/** 已发现的设备集合,避免重复触发回调,key=deviceId, value="host:port" */
|
||||
private val knownDevices = ConcurrentHashMap<String, String>()
|
||||
|
||||
@Volatile
|
||||
private var running = false
|
||||
private var socket: DatagramSocket? = null
|
||||
|
||||
/**
|
||||
* 发现新设备时的回调:(deviceId, host, port)
|
||||
* 与 MdnsDiscoveryManager.onDeviceFound 签名一致,可共用同一处理逻辑
|
||||
*/
|
||||
var onDeviceFound: ((deviceId: String, host: String, port: Int) -> Unit)? = null
|
||||
|
||||
/**
|
||||
* 开始监听 UDP 广播
|
||||
*/
|
||||
fun start() {
|
||||
if (running) return
|
||||
running = true
|
||||
executor.execute { listenLoop() }
|
||||
Log.d(TAG, "UDP 广播接收已启动, 监听端口=${UdpBroadcastSender.BROADCAST_PORT}")
|
||||
}
|
||||
|
||||
private fun listenLoop() {
|
||||
try {
|
||||
socket = DatagramSocket(UdpBroadcastSender.BROADCAST_PORT)
|
||||
val buf = ByteArray(BUFFER_SIZE)
|
||||
while (running) {
|
||||
val packet = DatagramPacket(buf, buf.size)
|
||||
socket?.receive(packet) ?: break
|
||||
val json = String(packet.data, 0, packet.length, Charsets.UTF_8)
|
||||
handlePacket(json)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (running) Log.w(TAG, "UDP 接收异常: ${e.message}")
|
||||
} finally {
|
||||
socket?.close()
|
||||
socket = null
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePacket(json: String) {
|
||||
try {
|
||||
val payload = gson.fromJson(json, UdpBroadcastSender.Payload::class.java)
|
||||
val key = "${payload.ip}:${payload.port}"
|
||||
// 同一设备已通知过则跳过,避免每5秒重复触发连接
|
||||
if (knownDevices.put(payload.deviceId, key) == key) return
|
||||
Log.d(TAG, "UDP 发现设备: ${payload.deviceId} -> ${payload.ip}:${payload.port}")
|
||||
onDeviceFound?.invoke(payload.deviceId, payload.ip, payload.port)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "解析 UDP 广播包失败: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除已知设备记录,设备离线时调用,确保设备重新上线后能再次触发回调
|
||||
*/
|
||||
fun removeDevice(deviceId: String) {
|
||||
knownDevices.remove(deviceId)
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止监听,释放资源
|
||||
*/
|
||||
fun stop() {
|
||||
running = false
|
||||
socket?.close()
|
||||
executor.shutdownNow()
|
||||
Log.d(TAG, "UDP 广播接收已停止")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.shuwei.dish.match.scale
|
||||
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import java.net.DatagramPacket
|
||||
import java.net.DatagramSocket
|
||||
import java.net.InetAddress
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.ScheduledFuture
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* UDP 广播发送器(子设备运行)
|
||||
* 每隔固定间隔向局域网广播本机的 deviceId、IP、WebSocket 端口
|
||||
* 作为 mDNS 的兜底发现机制,确保主设备能可靠发现子设备
|
||||
*/
|
||||
class UdpBroadcastSender(
|
||||
private val deviceId: String,
|
||||
private val localIp: String,
|
||||
private val wsPort: Int = MdnsRegisterManager.WS_PORT
|
||||
) {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "UdpBroadcastSender"
|
||||
/** UDP 广播目标端口,与 UdpBroadcastReceiver 保持一致 */
|
||||
const val BROADCAST_PORT = 8766
|
||||
/** 广播间隔(秒) */
|
||||
private const val INTERVAL_SEC = 5L
|
||||
}
|
||||
|
||||
private val gson = Gson()
|
||||
private val scheduler = Executors.newSingleThreadScheduledExecutor()
|
||||
private var task: ScheduledFuture<*>? = null
|
||||
|
||||
/** 广播数据包结构 */
|
||||
data class Payload(val deviceId: String, val ip: String, val port: Int)
|
||||
|
||||
/**
|
||||
* 开始定时广播
|
||||
*/
|
||||
fun start() {
|
||||
if (task != null) return
|
||||
task = scheduler.scheduleWithFixedDelay({
|
||||
sendBroadcast()
|
||||
}, 0, INTERVAL_SEC, TimeUnit.SECONDS)
|
||||
Log.d(TAG, "UDP 广播已启动, deviceId=$deviceId, ip=$localIp, port=$wsPort")
|
||||
}
|
||||
|
||||
private fun sendBroadcast() {
|
||||
try {
|
||||
val payload = gson.toJson(Payload(deviceId, localIp, wsPort))
|
||||
val data = payload.toByteArray(Charsets.UTF_8)
|
||||
DatagramSocket().use { socket ->
|
||||
socket.broadcast = true
|
||||
val packet = DatagramPacket(
|
||||
data, data.size,
|
||||
InetAddress.getByName("255.255.255.255"),
|
||||
BROADCAST_PORT
|
||||
)
|
||||
socket.send(packet)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "UDP 广播发送失败: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止广播,释放资源
|
||||
*/
|
||||
fun stop() {
|
||||
task?.cancel(false)
|
||||
task = null
|
||||
scheduler.shutdownNow()
|
||||
Log.d(TAG, "UDP 广播已停止")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user