|
@@ -4,64 +4,130 @@ import android.annotation.SuppressLint
|
|
|
import com.grkj.ui_base.business.ModbusBusinessManager
|
|
import com.grkj.ui_base.business.ModbusBusinessManager
|
|
|
import com.grkj.ui_base.utils.ble.BleSendDispatcher
|
|
import com.grkj.ui_base.utils.ble.BleSendDispatcher
|
|
|
import com.sik.cronjob.annotations.CronJob
|
|
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.Logger
|
|
|
import org.slf4j.LoggerFactory
|
|
import org.slf4j.LoggerFactory
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 检查钥匙信息任务
|
|
* 检查钥匙信息任务
|
|
|
|
|
+ *
|
|
|
|
|
+ * - 支持在检查过程中“挂起等待 isInLogin=true 后继续”
|
|
|
|
|
+ * - 防重入:同一时刻只会有一个检查任务在跑
|
|
|
|
|
+ * - 仍保留 CronJob 定时入口;也支持在进入登录页时延迟启动一次
|
|
|
*/
|
|
*/
|
|
|
-
|
|
|
|
|
class CheckKeyInfoTask {
|
|
class CheckKeyInfoTask {
|
|
|
|
|
+
|
|
|
private val logger: Logger = LoggerFactory.getLogger(this::class.java)
|
|
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
|
|
var isInLogin: Boolean = false
|
|
|
set(value) {
|
|
set(value) {
|
|
|
field = value
|
|
field = value
|
|
|
|
|
+ loginGate.value = value
|
|
|
if (value) {
|
|
if (value) {
|
|
|
- startCheckKeyInfo()
|
|
|
|
|
|
|
+ // 如果已经有一个延迟任务在排队,先取消再重新计时
|
|
|
|
|
+ startCheckKeyInfoJob?.cancel()
|
|
|
|
|
+ startCheckKeyInfoJob = scope.launch {
|
|
|
|
|
+ delay(60_000L)
|
|
|
|
|
+ safeCheckKeyInfo()
|
|
|
|
|
+ }
|
|
|
} else {
|
|
} 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")
|
|
@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) {
|
|
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) // 轻量轮询,避免忙等
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-}
|
|
|
|
|
|
|
+}
|