Revert "重构 HomeActivity:修复多项稳定性与架构问题"

This reverts commit 85903eb646.
This commit is contained in:
2026-03-31 10:57:46 +08:00
parent a116c4fc6c
commit 3bd60990b9
3 changed files with 71 additions and 154 deletions
@@ -3,11 +3,11 @@ package com.shuwei.intelligent.shelves.activity
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log import android.util.Log
import android.util.SparseIntArray
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@@ -26,7 +26,6 @@ import com.shuwei.intelligent.shelves.model.ShelfBody
import com.shuwei.intelligent.shelves.model.ShelfModel import com.shuwei.intelligent.shelves.model.ShelfModel
import com.shuwei.intelligent.shelves.model.ShelfResult import com.shuwei.intelligent.shelves.model.ShelfResult
import com.shuwei.intelligent.shelves.model.WeightEvent import com.shuwei.intelligent.shelves.model.WeightEvent
import com.shuwei.intelligent.shelves.net.HomeViewModel
import com.shuwei.intelligent.shelves.net.Loading import com.shuwei.intelligent.shelves.net.Loading
import com.shuwei.intelligent.shelves.net.NetViewModel import com.shuwei.intelligent.shelves.net.NetViewModel
import com.shuwei.intelligent.shelves.net.RespData import com.shuwei.intelligent.shelves.net.RespData
@@ -43,7 +42,6 @@ import com.shuwei.intelligent.shelves.utils.ext.toast
import com.shuwei.intelligent.shelves.utils.ext.visible import com.shuwei.intelligent.shelves.utils.ext.visible
import com.shuwei.intelligent.shelves.utils.hexToBinary import com.shuwei.intelligent.shelves.utils.hexToBinary
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
@@ -99,6 +97,16 @@ class HomeActivity : BaseActivity() {
const val TEMPERATURE_CTRL = "${HEADER}0F01040000${FOOTER}" const val TEMPERATURE_CTRL = "${HEADER}0F01040000${FOOTER}"
val list: MutableList<ShelfModel> = mutableListOf()
val weightArray = SparseIntArray()
var shelfIndex = 0
// public fun getRealWeight():Double = weightArray[shelfIndex]?:0.0
// 温度
var showTemperatureC = "0"
// 湿度
var showHumidity = "0"
} }
//401-false1楼餐厅-true //401-false1楼餐厅-true
@@ -107,7 +115,6 @@ class HomeActivity : BaseActivity() {
private lateinit var binding: ActivityHomeBinding private lateinit var binding: ActivityHomeBinding
private val viewModel: NetViewModel by viewModels() private val viewModel: NetViewModel by viewModels()
private val homeViewModel: HomeViewModel by viewModels()
@SuppressLint("NotifyDataSetChanged", "HardwareIds") @SuppressLint("NotifyDataSetChanged", "HardwareIds")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -121,23 +128,13 @@ class HomeActivity : BaseActivity() {
updateLeftStatus("") updateLeftStatus("")
initRecyclerView() initRecyclerView()
// 串口初始化:独立协程,仅执行一次
lifecycleScope.launch { lifecycleScope.launch {
val openState = SerialPortManager.open() val openState = SerialPortManager.open()
log("onCreate: openState=$openState") log("onCreate: openState=$openState")
if (openState) { if (openState) {
// 串口回调在 IO 线程,通过 Channel 无锁传递数据,不再嵌套 launch
SerialPortManager.startReceive { data -> SerialPortManager.startReceive { data ->
serialDataChannel.trySend(data) // 更新UI显示接收数据
}
//SerialPortManager.send(DEVICE_INFO_CMD)
}
}
// 串口数据消费:单一协程串行处理,serialReader 访问不存在并发竞争
lifecycleScope.launch { lifecycleScope.launch {
for (data in serialDataChannel) {
try { try {
receiveSerialPortData(data) receiveSerialPortData(data)
} catch (e: Exception) { } catch (e: Exception) {
@@ -145,10 +142,9 @@ class HomeActivity : BaseActivity() {
} }
} }
} }
//SerialPortManager.send(DEVICE_INFO_CMD)
// ViewModel 状态观察:STARTED 状态下收集,onStop 时自动取消,onStart 时恢复 }
lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.CREATED) {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch { launch {
viewModel.getAccessTokenUiState.collect { state -> viewModel.getAccessTokenUiState.collect { state ->
when (state) { when (state) {
@@ -206,16 +202,16 @@ class HomeActivity : BaseActivity() {
} }
private fun loadTestData() { private fun loadTestData() {
homeViewModel.shelfItems.add(ShelfModel(deviceNo = 1, goodsName = "鸡蛋", deviceId = App.deviceId)) list.add(ShelfModel(deviceNo = 1, goodsName = "鸡蛋", deviceId = App.deviceId))
homeViewModel.shelfItems.add(ShelfModel(deviceNo = 6, goodsName = "", deviceId = App.deviceId)) list.add(ShelfModel(deviceNo = 6, goodsName = "", deviceId = App.deviceId))
homeViewModel.shelfItems.add(ShelfModel(deviceNo = 2, goodsName = "", deviceId = App.deviceId)) list.add(ShelfModel(deviceNo = 2, goodsName = "", deviceId = App.deviceId))
homeViewModel.shelfItems.add(ShelfModel(deviceNo = 7, goodsName = "生姜", deviceId = App.deviceId)) list.add(ShelfModel(deviceNo = 7, goodsName = "生姜", deviceId = App.deviceId))
homeViewModel.shelfItems.add(ShelfModel(deviceNo = 3, goodsName = "黄瓜", deviceId = App.deviceId)) list.add(ShelfModel(deviceNo = 3, goodsName = "黄瓜", deviceId = App.deviceId))
homeViewModel.shelfItems.add(ShelfModel(deviceNo = 8, goodsName = "", deviceId = App.deviceId)) list.add(ShelfModel(deviceNo = 8, goodsName = "", deviceId = App.deviceId))
homeViewModel.shelfItems.add(ShelfModel(deviceNo = 4, goodsName = "", deviceId = App.deviceId)) list.add(ShelfModel(deviceNo = 4, goodsName = "", deviceId = App.deviceId))
homeViewModel.shelfItems.add(ShelfModel(deviceNo = 9, goodsName = "", deviceId = App.deviceId)) list.add(ShelfModel(deviceNo = 9, goodsName = "", deviceId = App.deviceId))
homeViewModel.shelfItems.add(ShelfModel(deviceNo = 5, goodsName = "", deviceId = App.deviceId)) list.add(ShelfModel(deviceNo = 5, goodsName = "", deviceId = App.deviceId))
homeViewModel.shelfItems.add(ShelfModel(deviceNo = 10, goodsName = "", deviceId = App.deviceId)) list.add(ShelfModel(deviceNo = 10, goodsName = "", deviceId = App.deviceId))
shelfAdapter.notifyDataSetChanged() shelfAdapter.notifyDataSetChanged()
} }
@@ -255,12 +251,12 @@ class HomeActivity : BaseActivity() {
// toast("数据错误") // toast("数据错误")
// return // return
// } // }
homeViewModel.shelfItems.clear() list.clear()
val subList01 = tempList.subList(0, tempList.size / 2) val subList01 = tempList.subList(0, tempList.size / 2)
val subList02 = tempList.subList(tempList.size / 2, tempList.size) val subList02 = tempList.subList(tempList.size / 2, tempList.size)
repeat(tempList.size / 2) { index -> repeat(tempList.size / 2) { index ->
homeViewModel.shelfItems.add(subList01[index].also { it.deviceId = App.deviceId }) list.add(subList01[index].also { it.deviceId = App.deviceId })
homeViewModel.shelfItems.add(subList02[index].also { it.deviceId = App.deviceId }) list.add(subList02[index].also { it.deviceId = App.deviceId })
} }
shelfAdapter.notifyDataSetChanged() shelfAdapter.notifyDataSetChanged()
} }
@@ -281,18 +277,7 @@ class HomeActivity : BaseActivity() {
*/ */
private var terminalVersion: String = "" private var terminalVersion: String = ""
/** private val serialReader by lazy { StringBuilder() }
* 串口数据接收通道,无界缓冲。
* IO 线程(串口回调)通过 trySend 写入,单一消费协程串行读取,
* 确保 serialReader 始终在同一协程中访问,规避并发竞争。
*/
private val serialDataChannel = Channel<String>(Channel.UNLIMITED)
/**
* 串口数据拼包缓冲器。
* 仅由 serialDataChannel 的单一消费协程访问,线程安全。
*/
private val serialReader = StringBuilder()
private fun receiveSerialPortData(srcData: String) { private fun receiveSerialPortData(srcData: String) {
log("receiveSerialPortData: $srcData") log("receiveSerialPortData: $srcData")
@@ -429,7 +414,7 @@ class HomeActivity : BaseActivity() {
//D6DADEDB1001015D020000000027001E00020000001E001C00B40000000000010000000100040000001900440003000F00000000000FFFE6000200010002FFD80044000300010005001E0001FFD80056001400280000000000000000008300090000003C00D2000000000000007F003700000000A100000031323136334238423134303233303456322E313100FFFF //D6DADEDB1001015D020000000027001E00020000001E001C00B40000000000010000000100040000001900440003000F00000000000FFFE6000200010002FFD80044000300010005001E0001FFD80056001400280000000000000000008300090000003C00D2000000000000007F003700000000A100000031323136334238423134303233303456322E313100FFFF
runOnUiThread { runOnUiThread {
if (tipDialog == null) { if (tipDialog == null) {
tipDialog = CommonDialog(this).apply { CommonDialog(this).apply {
dialogTitle = "温馨提示" dialogTitle = "温馨提示"
// dialogContent = logMsg // dialogContent = logMsg
confirmBlock = { confirmBlock = {
@@ -528,21 +513,21 @@ class HomeActivity : BaseActivity() {
var realWeight = 0 var realWeight = 0
var firstByteBinary = hexToBinary(weightHex.substring(0, 2)) var firstByteBinary = hexToBinary(weightHex.substring(0, 2))
if (firstByteBinary.startsWith("0")) { if (firstByteBinary.startsWith("0")) {
//正数:按十六进制解析,失败时兜底为 0 //正数
realWeight = weightHex.toIntOrNull(16) ?: 0 realWeight = weightHex.toInt()
} else { } else {
//负数:清除符号位后取绝对值,再取反 //负数
firstByteBinary = "0${firstByteBinary.substring(1)}" firstByteBinary = "0${firstByteBinary.substring(1)}"
val firstByteHex = binaryToHex(firstByteBinary) val firstByteHex = binaryToHex(firstByteBinary)
weightHex = firstByteHex + weightHex.substring(2) weightHex = firstByteHex + weightHex.substring(2)
realWeight = -1 * (weightHex.toIntOrNull(16) ?: 0) realWeight = -1 * weightHex.toInt()
} }
homeViewModel.putWeight(index, realWeight) weightArray.put(index, realWeight)
if (index in 1..10 && homeViewModel.shelfItems.size == 10) { if (index in 1..10 && list.size == 10) {
homeViewModel.shelfItems.firstOrNull { it.deviceNo == index }?.let { list.firstOrNull { it.deviceNo == index }?.let {
it.weight = realWeight.toDouble() it.weight = realWeight.toDouble()
val pos = homeViewModel.shelfItems.indexOf(it) val pos = list.indexOf(it)
shelfAdapter.notifyItemChanged(pos) shelfAdapter.notifyItemChanged(pos)
log("getWeightInfo: deviceNo=${it.deviceNo},realWeight=${realWeight}") log("getWeightInfo: deviceNo=${it.deviceNo},realWeight=${realWeight}")
EventBus.getDefault().post(SendWeightEvent(it.deviceNo, realWeight)) EventBus.getDefault().post(SendWeightEvent(it.deviceNo, realWeight))
@@ -623,44 +608,35 @@ class HomeActivity : BaseActivity() {
// TaskManager.cancelTask() // TaskManager.cancelTask()
saveTaskJob?.cancel() saveTaskJob?.cancel()
overdueTaskJob?.cancel() overdueTaskJob?.cancel()
// 关闭 Channel,消费协程的 for 循环将随之结束
serialDataChannel.close()
// 销毁前关闭 Dialog,防止 Activity 已销毁时窗口仍附着导致泄漏
tipDialog?.dismiss()
tipDialog = null
EventBus.getDefault().unregister(this) EventBus.getDefault().unregister(this)
super.onDestroy() super.onDestroy()
} }
@Suppress("DEPRECATION")
private val shelfAdapter by lazy { private val shelfAdapter by lazy {
ShelfAdapter(homeViewModel.shelfItems).apply { ShelfAdapter(list).apply {
setOnItemClickListener { _, _, position -> setOnItemClickListener { _, _, position ->
homeViewModel.selectedShelfIndex = position shelfIndex = position
lifecycleScope.launch { lifecycleScope.launch {
if (isNeedOpenLock) { if (isNeedOpenLock) {
val openCmd = if (homeViewModel.shelfItems[position].deviceNo in 1..5) val openCmd = if (list[position].deviceNo in 1..5)
LEFT_SHELF_OPEN_CMD LEFT_SHELF_OPEN_CMD
else else
RIGHT_SHELF_OPEN_CMD RIGHT_SHELF_OPEN_CMD
val openState = SerialPortManager.send(openCmd) val openState = SerialPortManager.send(openCmd)
} }
// if (openState.not()) { // if (openState.not()) {
// toast("${homeViewModel.shelfItems[position].goodsName}柜门开启失败") // toast("${list[position].goodsName}柜门开启失败")
// return@launch // return@launch
// } // }
//SerialPortManager.send(START_CMD) //SerialPortManager.send(START_CMD)
launch(Intent(this@HomeActivity, ShelfActivity::class.java).also { launch(Intent(this@HomeActivity, ShelfActivity::class.java).also {
it.putExtra(ShelfActivity.SHELF_MODEL, homeViewModel.shelfItems[position]) it.putExtra(ShelfActivity.SHELF_MODEL, list[position])
}) { }) {
it?.run { it?.run {
// API 33 起支持带类型参数的重载,低版本回退旧 API + as? 安全转换 val model =
val model = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { getSerializableExtra(ShelfActivity.SHELF_MODEL) as ShelfModel
getSerializableExtra(ShelfActivity.SHELF_MODEL, ShelfModel::class.java) list[position].apply {
} else {
@Suppress("DEPRECATION")
getSerializableExtra(ShelfActivity.SHELF_MODEL) as? ShelfModel
} ?: return@run
homeViewModel.shelfItems[position].apply {
goodsId = model.goodsId goodsId = model.goodsId
goodsName = model.goodsName goodsName = model.goodsName
weight = model.weight weight = model.weight
@@ -672,12 +648,12 @@ class HomeActivity : BaseActivity() {
try { try {
val submitList = homeViewModel.shelfItems.sortedBy { item -> item.deviceNo } val submitList = list.sortedBy { item -> item.deviceNo }
val body = ShelfBody().also { body -> val body = ShelfBody().also { body ->
body.deviceId = App.deviceId ?: "" body.deviceId = App.deviceId ?: ""
body.canteenId = App.canteenId body.canteenId = App.canteenId
body.temperature = homeViewModel.temperatureC body.temperature = showTemperatureC
body.humidity = homeViewModel.humidity body.humidity = showHumidity
body.goodsList = submitList body.goodsList = submitList
} }
Log.d("mzf", "performSync: body:${body.toJsonString()}") Log.d("mzf", "performSync: body:${body.toJsonString()}")
@@ -738,7 +714,7 @@ class HomeActivity : BaseActivity() {
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
private fun loadEmptyView() { private fun loadEmptyView() {
homeViewModel.shelfItems.clear() list.clear()
shelfAdapter.notifyDataSetChanged() shelfAdapter.notifyDataSetChanged()
binding.include?.let { binding.include?.let {
@@ -759,7 +735,7 @@ class HomeActivity : BaseActivity() {
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
public fun clearEmptyShelf(event: ClearShelfEvent) { public fun clearEmptyShelf(event: ClearShelfEvent) {
val item = homeViewModel.shelfItems.firstOrNull { it.deviceNo == event.shelfNo } val item = list.firstOrNull { it.deviceNo == event.shelfNo }
item?.apply { item?.apply {
goodsId = "" goodsId = ""
goodsName = null goodsName = null
@@ -768,7 +744,7 @@ class HomeActivity : BaseActivity() {
createTime = null createTime = null
overdueDay = null overdueDay = null
} }
shelfAdapter.notifyItemChanged(homeViewModel.shelfItems.indexOf(item)) shelfAdapter.notifyItemChanged(list.indexOf(item))
} }
private val taskExecutor by lazy { IntervalExecutor() } private val taskExecutor by lazy { IntervalExecutor() }
@@ -797,14 +773,10 @@ class HomeActivity : BaseActivity() {
private fun updateOverdueState() { private fun updateOverdueState() {
viewModel.updateOverdueState { state, items -> viewModel.updateOverdueState { state, items ->
if (items.isNullOrEmpty()) return@updateOverdueState if (items.isNullOrEmpty()) return@updateOverdueState
homeViewModel.shelfItems.forEachIndexed { index, model -> list.forEachIndexed { index, model ->
// 按 deviceNo 精确匹配接口数据,避免: //接口过期状态变化后更新状态
// 1. 两端列表大小不一致时的 IndexOutOfBoundsException if (items[index].overdue != model.overdue) {
// 2. 本地列表交叉排列与接口顺序不一致导致的状态串位 model.overdue = items[index].overdue
val serverModel = items.find { it.deviceNo == model.deviceNo }
?: return@forEachIndexed
if (serverModel.overdue != model.overdue) {
model.overdue = serverModel.overdue
shelfAdapter.notifyItemChanged(index) shelfAdapter.notifyItemChanged(index)
log("更新编号为${model.deviceNo}物品:${model.goodsName}的过期状态:${model.overdue}") log("更新编号为${model.deviceNo}物品:${model.goodsName}的过期状态:${model.overdue}")
} }
@@ -813,18 +785,22 @@ class HomeActivity : BaseActivity() {
} }
private fun saveShelfGoodsList() { private fun saveShelfGoodsList() {
log("performSync: list:${homeViewModel.shelfItems.toJsonString()}") log("performSync: list:${list.toJsonString()}")
val submitList = homeViewModel.shelfItems.sortedBy { it.deviceNo } val submitList = list.sortedBy { it.deviceNo }
submitList.forEach { submitList.forEach {
if (it.goodsId.isNullOrBlank()) { if (it.goodsId.isNullOrBlank()) {
it.weight = it.weightBak it.weight = it.weightBak
} }
} }
// submitList.forEach {
// it.temperature = HomeActivity.showTemperatureC
// it.humidity = HomeActivity.showHumidity
// }
val body = ShelfBody().also { val body = ShelfBody().also {
it.deviceId = App.deviceId it.deviceId = App.deviceId
it.canteenId = App.canteenId it.canteenId = App.canteenId
it.temperature = homeViewModel.temperatureC it.temperature = showTemperatureC
it.humidity = homeViewModel.humidity it.humidity = showHumidity
it.goodsList = submitList it.goodsList = submitList
} }
log("performSync: body:${body.toJsonString()}") log("performSync: body:${body.toJsonString()}")
@@ -1,59 +0,0 @@
package com.shuwei.intelligent.shelves.net
import androidx.lifecycle.ViewModel
import com.shuwei.intelligent.shelves.model.ShelfModel
/**
* HomeActivity 专用 ViewModel。
*
* 职责:管理原本散落在 HomeActivity.companion object 中的 UI 状态数据,
* 避免静态共享状态导致的生命周期污染与状态残留问题。
*
* 包含:货架列表、重量数据、选中索引、温湿度。
*/
class HomeViewModel : ViewModel() {
/**
* 货架列表。
* Adapter 持有同一个列表引用,修改后需调用 notifyDataSetChanged / notifyItemChanged 触发刷新。
*/
val shelfItems: MutableList<ShelfModel> = mutableListOf()
/**
* 重量数据缓存。
* keydeviceNo(货架编号),value:重量(克)。
* 替代原 SparseIntArray,使用更符合 Kotlin 习惯的 MutableMap。
*/
private val weightMap: MutableMap<Int, Int> = mutableMapOf()
/**
* 当前选中货架的列表索引(点击 item 时更新)。
*/
var selectedShelfIndex: Int = 0
/**
* 当前摄氏温度,由串口心跳数据更新,字符串格式(如 "24.5")。
*/
var temperatureC: String = "0"
/**
* 当前湿度,由串口心跳数据更新,字符串格式(如 "60")。
*/
var humidity: String = "0"
/**
* 更新指定货架编号的重量。
* @param deviceNo 货架编号
* @param weight 重量(克)
*/
fun putWeight(deviceNo: Int, weight: Int) {
weightMap[deviceNo] = weight
}
/**
* 获取指定货架编号的重量。
* @param deviceNo 货架编号
* @return 重量(克),若无记录返回 0
*/
fun getWeight(deviceNo: Int): Int = weightMap[deviceNo] ?: 0
}
+1 -1
View File
@@ -2,7 +2,7 @@
#agp = "8.11.1" #agp = "8.11.1"
agp = "8.4.0" agp = "8.4.0"
kotlin = "2.0.21" kotlin = "2.0.21"
coreKtx = "1.12.0" coreKtx = "1.10.1"
junit = "4.13.2" junit = "4.13.2"
junitVersion = "1.1.5" junitVersion = "1.1.5"
espressoCore = "3.5.1" espressoCore = "3.5.1"