refactor(activity): 优化 FoodCollectionActivity 性能与稳定性

- 修复 runBlocking 阻塞主线程,改为 lifecycleScope.launch 协程异步执行
- 耗时操作(I/O、图像裁剪、TFLite 推理、ObjectBox 读写)全部切换至 Dispatchers.IO
- 修复 bitmap 内存泄漏:用 try/finally 保证所有退出路径均回收
- 修复 uriToBitmap 返回 null 时 loading 转圈永不消失的 Bug
- 修复 DbException 块、index==-1 分支缺少 hideWaitingDialog() 的 Bug
- 移除重复 ReceiptViewModel,统一使用单例;移除空 initBox() 函数
- 新增 searchJob 取消上一次未完成搜索,修复 clearData() 遗漏取消的 Bug
- 提取 createWaitingDialog() 消除 dialog 创建代码重复
- 修复 clearCoilCache() 使用新实例无法清缓存,改用 context.imageLoader 单例
- upload() 两处强制解包 !! 改为安全调用,避免 NPE 崩溃
- 清理无用注释、废弃代码块、冗余注解及无用 import

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 18:04:21 +08:00
co-authored by Claude Sonnet 4.6
parent 05015a7a18
commit 7563209cf1
@@ -1,14 +1,12 @@
package com.sw.inbound.activity package com.sw.inbound.activity
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Dialog
import android.content.Context import android.content.Context
import android.graphics.Bitmap
import android.net.Uri import android.net.Uri
import android.graphics.Bitmap
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.text.TextUtils
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import com.sw.inbound.base.BaseActivity import com.sw.inbound.base.BaseActivity
import androidx.activity.viewModels import androidx.activity.viewModels
@@ -17,8 +15,8 @@ import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import coil.ImageLoader
import coil.annotation.ExperimentalCoilApi import coil.annotation.ExperimentalCoilApi
import coil.imageLoader
import com.sw.inbound.R import com.sw.inbound.R
import com.sw.inbound.adapter.FoodCollectionAdapter import com.sw.inbound.adapter.FoodCollectionAdapter
import com.sw.inbound.adapter.GoodsSearchAdapter import com.sw.inbound.adapter.GoodsSearchAdapter
@@ -47,21 +45,19 @@ import com.sw.inbound.utils.ext.hideKeyboard
import com.sw.inbound.utils.ext.invisible import com.sw.inbound.utils.ext.invisible
import com.sw.inbound.utils.ext.startActivity import com.sw.inbound.utils.ext.startActivity
import com.sw.inbound.utils.ext.toJsonString import com.sw.inbound.utils.ext.toJsonString
import com.sw.inbound.utils.ext.toObject
import com.sw.inbound.utils.ext.toast import com.sw.inbound.utils.ext.toast
import com.sw.inbound.utils.ext.visible import com.sw.inbound.utils.ext.visible
import com.sw.inbound.viewmodel.ReceiptViewModel import com.sw.inbound.viewmodel.ReceiptViewModel
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.objectbox.Box
import io.objectbox.exception.DbException import io.objectbox.exception.DbException
import io.objectbox.exception.FileCorruptException import io.objectbox.exception.FileCorruptException
import io.objectbox.kotlin.boxFor import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext
import okhttp3.RequestBody import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import timber.log.Timber import timber.log.Timber
import java.io.File
/** /**
* 采集菜品 * 采集菜品
@@ -76,12 +72,11 @@ class FoodCollectionActivity : BaseActivity() {
const val PAGE_SIZE = 30 const val PAGE_SIZE = 30
} }
// private var box: Box<Food>? = null private val collectList: MutableList<FoodCollectionBean> = mutableListOf()
private val collectList: MutableList<FoodCollectionBean> = mutableListOf<FoodCollectionBean>()
private lateinit var binding: ActivityFoodCollectionBinding private lateinit var binding: ActivityFoodCollectionBinding
private lateinit var previewView: PreviewView private lateinit var previewView: PreviewView
val viewModel: ReceiptViewModel by viewModels() private val viewModel: ReceiptViewModel by viewModels()
private val cameraUtils: CameraUtils by lazy { private val cameraUtils: CameraUtils by lazy {
CameraUtils(this) CameraUtils(this)
@@ -90,9 +85,14 @@ class FoodCollectionActivity : BaseActivity() {
FoodCollectionAdapter(collectList).apply { FoodCollectionAdapter(collectList).apply {
setOnItemClickListener { adapter, view, position -> setOnItemClickListener { adapter, view, position ->
val file = collectList[position].imageFile ?: return@setOnItemClickListener val file = collectList[position].imageFile ?: return@setOnItemClickListener
// 磁盘读取和 TFLite 推理均为耗时操作,切换到 IO 线程执行
lifecycleScope.launch(Dispatchers.IO) {
ImageUtil.decodeFromFile(file)?.let { bitmap -> ImageUtil.decodeFromFile(file)?.let { bitmap ->
val imageVector = FoodModule.bitmap2FloatArray(bitmap, false) val imageVector = FoodModule.bitmap2FloatArray(bitmap, false)
Timber.tag(TAG).d("向量:${imageVector.toJsonString()}") Timber.tag(TAG).d("向量:${imageVector.toJsonString()}")
// 向量提取完毕后立即回收,避免内存泄漏
if (bitmap.isRecycled.not()) bitmap.recycle()
}
} }
} }
addOnItemChildClickListener(R.id.ivDelete) { _, _, position -> addOnItemChildClickListener(R.id.ivDelete) { _, _, position ->
@@ -105,8 +105,6 @@ class FoodCollectionActivity : BaseActivity() {
it.uploadSuccess = false it.uploadSuccess = false
} }
collectionAdapter.notifyItemChanged(position) collectionAdapter.notifyItemChanged(position)
// val count = collectList.count { it.imageFile != null && it.uploadSuccess.not() }
// binding.btnUploadImage.text = "待上传图片${count}张"
} }
} }
} }
@@ -128,7 +126,6 @@ class FoodCollectionActivity : BaseActivity() {
binding.llCameraFlag.visible() binding.llCameraFlag.visible()
} }
@SuppressLint("NotifyDataSetChanged")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityFoodCollectionBinding.inflate(layoutInflater) binding = ActivityFoodCollectionBinding.inflate(layoutInflater)
@@ -163,14 +160,6 @@ class FoodCollectionActivity : BaseActivity() {
searchGoods() searchGoods()
} }
} }
// binding.btnUploadImage.setOnClickListener {
// val count = collectList.count { it.imageFile!=null && it.uploadSuccess.not() }
// if (count == 0) {
// toast("已全部上传成功")
// return@setOnClickListener
// }
// uploadAllImage()
// }
binding.btnSave.setOnClickListener { _ -> binding.btnSave.setOnClickListener { _ ->
if (clickIndex == -1) { if (clickIndex == -1) {
toast("请选择物品名称") toast("请选择物品名称")
@@ -181,8 +170,6 @@ class FoodCollectionActivity : BaseActivity() {
toast("请拍摄物品照片") toast("请拍摄物品照片")
return@setOnClickListener return@setOnClickListener
} }
// vectorThread()
// uploadAllImage()
showUploadDialog() showUploadDialog()
} }
binding.btnTakePhoto.clickWithDebounce(800) { binding.btnTakePhoto.clickWithDebounce(800) {
@@ -215,18 +202,6 @@ class FoodCollectionActivity : BaseActivity() {
startActivity<LocalImagePreviewActivity> { } startActivity<LocalImagePreviewActivity> { }
} }
binding.tvTitle.setOnClickListener {
// startActivity<LocalImagePreviewActivity> { }
// val goodsImage = GoodsImage(goodsId = "1993493094888140802", goodsName = "多宝鱼", startTime = 1764120531856L, endTime = 1764121842825L)
// uploadImage(goodsImage)
// val goodsImage = GoodsImage(goodsId = "1986366073649287170", goodsName = "黑鱼(整条鱼)", startTime = 1764122703093L, endTime = 1764123156022L)
// uploadImage(goodsImage)
// val goodsImage = GoodsImage(goodsId = "1986699063868801025", goodsName = "鸡腿肉", startTime = 1764123375841L, endTime = 1764124039162L)
// uploadImage(goodsImage)
}
} }
private fun showUploadDialog() { private fun showUploadDialog() {
@@ -240,11 +215,13 @@ class FoodCollectionActivity : BaseActivity() {
confirmBlock = { confirmBlock = {
upload() upload()
} }
) .show() ).show()
} }
private var pageNo = 1 private var pageNo = 1
private var clickIndex = -1 private var clickIndex = -1
// 保存当前搜索任务,用于在新搜索发起时取消上一次未完成的请求
private var searchJob: Job? = null
private val searchGoodsList: MutableList<SearchGoodsInfo.Record> = mutableListOf() private val searchGoodsList: MutableList<SearchGoodsInfo.Record> = mutableListOf()
private val searchAdapter by lazy { private val searchAdapter by lazy {
@@ -274,7 +251,6 @@ class FoodCollectionActivity : BaseActivity() {
} }
} }
@SuppressLint("NotifyDataSetChanged")
private fun takePhoto() { private fun takePhoto() {
val count = collectList.count { it.imageFile != null } val count = collectList.count { it.imageFile != null }
if (count == MAX_COUNT) { if (count == MAX_COUNT) {
@@ -285,10 +261,12 @@ class FoodCollectionActivity : BaseActivity() {
cameraUtils.takePhoto(cameraCallback) cameraUtils.takePhoto(cameraCallback)
} }
@SuppressLint("NotifyDataSetChanged")
private fun clearData() { private fun clearData() {
val count = collectList.count { it.imageFile != null } val count = collectList.count { it.imageFile != null }
if (count == 0) return if (count == 0) return
// 取消正在进行的搜索,防止清空后回调重新填充数据
searchJob?.cancel()
searchJob = null
clearCoilCache(this) clearCoilCache(this)
collectList.forEach { collectList.forEach {
try { try {
@@ -313,7 +291,6 @@ class FoodCollectionActivity : BaseActivity() {
searchAdapter.notifyDataSetChanged() searchAdapter.notifyDataSetChanged()
loadEmptyView() loadEmptyView()
binding.ivFinish.invisible() binding.ivFinish.invisible()
// binding.btnUploadImage.text = "待上传图片${0}张"
} }
private val cameraCallback: (Uri) -> Unit = { uri -> private val cameraCallback: (Uri) -> Unit = { uri ->
@@ -326,48 +303,73 @@ class FoodCollectionActivity : BaseActivity() {
val index = collectList.indexOfFirst { it.imageFile == null } val index = collectList.indexOfFirst { it.imageFile == null }
if (index == -1) { if (index == -1) {
toast("每次只允许保存${MAX_COUNT}条数据") toast("每次只允许保存${MAX_COUNT}条数据")
showWaitingDialog2("加载中") hideWaitingDialog()
return return
} }
// toast("index=$index") // 将 bitmap/cropBitmap 提到外层,确保任意退出路径都能在 finally 中回收,避免内存泄漏
var bitmap: Bitmap? = null
var cropBitmap: Bitmap? = null
try { try {
ImageUtil.uriToBitmap(this, uri)?.let { bitmap -> // 所有耗时操作(磁盘 I/O、图像裁剪、TFLite 推理)切换到 IO 线程,避免阻塞主线程
val cropBitmap = BitmapCropper.cropCenter( bitmap = withContext(Dispatchers.IO) {
ImageUtil.uriToBitmap(this@FoodCollectionActivity, uri)
}
if (bitmap == null) {
// uriToBitmap 返回 null 时关闭 loading,否则转圈永不消失
hideWaitingDialog()
toast("图片读取失败,请重试")
return
}
cropBitmap = withContext(Dispatchers.IO) {
BitmapCropper.cropCenter(
original = bitmap, original = bitmap,
targetWidth = 1000, targetHeight = 1300, targetWidth = 1000, targetHeight = 1300,
offsetX = 30, offsetY = 100 offsetX = 30, offsetY = 100
) )
initBox() }
val imageVector = try { val imageVector = try {
// TFLite 推理为 CPU 密集型,切换到 IO 线程执行
withContext(Dispatchers.IO) {
FoodModule.bitmap2FloatArray(cropBitmap, false) FoodModule.bitmap2FloatArray(cropBitmap, false)
}
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
hideWaitingDialog() hideWaitingDialog()
toast("操作失败") toast("操作失败")
return@let return
} }
Timber.tag(TAG).d("向量:${imageVector.toJsonString()}") Timber.tag(TAG).d("向量:${imageVector.toJsonString()}")
val foodList = try { val foodList = try {
// 数据库查询切换到 IO 线程
withContext(Dispatchers.IO) {
FoodModule.queryFoodNameScore(imageVector) FoodModule.queryFoodNameScore(imageVector)
}
} catch (e: DbException) { } catch (e: DbException) {
hideWaitingDialog()
try { try {
ObjectBox.init(this) // ObjectBox 重新初始化为磁盘 I/O,切换到 IO 线程
withContext(Dispatchers.IO) {
ObjectBox.init(this@FoodCollectionActivity)
}
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
toast("数据库异常,请稍后重试") toast("数据库异常,请稍后重试")
return@let return
} }
val filterList = val filterList =
foodList.filter { it.score < FoodModule.BAG_RATE && it.name.contains("黑袋子") } foodList.filter { it.score < FoodModule.BAG_RATE && it.name.contains("黑袋子") }
if (filterList.isNotEmpty()) { if (filterList.isNotEmpty()) {
hideWaitingDialog() hideWaitingDialog()
toast("当前物品可能被袋子遮挡,请检查后重试") toast("当前物品可能被袋子遮挡,请检查后重试")
return@let return
} }
val file = BitmapSaver.saveToAppFilesDir( // 图片保存为磁盘 I/O,切换到 IO 线程
cropBitmap, this, "IMG_CROP_${System.currentTimeMillis()}.jpg" val file = withContext(Dispatchers.IO) {
BitmapSaver.saveToAppFilesDir(
cropBitmap, this@FoodCollectionActivity, "IMG_CROP_${System.currentTimeMillis()}.jpg"
) )
}
Timber.d("${this.javaClass.simpleName}-searchFood-裁剪bitmap保存文件路径:${file?.absolutePath}") Timber.d("${this.javaClass.simpleName}-searchFood-裁剪bitmap保存文件路径:${file?.absolutePath}")
collectList[index].let { collectList[index].let {
it.imageVector = imageVector it.imageVector = imageVector
@@ -378,16 +380,7 @@ class FoodCollectionActivity : BaseActivity() {
it.uploadSuccess = false it.uploadSuccess = false
} }
collectionAdapter.notifyItemChanged(index) collectionAdapter.notifyItemChanged(index)
// val count = collectList.count { it.imageFile != null && it.uploadSuccess.not() }
// binding.btnUploadImage.text = "待上传图片${count}张"
if (cropBitmap.isRecycled.not()) {
cropBitmap.recycle()
}
if (bitmap.isRecycled.not()) {
bitmap.recycle()
}
hideWaitingDialog() hideWaitingDialog()
}
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
hideWaitingDialog() hideWaitingDialog()
@@ -397,7 +390,10 @@ class FoodCollectionActivity : BaseActivity() {
e is DbException e is DbException
) { ) {
try { try {
ObjectBox.init(this) // ObjectBox 重新初始化为磁盘 I/O,切换到 IO 线程
withContext(Dispatchers.IO) {
ObjectBox.init(this@FoodCollectionActivity)
}
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
@@ -405,6 +401,10 @@ class FoodCollectionActivity : BaseActivity() {
} else { } else {
toast("程序异常$errMsg") toast("程序异常$errMsg")
} }
} finally {
// 无论成功、失败还是提前返回,均在此回收 bitmap,彻底消除内存泄漏
if (cropBitmap?.isRecycled == false) cropBitmap.recycle()
if (bitmap?.isRecycled == false) bitmap.recycle()
} }
} }
@@ -412,15 +412,13 @@ class FoodCollectionActivity : BaseActivity() {
Loading.show(this) Loading.show(this)
lifecycleScope.launch { lifecycleScope.launch {
collectList collectList
//.filter { it.bitmap != null }
// .filter { it.imageVector != null }
.forEachIndexed { index, it -> .forEachIndexed { index, it ->
//image2VectorTask(it.bitmap!!, index)
it.imageVector?.let { imageVector -> it.imageVector?.let { imageVector ->
image2VectorTask(imageVector = imageVector, position = index) image2VectorTask(imageVector = imageVector, position = index)
} }
} }
runOnUiThread { // lifecycleScope 默认在主线程,直接切换到主线程调度器更新 UI
withContext(Dispatchers.Main) {
window.decorView.postDelayed({ window.decorView.postDelayed({
Loading.dismiss() Loading.dismiss()
toast("保存成功") toast("保存成功")
@@ -430,30 +428,19 @@ class FoodCollectionActivity : BaseActivity() {
} }
private suspend fun image2VectorTask( private suspend fun image2VectorTask(
bitmap: Bitmap? = null, imageVector: FloatArray,
imageVector: FloatArray? = null,
position: Int position: Int
) { ) {
try { try {
initBox()
// val imageVector = try {
// FoodModule.bitmap2FloatArray(bitmap)
// } catch (e: Exception) {
// e.printStackTrace()
// //toast("保存失败")
// return
// }
val goods = searchGoodsList[clickIndex] val goods = searchGoodsList[clickIndex]
val saveName = goods.goodsName + goods.goodsCode val saveName = goods.goodsName + goods.goodsCode
// ObjectBox.boxStore.runInTx { // ObjectBox 写入为磁盘 I/O,切换到 IO 线程避免主线程卡顿
// box?.put(Food(name = saveName, foodIdx = 0, foodVector = imageVector)) withContext(Dispatchers.IO) {
// }
ObjectBox.put(Food(name = saveName, foodIdx = 0, foodVector = imageVector)) ObjectBox.put(Food(name = saveName, foodIdx = 0, foodVector = imageVector))
collectList[position].let {
// it.imageVector = imageVector
it.isFinish = true
} }
runOnUiThread { collectList[position].isFinish = true
// 切换到主线程刷新列表项,与协程调度器保持一致
withContext(Dispatchers.Main) {
collectionAdapter.notifyItemChanged(position) collectionAdapter.notifyItemChanged(position)
} }
} catch (e: Exception) { } catch (e: Exception) {
@@ -461,7 +448,6 @@ class FoodCollectionActivity : BaseActivity() {
} }
} }
@SuppressLint("NotifyDataSetChanged")
private fun searchGoods() { private fun searchGoods() {
val searchName = binding.etGoodsInput.text.toString().trim() val searchName = binding.etGoodsInput.text.toString().trim()
if (searchName.isBlank()) { if (searchName.isBlank()) {
@@ -516,58 +502,37 @@ class FoodCollectionActivity : BaseActivity() {
pageSize: Int = PAGE_SIZE, pageSize: Int = PAGE_SIZE,
callback: (List<SearchGoodsInfo.Record>) -> Unit callback: (List<SearchGoodsInfo.Record>) -> Unit
) { ) {
runBlocking { // 取消上一次未完成的搜索,避免并发竞争导致结果乱序
searchJob?.cancel()
searchJob = lifecycleScope.launch {
val records = viewModel.searchGoodsInfoList2( val records = viewModel.searchGoodsInfoList2(
goodsName = searchName, goodsName = searchName,
pageNo = pageNo, pageNo = pageNo,
pageSize = pageSize pageSize = pageSize
) )
runOnUiThread {
callback(records) callback(records)
} }
} }
}
private fun initBox() {
// if (box == null) {
// box = ObjectBox.boxStore.boxFor(Food::class)
// }
}
// private fun uploadAllImage() {
// Loading.show(this)
// val files = collectList.filter { it.imageFile!=null }.map { it.imageFile!! }
// if (files.isEmpty()) {
// return
// }
// val goods = searchGoodsList[clickIndex]
// for (index in collectList.indices step 5) {
// val end = if(index + 5 < collectList.size - 1) index + 5 else collectList.size - 1
// val subList = collectList.subList(index, end)
// val subFiles = subList.map { it.imageFile }
// viewModel.uploadMultipleImages(goods.id!!, goods.goodsName!!, subFiles) {isSuccess->
// Timber.tag(TAG).d("uploadMultipleImages: ${isSuccess}")
// subList.filter { it.imageFile!=null }.forEach { it.uploadSuccess = isSuccess }
//
// val count = collectList.count { it.imageFile!=null && it.uploadSuccess.not() }
// runOnUiThread {
// binding.btnUploadImage.text = "待上传图片${count}张"
// if (count == 0) {
// Loading.dismiss()
// }
// }
// }
// }
// }
private fun upload() { private fun upload() {
lifecycleScope.launch { lifecycleScope.launch {
val totalFileCount = collectList.count { it.imageFile != null } val totalFileCount = collectList.count { it.imageFile != null }
showWaitingDialog2("图片上传中0/$totalFileCount") showWaitingDialog2("图片上传中0/$totalFileCount")
val goods = searchGoodsList[clickIndex] val goods = searchGoodsList[clickIndex]
val params = HashMap<String, RequestBody>() val params = HashMap<String, RequestBody>()
params["goodsId"] = goods.id!!.toRequestBody() // 使用安全调用避免 id/goodsName 为 null 时崩溃,字段缺失则跳过上传
params["goodsName"] = goods.goodsName!!.toRequestBody() val goodsId = goods.id ?: run {
//params["foodVector"] = foodVector.toRequestBody() hideWaitingDialog()
toast("商品数据异常,请重新选择")
return@launch
}
val goodsName = goods.goodsName ?: run {
hideWaitingDialog()
toast("商品数据异常,请重新选择")
return@launch
}
params["goodsId"] = goodsId.toRequestBody()
params["goodsName"] = goodsName.toRequestBody()
ImageUploader( ImageUploader(
totalList = collectList, totalList = collectList,
uploadImage = { batch -> uploadImage = { batch ->
@@ -575,18 +540,16 @@ class FoodCollectionActivity : BaseActivity() {
viewModel.uploadMultipleImages(files = files, params) viewModel.uploadMultipleImages(files = files, params)
}, },
onProgress = { count, batch -> onProgress = { count, batch ->
runOnUiThread { // lifecycleScope 默认在主线程执行,无需 runOnUiThread
showWaitingDialog2("图片上传中$count/$totalFileCount") showWaitingDialog2("图片上传中$count/$totalFileCount")
batch.forEach { batch.forEach {
it.uploadSuccess = true it.uploadSuccess = true
} }
}
}, },
onError = { onError = {
runOnUiThread { // lifecycleScope 默认在主线程执行,无需 runOnUiThread
hideWaitingDialog() hideWaitingDialog()
toast("上传失败,请稍后重试") toast("上传失败,请稍后重试")
}
}, },
onComplete = { onComplete = {
hideWaitingDialog() hideWaitingDialog()
@@ -596,10 +559,9 @@ class FoodCollectionActivity : BaseActivity() {
} }
} }
private val receiptViewModel: ReceiptViewModel by viewModels()
fun searchGoodsInfoList(goodsName: String, block: (List<SearchGoodsInfo.Record>) -> Unit) { fun searchGoodsInfoList(goodsName: String, block: (List<SearchGoodsInfo.Record>) -> Unit) {
runBlocking { // 使用协程异步执行,避免阻塞主线程
lifecycleScope.launch {
val list = searchGoodsInfoList2(goodsName) val list = searchGoodsInfoList2(goodsName)
block(list) block(list)
} }
@@ -610,25 +572,35 @@ class FoodCollectionActivity : BaseActivity() {
pageNo: Int = 1, pageNo: Int = 1,
pageSize: Int = 10 pageSize: Int = 10
): List<SearchGoodsInfo.Record> { ): List<SearchGoodsInfo.Record> {
return receiptViewModel.searchGoodsInfoList2(goodsName, pageNo, pageSize) // 统一使用类顶部声明的 viewModel,避免重复创建 ViewModel 实例
return viewModel.searchGoodsInfoList2(goodsName, pageNo, pageSize)
} }
private var mDialogWaiting: CustomDialog? = null private var mDialogWaiting: CustomDialog? = null
/** /**
* 显示等待提示框 * 内部方法:创建并显示等待提示框(调用前须确保已在主线程)
* showWaitingDialog 与 showWaitingDialog2 共用此创建逻辑,避免重复代码
*/
private fun createWaitingDialog(tip: String?) {
mDialogWaiting?.dismiss()
mDialogWaiting = null
val view = View.inflate(this, R.layout.dialog_waiting, null)
if (!TextUtils.isEmpty(tip)) {
(view.findViewById<View>(R.id.tvTip) as TextView).text = tip
}
mDialogWaiting = CustomDialog(this, view, R.style.MyDialog).apply {
show()
setCanceledOnTouchOutside(true)
setCancelable(true)
}
}
/**
* 显示等待提示框(每次调用都重新创建对话框)
*/ */
fun showWaitingDialog(tip: String?) { fun showWaitingDialog(tip: String?) {
runOnUiThread { runOnUiThread { createWaitingDialog(tip) }
hideWaitingDialog()
val view = View.inflate(this, R.layout.dialog_waiting, null)
if (!TextUtils.isEmpty(tip)) (view.findViewById<View?>(R.id.tvTip) as TextView).text =
tip
mDialogWaiting = CustomDialog(this, view, R.style.MyDialog)
mDialogWaiting!!.show()
mDialogWaiting!!.setCanceledOnTouchOutside(true)
mDialogWaiting!!.setCancelable(true)
}
} }
/** /**
@@ -641,19 +613,18 @@ class FoodCollectionActivity : BaseActivity() {
} }
} }
/**
* 显示或更新等待提示框
* 若对话框已存在则仅更新文字,否则创建新对话框
*/
fun showWaitingDialog2(tip: String?) { fun showWaitingDialog2(tip: String?) {
runOnUiThread { runOnUiThread {
if (mDialogWaiting == null) { if (mDialogWaiting == null) {
hideWaitingDialog() createWaitingDialog(tip)
val view = View.inflate(this, R.layout.dialog_waiting, null) } else {
mDialogWaiting = CustomDialog(this, view, R.style.MyDialog) // 对话框已显示,仅更新提示文字
mDialogWaiting!!.show() mDialogWaiting?.findViewById<TextView>(R.id.tvTip)?.text = tip
mDialogWaiting!!.setCanceledOnTouchOutside(true)
mDialogWaiting!!.setCancelable(true)
} }
val contentView = mDialogWaiting?.findViewById<ViewGroup>(android.R.id.content)
val tvTip = contentView?.findViewById<TextView>(R.id.tvTip)
tvTip?.text = tip
} }
} }
@@ -662,128 +633,9 @@ class FoodCollectionActivity : BaseActivity() {
*/ */
@OptIn(ExperimentalCoilApi::class) @OptIn(ExperimentalCoilApi::class)
fun clearCoilCache(context: Context) { fun clearCoilCache(context: Context) {
val imageLoader = ImageLoader.Builder(context).build() // 使用 Coil 全局单例,避免每次新建 ImageLoader 实例导致清除无效
// 清除内存缓存 context.imageLoader.memoryCache?.clear()
imageLoader.memoryCache?.clear() context.imageLoader.diskCache?.clear()
// 清除磁盘缓存
imageLoader.diskCache?.clear()
} }
data class RespData(
var code: String? = null,
var data: String? = null,
var msg: String? = null,
)
data class GoodsImage(
var goodsId: String,
var goodsName: String,
var startTime: Long,
var endTime: Long
)
//
// val goodsImageList = mutableListOf<GoodsImage>().apply {
// add(
// GoodsImage(
// goodsId = "1986353555971297282",
// goodsName = "马铃薯[土豆、洋芋]",
// startTime = 1763692477786L,
// endTime = 1763693153825L
// )
// )
// add(GoodsImage(
// goodsId = "1986607476769869825",
// goodsName = "辣椒(青辣椒,螺丝椒)",
// startTime = 1763693182929L,
// endTime = 1763693621664L
// ))
// add(GoodsImage(
// goodsId = "1987880591453859842",
// goodsName = "辣椒(红,小)",
// startTime = 1763693683837L,
// endTime = 1763694154521L
// ))
// add(GoodsImage(
// goodsId = "1986699063868801025",
// goodsName = "鸡腿肉",
// startTime = 1763694221267L,
// endTime = 1763695052924L
// ))
//
// add(GoodsImage(
// goodsId = "1986689570300809217",
// goodsName = "猪腿肉",
// startTime = 1763695212428L,
// endTime = 1763695810578L
// ))
// add(GoodsImage(
// goodsId = "1986673470079029249",
// goodsName = "西兰花[绿菜花]",
// startTime = 1763695924575L,
// endTime = 1763696252669L
// ))
// add(GoodsImage(
// goodsId = "1991473021965070338",
// goodsName = "姜[黄姜]",
// startTime = 1763696258001L,
// endTime = 1763696294831L
// ))
// add(GoodsImage(
// goodsId = "00000",
// goodsName = "其它",
// startTime = 1763696442577L,
// endTime = 1763696990541L
// ))
// }
// private fun uploadImage(goodsImage: GoodsImage) {
// Loading.show(this)
// val cropFile = File(this.cacheDir, "crop")
// val list = mutableListOf<File>()
// val startTime = goodsImage.startTime
// val endTime = goodsImage.endTime
// cropFile.listFiles()?.forEach {
// if (it.isDirectory) {
// return@forEach
// }
// val start = "IMG_CROP_".length
// val end = it.name.indexOf(".jpg")
// val time = it.name.substring(start, end).toLong()
// if (time in startTime..endTime) {
// list.add(it)
// }
// if (list.size < 5) {
// return@forEach
// }
// viewModel.uploadMultipleImages(
// goodsId = goodsImage.goodsId,
// goodsName = goodsImage.goodsName,
// files = list
// ) { isSuccess ->
//// val resp = data?.toObject<RespData>()
//// val isSuccess = resp?.code == "00000"
// binding.ivFinish.run {
// if (isSuccess) visible() else invisible()
// }
// }
// list.clear()
// }
// if (list.isNotEmpty()) {
// viewModel.uploadMultipleImages(
// goodsId = goodsImage.goodsId,
// goodsName = goodsImage.goodsName,
// files = list
// ) { isSuccess ->
//// val resp = data?.toObject<RespData>()
//// val isSuccess = resp?.code == "00000"
// binding.ivFinish.run {
// if (isSuccess) visible() else invisible()
// }
// }
// }
// Loading.dismiss()
// }
} }