Эх сурвалжийг харах

refactor(更新)
- 优化钥匙信息检查任务逻辑,支持在检查过程中挂起等待
- 钥匙信息检查任务增加防重入机制

周文健 2 сар өмнө
parent
commit
caae3b3837

+ 96 - 30
ui-base/src/main/java/com/grkj/ui_base/service/CheckKeyInfoTask.kt

@@ -4,64 +4,130 @@ import android.annotation.SuppressLint
 import com.grkj.ui_base.business.ModbusBusinessManager
 import com.grkj.ui_base.utils.ble.BleSendDispatcher
 import com.sik.cronjob.annotations.CronJob
-import com.sik.sikcore.thread.ThreadUtils
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 
 /**
  * 检查钥匙信息任务
+ *
+ * - 支持在检查过程中“挂起等待 isInLogin=true 后继续”
+ * - 防重入:同一时刻只会有一个检查任务在跑
+ * - 仍保留 CronJob 定时入口;也支持在进入登录页时延迟启动一次
  */
-
 class CheckKeyInfoTask {
+
     private val logger: Logger = LoggerFactory.getLogger(this::class.java)
 
+    /** 私有协程域(IO) */
+    private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
+
+    /** 防重入锁:避免并发跑多个 check */
+    private val runMutex = Mutex()
+
+    /** 登录闸门:true=放行;false=挂起等待 */
+    private val loginGate = MutableStateFlow(false)
+
+    /** 延迟启动任务的句柄(可取消) */
+    private var startCheckKeyInfoJob: Job? = null
+
     /**
      * 是否在登录界面
+     * 设为 true:打开闸门,并延迟 60s 启动一次检查
+     * 设为 false:关闭闸门,正在执行的检查会在下一个 await 处挂起
      */
     var isInLogin: Boolean = false
         set(value) {
             field = value
+            loginGate.value = value
             if (value) {
-                startCheckKeyInfo()
+                // 如果已经有一个延迟任务在排队,先取消再重新计时
+                startCheckKeyInfoJob?.cancel()
+                startCheckKeyInfoJob = scope.launch {
+                    delay(60_000L)
+                    safeCheckKeyInfo()
+                }
             } else {
-                ThreadUtils.cancel(startCheckKeyInfoJob)
+                // 退出登录页时,不强制取消正在跑的任务——让它在 await 处挂起即可
+                startCheckKeyInfoJob?.cancel()
+                startCheckKeyInfoJob = null
             }
         }
 
-    /**
-     * 开始检查钥匙信息任务
-     */
-    private var startCheckKeyInfoJob: String = ""
-
-    /**
-     * 开始检查钥匙信息延迟60s
-     */
-    private fun startCheckKeyInfo() {
-        startCheckKeyInfoJob = ThreadUtils.runOnIODelayed(60 * 1000L) {
-            checkKeyInfo()
-        }
+    /** 对外:定时器触发的入口(保持不变) */
+    @SuppressLint("MissingPermission")
+    @CronJob(intervalMillis = 30 * 60_000L, initialDelay = 0, runOnMainThread = false)
+    fun checkKeyInfo() {
+        // 用协程跑,避免阻塞 CronJob 线程
+        scope.launch { safeCheckKeyInfo() }
     }
 
     /**
-     * 检查钥匙信息
+     * 受互斥保护的检查逻辑
      */
     @SuppressLint("MissingPermission")
-    @CronJob(intervalMillis = 30 * 60_000L, initialDelay = 0, runOnMainThread = false)
-    fun checkKeyInfo() {
-        logger.info("开始检查钥匙信息")
-        val existsKey = ModbusBusinessManager.getExistsKey()
-        ThreadUtils.runOnIO {
+    private suspend fun safeCheckKeyInfo() = runMutex.withLock {
+        try {
+            logger.info("开始检查钥匙信息")
+            // 第一次进入也要尊重闸门:如果不在登录页,这里直接挂起,直到回到登录页
+            awaitLogin()
+
+            val existsKey = ModbusBusinessManager.getExistsKey()
             for (bean in existsKey) {
-                bean.mac?.let { mac ->
-                    if (BleSendDispatcher.canConnect() && isInLogin) {
-                        BleSendDispatcher.submit(mac) {
-                            if (isInLogin) {
-                                BleSendDispatcher.scheduleDisconnect(mac)
-                            }
-                        }
+                val mac = bean.mac ?: continue
+
+                // 每个 mac 开始前都尊重闸门;如果离开登录页,这里会挂起等待
+                awaitLogin()
+
+                // 还可以防止过度并发:如果连接已满,这里做一个小等候(可按需调整)
+                waitUntilCanConnect()
+
+                if (!isInLogin) {
+                    // await 之后仍有可能被关闭;稳妥再判一次
+                    logger.info("检测到不在登录页,暂停检查;mac=$mac")
+                    awaitLogin() // 再挂起一次,直到登录页回归
+                }
+
+                BleSendDispatcher.submit(mac) { ok ->
+                    // 回调时:电量/状态已更新完毕
+                    if (isInLogin) {
+                        // 登录页场景:礼貌性断开(由队列自己控制是否立即断)
+                        BleSendDispatcher.scheduleDisconnect(mac)
                     }
                 }
             }
+            logger.info("检查钥匙信息结束")
+        } catch (ce: CancellationException) {
+            logger.warn("检查任务被取消:${ce.message}")
+        } catch (e: Throwable) {
+            logger.error("检查任务异常", e)
+        }
+    }
+
+    /**
+     * 挂起直到登录页(isInLogin=true)
+     * - 不 busy-wait;真正挂起,省电省 CPU
+     */
+    private suspend fun awaitLogin() {
+        if (loginGate.value) return
+        logger.info("不在登录页,挂起等待 …")
+        loginGate.first { it } // 挂起直到变为 true
+        logger.info("登录页已就绪,恢复检查")
+    }
+
+    /**
+     * 如果连接达到上限,则小睡等待;防止 submit 直接把队列堆爆
+     * (这里选了一个很轻的策略:短暂重试,避免卡死。你也可以结合信号量/通道做更细控制)
+     */
+    private suspend fun waitUntilCanConnect(maxWaitMillis: Long = 5_000L) {
+        val start = System.currentTimeMillis()
+        while (!BleSendDispatcher.canConnect()) {
+            if (System.currentTimeMillis() - start > maxWaitMillis) break
+            delay(100) // 轻量轮询,避免忙等
         }
     }
-}
+}