|
|
@@ -1,151 +1,151 @@
|
|
|
-package com.grkj.ui_base.service
|
|
|
-
|
|
|
-import android.annotation.SuppressLint
|
|
|
-import com.grkj.ui_base.business.HardwareBusinessManager
|
|
|
-import com.grkj.data.hardware.ble.BleSendDispatcher
|
|
|
-import com.sik.cronjob.annotations.CronJob
|
|
|
-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
|
|
|
-import kotlinx.coroutines.flow.debounce
|
|
|
-import kotlinx.coroutines.flow.distinctUntilChanged
|
|
|
-import kotlinx.coroutines.flow.map
|
|
|
-
|
|
|
-/**
|
|
|
- * 检查钥匙信息任务
|
|
|
- *
|
|
|
- * - 支持在检查过程中“挂起等待 isInLogin=true 后继续”
|
|
|
- * - 防重入:同一时刻只会有一个检查任务在跑
|
|
|
- * - 仍保留 CronJob 定时入口;也支持在进入登录页时延迟启动一次
|
|
|
- */
|
|
|
-class CheckKeyInfoTask {
|
|
|
-
|
|
|
- private val logger: Logger = LoggerFactory.getLogger(this::class.java)
|
|
|
- private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
|
|
- private val runMutex = Mutex()
|
|
|
-
|
|
|
- /** 闸门:true = 允许跑(即 isInLogin == false) */
|
|
|
- private val runGate = MutableStateFlow(false)
|
|
|
-
|
|
|
- private var startCheckKeyInfoJob: Job? = null
|
|
|
-
|
|
|
- /** 参数可按需调整 */
|
|
|
- private companion object {
|
|
|
- const val STABLE_MS = 1200L // 退出登录需稳定的时间窗
|
|
|
- const val DELAY_AFTER_EXIT_MS = 60_000L // 退出登录后再延迟启动(低优先级)
|
|
|
- const val CHECK_TIMEOUT_MS = 10_000L // 单次 BLE 信息获取的超时时间
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 登录态:false = 允许跑;true = 暂停(必须在非登录下运行)
|
|
|
- */
|
|
|
- var isInLogin: Boolean = false
|
|
|
- set(value) {
|
|
|
- field = value
|
|
|
- runGate.value = !value // 反向:非登录 → 允许跑
|
|
|
-
|
|
|
- // 触发策略:只有“退出登录”(value=false)才计划一次低优先级检查
|
|
|
- startCheckKeyInfoJob?.cancel()
|
|
|
- if (!value) {
|
|
|
- // 抗抖 + 延迟(降低优先级,不跟业务抢)
|
|
|
- startCheckKeyInfoJob = scope.launch {
|
|
|
- awaitNotInLogin(stableMs = STABLE_MS) // 先稳定
|
|
|
- delay(DELAY_AFTER_EXIT_MS)
|
|
|
- safeCheckKeyInfo()
|
|
|
- }
|
|
|
- } else {
|
|
|
- startCheckKeyInfoJob = null
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @SuppressLint("MissingPermission")
|
|
|
- @CronJob(intervalMillis = 30 * 60_000L, initialDelay = 0, runOnMainThread = false)
|
|
|
- fun checkKeyInfo() {
|
|
|
- scope.launch { safeCheckKeyInfo() }
|
|
|
- }
|
|
|
-
|
|
|
- @SuppressLint("MissingPermission")
|
|
|
- private suspend fun safeCheckKeyInfo() {
|
|
|
- // 不在锁里等闸门,避免大锁长期被占
|
|
|
- awaitNotInLogin()
|
|
|
- runMutex.withLock {
|
|
|
- logger.info("开始检查钥匙信息(非登录态)")
|
|
|
- for (mac in HardwareBusinessManager.getExistsKeyMac()) {
|
|
|
- mac?.let { handleSingleMac(it) } ?: continue
|
|
|
- }
|
|
|
- logger.info("检查钥匙信息结束")
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private suspend fun handleSingleMac(mac: String) {
|
|
|
- // 任意时刻闸门关了就暂停
|
|
|
- awaitNotInLogin()
|
|
|
- waitUntilCanConnect() // 若无可用连接位,轻量等待;闸门关了会提前返回
|
|
|
-
|
|
|
- if (!runGate.value) {
|
|
|
- logger.info("检测到进入登录页,暂停检查;mac=$mac")
|
|
|
- awaitNotInLogin()
|
|
|
- }
|
|
|
-
|
|
|
- awaitBleCheck(mac) // 取信息 + 强制断开(见下)
|
|
|
- }
|
|
|
-
|
|
|
- /** 统一策略:完成或取消都 scheduleDisconnect,确保单连接芯片不被占坑 */
|
|
|
- @SuppressLint("MissingPermission")
|
|
|
- private suspend fun awaitBleCheck(mac: String) {
|
|
|
- withTimeout(CHECK_TIMEOUT_MS) {
|
|
|
- suspendCancellableCoroutine { cont ->
|
|
|
- // 监听闸门:一旦进入登录页(runGate=false),立即取消
|
|
|
- val watcher = scope.launch {
|
|
|
- runGate
|
|
|
- .first { allowed -> !allowed } // 变为不允许时
|
|
|
- if (cont.isActive) cont.cancel(CancellationException("Gate closed"))
|
|
|
- }
|
|
|
-
|
|
|
- cont.invokeOnCancellation {
|
|
|
- try { BleSendDispatcher.scheduleDisconnect(mac) } catch (_: Throwable) {}
|
|
|
- watcher.cancel()
|
|
|
- }
|
|
|
-
|
|
|
- BleSendDispatcher.submit(mac) { ok ->
|
|
|
- // 拿到信息后,无条件断开(不依赖实时 isInLogin,避免竞态)
|
|
|
- try { BleSendDispatcher.scheduleDisconnect(mac) } catch (_: Throwable) {}
|
|
|
- if (cont.isActive) cont.resume(Unit) {}
|
|
|
- watcher.cancel()
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /** 等待“非登录态”且稳定 STABLE_MS,真正挂起,不 busy-wait */
|
|
|
- private suspend fun awaitNotInLogin(stableMs: Long = STABLE_MS) {
|
|
|
- if (runGate.value) return
|
|
|
- logger.info("当前在登录页,挂起等待退出登录…")
|
|
|
- runGate
|
|
|
- .debounce(stableMs) // 抗抖:需稳定一段时间
|
|
|
- .first { it } // 等到允许跑
|
|
|
- logger.info("已退出登录且稳定 ${stableMs}ms,继续检查")
|
|
|
- }
|
|
|
-
|
|
|
- /** 轻量的连接位等待:闸门关闭或超时就让路;指数退避,避免忙等 */
|
|
|
- private suspend fun waitUntilCanConnect(maxWaitMillis: Long = 5_000L) {
|
|
|
- val start = System.currentTimeMillis()
|
|
|
- var delayMs = 100L
|
|
|
- while (!BleSendDispatcher.canConnect()) {
|
|
|
- if (!runGate.value) return // 闸门关了,优先让路
|
|
|
- if (System.currentTimeMillis() - start > maxWaitMillis) break
|
|
|
- delay(delayMs)
|
|
|
- delayMs = (delayMs * 2).coerceAtMost(800L)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /** 建议加:在宿主销毁时调用,避免悬挂 */
|
|
|
- fun close() {
|
|
|
- startCheckKeyInfoJob?.cancel()
|
|
|
- scope.cancel()
|
|
|
- }
|
|
|
-}
|
|
|
+//package com.grkj.ui_base.service
|
|
|
+//
|
|
|
+//import android.annotation.SuppressLint
|
|
|
+//import com.grkj.ui_base.business.HardwareBusinessManager
|
|
|
+//import com.grkj.data.hardware.ble.BleSendDispatcher
|
|
|
+//import com.sik.cronjob.annotations.CronJob
|
|
|
+//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
|
|
|
+//import kotlinx.coroutines.flow.debounce
|
|
|
+//import kotlinx.coroutines.flow.distinctUntilChanged
|
|
|
+//import kotlinx.coroutines.flow.map
|
|
|
+//
|
|
|
+///**
|
|
|
+// * 检查钥匙信息任务
|
|
|
+// *
|
|
|
+// * - 支持在检查过程中“挂起等待 isInLogin=true 后继续”
|
|
|
+// * - 防重入:同一时刻只会有一个检查任务在跑
|
|
|
+// * - 仍保留 CronJob 定时入口;也支持在进入登录页时延迟启动一次
|
|
|
+// */
|
|
|
+//class CheckKeyInfoTask {
|
|
|
+//
|
|
|
+// private val logger: Logger = LoggerFactory.getLogger(this::class.java)
|
|
|
+// private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
|
|
+// private val runMutex = Mutex()
|
|
|
+//
|
|
|
+// /** 闸门:true = 允许跑(即 isInLogin == false) */
|
|
|
+// private val runGate = MutableStateFlow(false)
|
|
|
+//
|
|
|
+// private var startCheckKeyInfoJob: Job? = null
|
|
|
+//
|
|
|
+// /** 参数可按需调整 */
|
|
|
+// private companion object {
|
|
|
+// const val STABLE_MS = 1200L // 退出登录需稳定的时间窗
|
|
|
+// const val DELAY_AFTER_EXIT_MS = 60_000L // 退出登录后再延迟启动(低优先级)
|
|
|
+// const val CHECK_TIMEOUT_MS = 10_000L // 单次 BLE 信息获取的超时时间
|
|
|
+// }
|
|
|
+//
|
|
|
+// /**
|
|
|
+// * 登录态:false = 允许跑;true = 暂停(必须在非登录下运行)
|
|
|
+// */
|
|
|
+// var isInLogin: Boolean = false
|
|
|
+// set(value) {
|
|
|
+// field = value
|
|
|
+// runGate.value = !value // 反向:非登录 → 允许跑
|
|
|
+//
|
|
|
+// // 触发策略:只有“退出登录”(value=false)才计划一次低优先级检查
|
|
|
+// startCheckKeyInfoJob?.cancel()
|
|
|
+// if (!value) {
|
|
|
+// // 抗抖 + 延迟(降低优先级,不跟业务抢)
|
|
|
+// startCheckKeyInfoJob = scope.launch {
|
|
|
+// awaitNotInLogin(stableMs = STABLE_MS) // 先稳定
|
|
|
+// delay(DELAY_AFTER_EXIT_MS)
|
|
|
+// safeCheckKeyInfo()
|
|
|
+// }
|
|
|
+// } else {
|
|
|
+// startCheckKeyInfoJob = null
|
|
|
+// }
|
|
|
+// }
|
|
|
+//
|
|
|
+// @SuppressLint("MissingPermission")
|
|
|
+// @CronJob(intervalMillis = 30 * 60_000L, initialDelay = 0, runOnMainThread = false)
|
|
|
+// fun checkKeyInfo() {
|
|
|
+// scope.launch { safeCheckKeyInfo() }
|
|
|
+// }
|
|
|
+//
|
|
|
+// @SuppressLint("MissingPermission")
|
|
|
+// private suspend fun safeCheckKeyInfo() {
|
|
|
+// // 不在锁里等闸门,避免大锁长期被占
|
|
|
+// awaitNotInLogin()
|
|
|
+// runMutex.withLock {
|
|
|
+// logger.info("开始检查钥匙信息(非登录态)")
|
|
|
+// for (mac in HardwareBusinessManager.getExistsKeyMac()) {
|
|
|
+// mac?.let { handleSingleMac(it) } ?: continue
|
|
|
+// }
|
|
|
+// logger.info("检查钥匙信息结束")
|
|
|
+// }
|
|
|
+// }
|
|
|
+//
|
|
|
+// private suspend fun handleSingleMac(mac: String) {
|
|
|
+// // 任意时刻闸门关了就暂停
|
|
|
+// awaitNotInLogin()
|
|
|
+// waitUntilCanConnect() // 若无可用连接位,轻量等待;闸门关了会提前返回
|
|
|
+//
|
|
|
+// if (!runGate.value) {
|
|
|
+// logger.info("检测到进入登录页,暂停检查;mac=$mac")
|
|
|
+// awaitNotInLogin()
|
|
|
+// }
|
|
|
+//
|
|
|
+// awaitBleCheck(mac) // 取信息 + 强制断开(见下)
|
|
|
+// }
|
|
|
+//
|
|
|
+// /** 统一策略:完成或取消都 scheduleDisconnect,确保单连接芯片不被占坑 */
|
|
|
+// @SuppressLint("MissingPermission")
|
|
|
+// private suspend fun awaitBleCheck(mac: String) {
|
|
|
+// withTimeout(CHECK_TIMEOUT_MS) {
|
|
|
+// suspendCancellableCoroutine { cont ->
|
|
|
+// // 监听闸门:一旦进入登录页(runGate=false),立即取消
|
|
|
+// val watcher = scope.launch {
|
|
|
+// runGate
|
|
|
+// .first { allowed -> !allowed } // 变为不允许时
|
|
|
+// if (cont.isActive) cont.cancel(CancellationException("Gate closed"))
|
|
|
+// }
|
|
|
+//
|
|
|
+// cont.invokeOnCancellation {
|
|
|
+// try { BleSendDispatcher.scheduleDisconnect(mac) } catch (_: Throwable) {}
|
|
|
+// watcher.cancel()
|
|
|
+// }
|
|
|
+//
|
|
|
+// BleSendDispatcher.submit(mac) { ok ->
|
|
|
+// // 拿到信息后,无条件断开(不依赖实时 isInLogin,避免竞态)
|
|
|
+// try { BleSendDispatcher.scheduleDisconnect(mac) } catch (_: Throwable) {}
|
|
|
+// if (cont.isActive) cont.resume(Unit) {}
|
|
|
+// watcher.cancel()
|
|
|
+// }
|
|
|
+// }
|
|
|
+// }
|
|
|
+// }
|
|
|
+//
|
|
|
+// /** 等待“非登录态”且稳定 STABLE_MS,真正挂起,不 busy-wait */
|
|
|
+// private suspend fun awaitNotInLogin(stableMs: Long = STABLE_MS) {
|
|
|
+// if (runGate.value) return
|
|
|
+// logger.info("当前在登录页,挂起等待退出登录…")
|
|
|
+// runGate
|
|
|
+// .debounce(stableMs) // 抗抖:需稳定一段时间
|
|
|
+// .first { it } // 等到允许跑
|
|
|
+// logger.info("已退出登录且稳定 ${stableMs}ms,继续检查")
|
|
|
+// }
|
|
|
+//
|
|
|
+// /** 轻量的连接位等待:闸门关闭或超时就让路;指数退避,避免忙等 */
|
|
|
+// private suspend fun waitUntilCanConnect(maxWaitMillis: Long = 5_000L) {
|
|
|
+// val start = System.currentTimeMillis()
|
|
|
+// var delayMs = 100L
|
|
|
+// while (!BleSendDispatcher.canConnect()) {
|
|
|
+// if (!runGate.value) return // 闸门关了,优先让路
|
|
|
+// if (System.currentTimeMillis() - start > maxWaitMillis) break
|
|
|
+// delay(delayMs)
|
|
|
+// delayMs = (delayMs * 2).coerceAtMost(800L)
|
|
|
+// }
|
|
|
+// }
|
|
|
+//
|
|
|
+// /** 建议加:在宿主销毁时调用,避免悬挂 */
|
|
|
+// fun close() {
|
|
|
+// startCheckKeyInfoJob?.cancel()
|
|
|
+// scope.cancel()
|
|
|
+// }
|
|
|
+//}
|