fix: 修根因——EventBus加buffer防事件丢失+恢复正确架构
根因:SharedFlow(replay=0,extraBufferCapacity=0)导致emit挂起, 多个collector竞争时事件丢失。之前的补丁越改越乱。 修复: 1. EventBus: extraBufferCapacity=64,emit不再阻塞 2. 恢复正确架构: - MainActivity: 监听MQTT type=1→NotificationManager→横幅 - NotificationManager: 处理后emit NewTaskArrived - HomeFragment: 监听NewTaskArrived→红点+统计 3. StatusBarView: 电池位置恢复原位,默认电量-1,主动查询系统电量 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,9 +7,16 @@ import android.view.View
|
|||||||
import androidx.activity.OnBackPressedCallback
|
import androidx.activity.OnBackPressedCallback
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import com.xiaoqu.watch.databinding.ActivityMainBinding
|
import com.xiaoqu.watch.databinding.ActivityMainBinding
|
||||||
|
import com.xiaoqu.watch.event.AppEvent
|
||||||
|
import com.xiaoqu.watch.event.EventBus
|
||||||
|
import com.xiaoqu.watch.service.manager.NotificationManager
|
||||||
import com.xiaoqu.watch.service.manager.SystemStateMonitor
|
import com.xiaoqu.watch.service.manager.SystemStateMonitor
|
||||||
import com.xiaoqu.watch.ui.widget.NotificationBannerView
|
import com.xiaoqu.watch.ui.widget.NotificationBannerView
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
@@ -25,8 +32,10 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
/** 系统状态监听器(电量、蓝牙状态) */
|
/** 系统状态监听器(电量、蓝牙状态) */
|
||||||
@Inject lateinit var systemStateMonitor: SystemStateMonitor
|
@Inject lateinit var systemStateMonitor: SystemStateMonitor
|
||||||
/** 通知横幅(HomeFragment 需访问) */
|
@Inject lateinit var notificationManager: NotificationManager
|
||||||
|
@Inject lateinit var eventBus: EventBus
|
||||||
lateinit var notificationBanner: NotificationBannerView
|
lateinit var notificationBanner: NotificationBannerView
|
||||||
|
private val activityScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -53,6 +62,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
// 初始化通知横幅
|
// 初始化通知横幅
|
||||||
notificationBanner = binding.notificationBanner
|
notificationBanner = binding.notificationBanner
|
||||||
|
|
||||||
|
// 监听 MQTT type=1 → 处理通知 + 显示横幅
|
||||||
|
observeMqttMessages()
|
||||||
|
|
||||||
Timber.d("MainActivity created")
|
Timber.d("MainActivity created")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,11 +74,21 @@ class MainActivity : AppCompatActivity() {
|
|||||||
notificationBanner.destroy()
|
notificationBanner.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== 通知横幅(由 HomeFragment 调用显示) =====
|
// ===== MQTT 新任务处理 =====
|
||||||
|
|
||||||
/** 显示通知横幅(供 HomeFragment 调用) */
|
/** 监听 MQTT type=1 → 通知管理器处理 → 横幅 + NewTaskArrived 事件 */
|
||||||
fun showNotificationBanner(count: Int) {
|
private fun observeMqttMessages() {
|
||||||
notificationBanner.show(count)
|
activityScope.launch {
|
||||||
|
eventBus.events.collect { event ->
|
||||||
|
if (event is AppEvent.MqttMessageReceived && event.type == 1) {
|
||||||
|
notificationManager.onNewTaskMessage(event.rawJson)
|
||||||
|
val count = notificationManager.pendingCount
|
||||||
|
if (count > 0) {
|
||||||
|
notificationBanner.show(count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== 下拉手势检测(Activity 级别,不可能被子 View 拦截) =====
|
// ===== 下拉手势检测(Activity 级别,不可能被子 View 拦截) =====
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import javax.inject.Singleton
|
|||||||
@Singleton
|
@Singleton
|
||||||
class EventBus @Inject constructor() {
|
class EventBus @Inject constructor() {
|
||||||
|
|
||||||
private val _events = MutableSharedFlow<AppEvent>(replay = 0)
|
private val _events = MutableSharedFlow<AppEvent>(replay = 0, extraBufferCapacity = 64)
|
||||||
val events: SharedFlow<AppEvent> = _events.asSharedFlow()
|
val events: SharedFlow<AppEvent> = _events.asSharedFlow()
|
||||||
|
|
||||||
suspend fun emit(event: AppEvent) {
|
suspend fun emit(event: AppEvent) {
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ import javax.inject.Singleton
|
|||||||
@Singleton
|
@Singleton
|
||||||
class NotificationManager @Inject constructor(
|
class NotificationManager @Inject constructor(
|
||||||
private val vibrationController: VibrationController,
|
private val vibrationController: VibrationController,
|
||||||
private val screenController: ScreenController
|
private val screenController: ScreenController,
|
||||||
|
private val eventBus: com.xiaoqu.watch.event.EventBus
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
/** 去抖间隔(毫秒) */
|
/** 去抖间隔(毫秒) */
|
||||||
@@ -104,8 +105,12 @@ class NotificationManager @Inject constructor(
|
|||||||
for (json in jsons) {
|
for (json in jsons) {
|
||||||
processMessageSilent(json) // 只加 ID,不重复震动
|
processMessageSilent(json) // 只加 ID,不重复震动
|
||||||
}
|
}
|
||||||
// 合并后通知 UI 更新数字
|
// 合并后通知 UI
|
||||||
onPendingCountChanged?.invoke(pendingCount)
|
scope.launch {
|
||||||
|
eventBus.emit(com.xiaoqu.watch.event.AppEvent.NewTaskArrived(
|
||||||
|
_pendingTaskIds.toList(), _pendingTaskIds.size
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,7 +133,13 @@ class NotificationManager @Inject constructor(
|
|||||||
vibrationController.executePattern(pattern)
|
vibrationController.executePattern(pattern)
|
||||||
}
|
}
|
||||||
screenController.turnOn()
|
screenController.turnOn()
|
||||||
// UI 更新(横幅+红点)由调用方处理,不再通过 EventBus
|
|
||||||
|
// 通知 HomeFragment 更新红点
|
||||||
|
scope.launch {
|
||||||
|
eventBus.emit(com.xiaoqu.watch.event.AppEvent.NewTaskArrived(
|
||||||
|
_pendingTaskIds.toList(), _pendingTaskIds.size
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 静默处理(只加 ID,不震动不亮屏,用于合并暂存消息) */
|
/** 静默处理(只加 ID,不震动不亮屏,用于合并暂存消息) */
|
||||||
|
|||||||
@@ -122,6 +122,9 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||||||
// 启动时钟定时器
|
// 启动时钟定时器
|
||||||
startClockUpdater()
|
startClockUpdater()
|
||||||
|
|
||||||
|
// 主动获取当前电量(ACTION_BATTERY_CHANGED 是粘性广播,但 EventBus 可能丢失首次事件)
|
||||||
|
initBatteryStatus()
|
||||||
|
|
||||||
// 加载任务统计数据
|
// 加载任务统计数据
|
||||||
fetchStatistics()
|
fetchStatistics()
|
||||||
|
|
||||||
@@ -131,11 +134,6 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||||||
// 监听打卡状态
|
// 监听打卡状态
|
||||||
observePunchState()
|
observePunchState()
|
||||||
|
|
||||||
// 去抖合并后更新横幅数字
|
|
||||||
notificationManager.onPendingCountChanged = { count ->
|
|
||||||
(activity as? com.xiaoqu.watch.app.MainActivity)?.showNotificationBanner(count)
|
|
||||||
fetchStatistics(checkDots = true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
@@ -187,7 +185,6 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||||||
it.onBackKeyPressed = null
|
it.onBackKeyPressed = null
|
||||||
it.notificationBanner.onClick = null
|
it.notificationBanner.onClick = null
|
||||||
}
|
}
|
||||||
notificationManager.onPendingCountChanged = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== 打卡面板 =====
|
// ===== 打卡面板 =====
|
||||||
@@ -338,6 +335,22 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 主动获取当前电量(不依赖 EventBus 广播) */
|
||||||
|
private fun initBatteryStatus() {
|
||||||
|
val batteryIntent = requireContext().registerReceiver(
|
||||||
|
null, android.content.IntentFilter(android.content.Intent.ACTION_BATTERY_CHANGED)
|
||||||
|
)
|
||||||
|
if (batteryIntent != null) {
|
||||||
|
val level = batteryIntent.getIntExtra(android.os.BatteryManager.EXTRA_LEVEL, 0)
|
||||||
|
val scale = batteryIntent.getIntExtra(android.os.BatteryManager.EXTRA_SCALE, 100)
|
||||||
|
val percent = (level * 100) / scale
|
||||||
|
val status = batteryIntent.getIntExtra(android.os.BatteryManager.EXTRA_STATUS, -1)
|
||||||
|
val isCharging = status == android.os.BatteryManager.BATTERY_STATUS_CHARGING
|
||||||
|
|| status == android.os.BatteryManager.BATTERY_STATUS_FULL
|
||||||
|
statusBar.updateBattery(percent, isCharging)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 初始化主页数据 */
|
/** 初始化主页数据 */
|
||||||
private fun initMainPage() {
|
private fun initMainPage() {
|
||||||
updateClock()
|
updateClock()
|
||||||
@@ -491,28 +504,19 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||||||
is AppEvent.BluetoothStateChanged -> {
|
is AppEvent.BluetoothStateChanged -> {
|
||||||
statusBar.updateBluetooth(event.isOn)
|
statusBar.updateBluetooth(event.isOn)
|
||||||
}
|
}
|
||||||
|
// 新任务到达(由 MainActivity→NotificationManager 处理后发出)
|
||||||
|
is AppEvent.NewTaskArrived -> {
|
||||||
|
Timber.d("首页: 新任务到达 (${event.count} 条)")
|
||||||
|
fetchStatistics(checkDots = true)
|
||||||
|
setupBannerClick()
|
||||||
|
}
|
||||||
// MQTT 消息
|
// MQTT 消息
|
||||||
is AppEvent.MqttMessageReceived -> {
|
is AppEvent.MqttMessageReceived -> {
|
||||||
when (event.type) {
|
when (event.type) {
|
||||||
0 -> {
|
0 -> {
|
||||||
// 日常动态 → 刷新统计
|
|
||||||
Timber.d("首页: 收到日常动态")
|
Timber.d("首页: 收到日常动态")
|
||||||
fetchStatistics()
|
fetchStatistics()
|
||||||
}
|
}
|
||||||
1 -> {
|
|
||||||
// 新任务 → 直接处理(不走中间事件,避免 SharedFlow 竞争)
|
|
||||||
Timber.d("首页: 收到新任务通知")
|
|
||||||
notificationManager.onNewTaskMessage(event.rawJson)
|
|
||||||
// 显示横幅
|
|
||||||
val count = notificationManager.pendingCount
|
|
||||||
if (count > 0) {
|
|
||||||
(activity as? com.xiaoqu.watch.app.MainActivity)
|
|
||||||
?.showNotificationBanner(count)
|
|
||||||
}
|
|
||||||
// 刷新统计 + 红点
|
|
||||||
fetchStatistics(checkDots = true)
|
|
||||||
setupBannerClick()
|
|
||||||
}
|
|
||||||
3 -> {
|
3 -> {
|
||||||
// 解绑 → 停止蓝牙 → 清除数据 → 跳绑定页
|
// 解绑 → 停止蓝牙 → 清除数据 → 跳绑定页
|
||||||
Timber.d("首页: 收到解绑消息")
|
Timber.d("首页: 收到解绑消息")
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class StatusBarView @JvmOverloads constructor(
|
|||||||
// ===== 状态数据 =====
|
// ===== 状态数据 =====
|
||||||
private var bluetoothOn = true
|
private var bluetoothOn = true
|
||||||
private var signalLevel = 4 // 0-4
|
private var signalLevel = 4 // 0-4
|
||||||
private var batteryLevel = 75 // 0-100
|
private var batteryLevel = -1 // -1=未获取,0-100=实际电量
|
||||||
private var isCharging = false
|
private var isCharging = false
|
||||||
|
|
||||||
// ===== 画笔 =====
|
// ===== 画笔 =====
|
||||||
@@ -47,9 +47,10 @@ class StatusBarView @JvmOverloads constructor(
|
|||||||
drawBluetoothDot(canvas, 8f, centerY)
|
drawBluetoothDot(canvas, 8f, centerY)
|
||||||
drawSignalBars(canvas, 22f, centerY)
|
drawSignalBars(canvas, 22f, centerY)
|
||||||
|
|
||||||
// ===== 右侧:电量数值 + 电池图标 =====
|
// ===== 右侧:电池图标(固定位置)+ 电量百分比(电池左侧) =====
|
||||||
val batteryTextWidth = drawBatteryText(canvas, width.toFloat(), centerY)
|
val batteryStartX = width - 48f
|
||||||
drawBattery(canvas, width - batteryTextWidth - 52f, centerY)
|
drawBattery(canvas, batteryStartX, centerY)
|
||||||
|
drawBatteryText(canvas, batteryStartX - 4f, centerY)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 绘制蓝牙状态圆点 */
|
/** 绘制蓝牙状态圆点 */
|
||||||
@@ -78,19 +79,18 @@ class StatusBarView @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 绘制电量百分比文字(右对齐,返回文字宽度) */
|
/** 绘制电量百分比文字(电池图标左侧) */
|
||||||
private fun drawBatteryText(canvas: Canvas, rightEdge: Float, centerY: Float): Float {
|
private fun drawBatteryText(canvas: Canvas, rightEdge: Float, centerY: Float) {
|
||||||
|
if (batteryLevel < 0) return // 未获取时不显示
|
||||||
val text = "${batteryLevel}%"
|
val text = "${batteryLevel}%"
|
||||||
paint.style = Paint.Style.FILL
|
paint.style = Paint.Style.FILL
|
||||||
paint.color = 0x99FFFFFF.toInt() // 白色 60% 透明度
|
paint.color = 0x99FFFFFF.toInt() // 白色 60% 透明度
|
||||||
paint.textSize = 16f
|
paint.textSize = 15f
|
||||||
paint.textAlign = Paint.Align.RIGHT
|
paint.textAlign = Paint.Align.RIGHT
|
||||||
// 垂直居中:baseLine = centerY + textHeight/2 - descent
|
|
||||||
val metrics = paint.fontMetrics
|
val metrics = paint.fontMetrics
|
||||||
val baselineY = centerY - (metrics.ascent + metrics.descent) / 2
|
val baselineY = centerY - (metrics.ascent + metrics.descent) / 2
|
||||||
canvas.drawText(text, rightEdge - 2f, baselineY, paint)
|
canvas.drawText(text, rightEdge, baselineY, paint)
|
||||||
paint.textAlign = Paint.Align.LEFT // 恢复默认
|
paint.textAlign = Paint.Align.LEFT
|
||||||
return paint.measureText(text) + 4f
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 绘制电池图标(壳 + 填充条 + 凸起 + 充电闪电) */
|
/** 绘制电池图标(壳 + 填充条 + 凸起 + 充电闪电) */
|
||||||
@@ -117,7 +117,7 @@ class StatusBarView @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
val fillPadding = 2.5f
|
val fillPadding = 2.5f
|
||||||
val fillMaxW = shellW - fillPadding * 2
|
val fillMaxW = shellW - fillPadding * 2
|
||||||
val fillW = fillMaxW * batteryLevel / 100f
|
val fillW = fillMaxW * batteryLevel.coerceAtLeast(0) / 100f
|
||||||
val fillRect = RectF(
|
val fillRect = RectF(
|
||||||
startX + fillPadding,
|
startX + fillPadding,
|
||||||
shellTop + fillPadding,
|
shellTop + fillPadding,
|
||||||
@@ -126,17 +126,7 @@ class StatusBarView @JvmOverloads constructor(
|
|||||||
)
|
)
|
||||||
canvas.drawRoundRect(fillRect, 2f, 2f, paint)
|
canvas.drawRoundRect(fillRect, 2f, 2f, paint)
|
||||||
|
|
||||||
// 充电闪电标识(电池壳内居中)
|
// 充电状态通过填充条颜色区分(绿色=充电中,已在上方 when 处理)
|
||||||
if (isCharging) {
|
|
||||||
paint.color = colorWhite
|
|
||||||
paint.style = Paint.Style.FILL
|
|
||||||
paint.textSize = 11f
|
|
||||||
paint.textAlign = Paint.Align.CENTER
|
|
||||||
val metrics = paint.fontMetrics
|
|
||||||
val baselineY = centerY - (metrics.ascent + metrics.descent) / 2
|
|
||||||
canvas.drawText("⚡", startX + shellW / 2, baselineY, paint)
|
|
||||||
paint.textAlign = Paint.Align.LEFT
|
|
||||||
}
|
|
||||||
|
|
||||||
// 电池凸起(右侧小矩形)
|
// 电池凸起(右侧小矩形)
|
||||||
paint.color = colorBorder
|
paint.color = colorBorder
|
||||||
|
|||||||
Reference in New Issue
Block a user