From 379d784f70b4afccd7aff08f37307777f4555efb Mon Sep 17 00:00:00 2001 From: dongliang Date: Wed, 29 Apr 2026 15:27:06 +0930 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=8E=BB=E6=8A=96=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E6=9A=82=E5=AD=98=E5=90=88=E5=B9=B6=EF=BC=8C=E4=B8=8D=E4=B8=A2?= =?UTF-8?q?=E5=BC=83=E6=B6=88=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题:多条MQTT消息同时到达(间隔<1s),去抖直接丢弃后续消息。 旧版是延迟处理而非丢弃。 修复: - 去抖窗口内的消息暂存到pendingJsons - 1s后延迟任务统一处理暂存消息(只加ID,不重复震动) - 合并后统一发一次NewTaskArrived事件更新横幅 - MainActivity改为监听NewTaskArrived事件显示横幅 时序示例: t=0ms: 消息1(A) → 立即处理, pendingTaskIds=[A], 横幅"1条" t=100ms: 消息2(B) → 暂存 t=200ms: 消息3(C) → 暂存 t=1000ms: 延迟任务 → 处理B+C, pendingTaskIds=[A,B,C], 横幅"3条" Co-Authored-By: Claude Opus 4.6 (1M context) --- .../java/com/xiaoqu/watch/app/MainActivity.kt | 15 ++-- .../service/manager/NotificationManager.kt | 86 ++++++++++++++----- .../manager/NotificationManagerTest.kt | 19 ++-- 3 files changed, 79 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/com/xiaoqu/watch/app/MainActivity.kt b/app/src/main/java/com/xiaoqu/watch/app/MainActivity.kt index e6f96c1..96a7038 100644 --- a/app/src/main/java/com/xiaoqu/watch/app/MainActivity.kt +++ b/app/src/main/java/com/xiaoqu/watch/app/MainActivity.kt @@ -82,22 +82,21 @@ class MainActivity : AppCompatActivity() { // ===== MQTT 新任务 → 通知横幅 ===== - /** 监听 MQTT 消息,type=1 时通知横幅 */ + /** 监听 MQTT 消息和通知事件 */ private fun observeMqttMessages() { activityScope.launch { eventBus.events.collect { event -> when (event) { is AppEvent.MqttMessageReceived -> { if (event.type == 1) { - // 交给 NotificationManager 处理(去抖+震动+亮屏+事件) - val handled = notificationManager.onNewTaskMessage(event.rawJson) - if (handled) { - // 显示横幅 - notificationBanner.show(notificationManager.pendingCount) - } + // 交给 NotificationManager 处理(去抖+合并+震动+亮屏) + notificationManager.onNewTaskMessage(event.rawJson) } } - // 横幅点击由 HomeFragment 处理跳转 + // NotificationManager 处理完后发出事件 → 显示/更新横幅 + is AppEvent.NewTaskArrived -> { + notificationBanner.show(event.count) + } else -> {} } } diff --git a/app/src/main/java/com/xiaoqu/watch/service/manager/NotificationManager.kt b/app/src/main/java/com/xiaoqu/watch/service/manager/NotificationManager.kt index 5cc0cf4..da47fcc 100644 --- a/app/src/main/java/com/xiaoqu/watch/service/manager/NotificationManager.kt +++ b/app/src/main/java/com/xiaoqu/watch/service/manager/NotificationManager.kt @@ -47,6 +47,12 @@ class NotificationManager @Inject constructor( /** 上次消息处理时间(去抖用) */ private var lastMessageTime = 0L + /** 去抖窗口内暂存的消息(延迟合并处理,不丢弃) */ + private val pendingJsons = mutableListOf() + + /** 去抖延迟任务 */ + private var debounceJob: kotlinx.coroutines.Job? = null + /** 上次统计数据(对比红点用) */ var lastStats: TaskStatistics? = null @@ -55,46 +61,80 @@ class NotificationManager @Inject constructor( /** * 处理 MQTT type=1 新任务消息 + * 去抖策略:1s 窗口内的消息暂存,窗口结束后合并处理(不丢弃) * @param rawJson MQTT 消息原始 JSON - * @return true=已处理,false=被去抖过滤 */ - fun onNewTaskMessage(rawJson: String): Boolean { - // 1. 去抖:1s 内重复消息忽略 + fun onNewTaskMessage(rawJson: String) { val now = System.currentTimeMillis() + if (now - lastMessageTime < DEBOUNCE_MS) { - Timber.d("通知: 去抖过滤 (距上条 ${now - lastMessageTime}ms)") - return false + // 去抖窗口内 → 暂存,等延迟任务统一处理 + pendingJsons.add(rawJson) + Timber.d("通知: 去抖暂存 (已暂存 ${pendingJsons.size} 条)") + return } + + // 窗口外 → 立即处理本条 + 启动延迟任务处理后续暂存 lastMessageTime = now + processMessage(rawJson) - // 2. 解析任务 ID - val taskIds = parseTaskIds(rawJson) - if (taskIds.isEmpty()) { - Timber.w("通知: 消息中无有效任务 ID") - return false - } - - // 3. 加入未读列表(去重) - for (id in taskIds) { - if (id !in _pendingTaskIds) { - _pendingTaskIds.add(id) + // 启动延迟任务:1s 后处理暂存的消息 + debounceJob?.cancel() + debounceJob = scope.launch { + kotlinx.coroutines.delay(DEBOUNCE_MS) + if (pendingJsons.isNotEmpty()) { + Timber.d("通知: 处理暂存的 ${pendingJsons.size} 条消息") + val jsons = pendingJsons.toList() + pendingJsons.clear() + lastMessageTime = System.currentTimeMillis() + for (json in jsons) { + processMessageSilent(json) // 只加 ID,不重复震动 + } + // 合并后统一发一次事件 + notifyUi() } } - Timber.d("通知: 收到 ${taskIds.size} 个新任务, 当前未读 ${_pendingTaskIds.size}") + } - // 4. 震动 + 亮屏 + /** 处理单条消息(完整:解析+震动+亮屏+事件) */ + private fun processMessage(rawJson: String) { + val taskIds = parseTaskIds(rawJson) + if (taskIds.isEmpty()) return + + addTaskIds(taskIds) + + // 震动 + 亮屏(只在首条时触发,暂存的合并后不重复震动) val pattern = VibrationDefaults.getPattern(PLAN_NEW_MESSAGE) if (pattern != null) { vibrationController.executePattern(pattern) } screenController.turnOn() - // 5. 发送事件通知 - scope.launch { - eventBus.emit(AppEvent.NewTaskArrived(taskIds, _pendingTaskIds.size)) - } + notifyUi() + } - return true + /** 静默处理(只加 ID,不震动不亮屏,用于合并暂存消息) */ + private fun processMessageSilent(rawJson: String) { + val taskIds = parseTaskIds(rawJson) + if (taskIds.isEmpty()) return + addTaskIds(taskIds) + } + + /** 加入未读列表(去重) */ + private fun addTaskIds(taskIds: List) { + for (id in taskIds) { + if (id !in _pendingTaskIds) { + _pendingTaskIds.add(id) + } + } + Timber.d("通知: 当前未读 ${_pendingTaskIds.size}") + } + + /** 通知 UI 更新(横幅+红点) */ + private fun notifyUi() { + scope.launch { + eventBus.emit(AppEvent.NewTaskArrived(_pendingTaskIds.toList(), _pendingTaskIds.size)) + } } /** 消费所有未读消息(用户已查看列表) */ diff --git a/app/src/test/java/com/xiaoqu/watch/service/manager/NotificationManagerTest.kt b/app/src/test/java/com/xiaoqu/watch/service/manager/NotificationManagerTest.kt index 824bd52..2b0be1a 100644 --- a/app/src/test/java/com/xiaoqu/watch/service/manager/NotificationManagerTest.kt +++ b/app/src/test/java/com/xiaoqu/watch/service/manager/NotificationManagerTest.kt @@ -30,20 +30,19 @@ class NotificationManagerTest { @Test fun `onNewTaskMessage - first message should be handled`() { val json = """{"id":"100"}""" - val result = manager.onNewTaskMessage(json) - assertTrue(result) + manager.onNewTaskMessage(json) assertEquals(1, manager.pendingCount) } @Test - fun `onNewTaskMessage - duplicate within 1s should be filtered`() { - val json = """{"id":"100"}""" - manager.onNewTaskMessage(json) - // 立即再发一条(间隔 <1s) - val result = manager.onNewTaskMessage(json) - assertFalse(result) - // 只有第一条被处理 - assertEquals(1, manager.pendingCount) + fun `onNewTaskMessage - duplicate within 1s should be buffered not dropped`() { + val json1 = """{"id":"100"}""" + val json2 = """{"id":"200"}""" + manager.onNewTaskMessage(json1) + // 立即再发一条(间隔 <1s)→ 暂存,不丢弃 + manager.onNewTaskMessage(json2) + // 第一条立即处理,第二条暂存等延迟处理 + assertEquals(1, manager.pendingCount) // 暂存的还没处理 } @Test