diff --git a/app/src/main/java/com/sw/inbound/activity/FoodCollectionActivity.kt b/app/src/main/java/com/sw/inbound/activity/FoodCollectionActivity.kt index a1a89ea..55219ab 100644 --- a/app/src/main/java/com/sw/inbound/activity/FoodCollectionActivity.kt +++ b/app/src/main/java/com/sw/inbound/activity/FoodCollectionActivity.kt @@ -371,13 +371,9 @@ class FoodCollectionActivity : ComponentActivity() { // } val goods = searchGoodsList[clickIndex] val saveName = goods.goodsName + goods.goodsCode - box?.put( - Food( - name = saveName, - foodIdx = 0, - foodVector = imageVector - ) - ) + ObjectBox.boxStore.runInTx { + box?.put(Food(name = saveName, foodIdx = 0, foodVector = imageVector)) + } collectList[position].let { // it.imageVector = imageVector it.isFinish = true diff --git a/app/src/main/java/com/sw/inbound/dialog/CollectSearchDialog.kt b/app/src/main/java/com/sw/inbound/dialog/CollectSearchDialog.kt index 83c2f78..74bfb14 100644 --- a/app/src/main/java/com/sw/inbound/dialog/CollectSearchDialog.kt +++ b/app/src/main/java/com/sw/inbound/dialog/CollectSearchDialog.kt @@ -158,7 +158,9 @@ class CollectSearchDialog( } val name = list[position].goodsName val filterIdList = box?.all?.filter { it.name == name }?.map { it.id } - box?.removeByIds(filterIdList) + ObjectBox.boxStore.runInTx { + box?.removeByIds(filterIdList) + } activity?.runOnUiThread { Loading.dismiss() //list.remove(list[position]) @@ -209,24 +211,24 @@ class CollectSearchDialog( binding.root.hideKeyboard() } - fun test() { - val goodsName = "多宝鱼,黑鱼(整条鱼),鸡腿肉" - activity?.searchGoodsInfoList(goodsName = goodsName) { items-> - items.forEach { goods -> - box?.all?.filter { - it.name?.contains(goods.goodsName?:"-") == true - }?.forEach { - activity?.runOnUiThread { - activity?.toast(goods.goodsName) - } - it.name = goods.goodsName + goods.goodsCode - box?.put(it) - } - } -// activity?.runOnUiThread { -// activity?.toast("已保存") +// fun test() { +// val goodsName = "多宝鱼,黑鱼(整条鱼),鸡腿肉" +// activity?.searchGoodsInfoList(goodsName = goodsName) { items-> +// items.forEach { goods -> +// box?.all?.filter { +// it.name?.contains(goods.goodsName?:"-") == true +// }?.forEach { +// activity?.runOnUiThread { +// activity?.toast(goods.goodsName) +// } +// it.name = goods.goodsName + goods.goodsCode +// box?.put(it) +// } // } - } - } +//// activity?.runOnUiThread { +//// activity?.toast("已保存") +//// } +// } +// } } \ No newline at end of file diff --git a/app/src/main/java/com/sw/inbound/dialog/GoodsStoreDialog.kt b/app/src/main/java/com/sw/inbound/dialog/GoodsStoreDialog.kt index e7f09c0..b290bef 100644 --- a/app/src/main/java/com/sw/inbound/dialog/GoodsStoreDialog.kt +++ b/app/src/main/java/com/sw/inbound/dialog/GoodsStoreDialog.kt @@ -454,11 +454,11 @@ class GoodsStoreDialog( Thread { try { val imageVector = FoodModule.bitmap2FloatArray(bitmap) - box?.put( - Food( - name = goods.goodsName + goods.goodsCode, foodIdx = 0, foodVector = imageVector - ) - ) + if (imageVector != null) { + ObjectBox.boxStore.runInTx { + box?.put(Food(name = goods.goodsName + goods.goodsCode, foodIdx = 0, foodVector = imageVector)) + } + } } catch (e: Exception) { e.printStackTrace() } diff --git a/app/src/main/java/com/sw/inbound/objbox/FoodModule.kt b/app/src/main/java/com/sw/inbound/objbox/FoodModule.kt index e2d4df4..76c6ef2 100644 --- a/app/src/main/java/com/sw/inbound/objbox/FoodModule.kt +++ b/app/src/main/java/com/sw/inbound/objbox/FoodModule.kt @@ -3,7 +3,8 @@ package com.sw.inbound.objbox import android.content.Context import android.graphics.Bitmap import android.net.Uri -import android.util.SparseLongArray +import android.renderscript.Element.DataType +import androidx.core.graphics.scale import com.google.gson.Gson import com.google.gson.reflect.TypeToken import com.sw.inbound.MyApp @@ -22,6 +23,7 @@ import java.io.FileOutputStream import java.io.IOException import java.io.InputStream + object FoodModule { private lateinit var module_mobile: Module @@ -31,6 +33,9 @@ object FoodModule { private lateinit var classInfo: FoodClassInfo private val NO_MEAN_RGB = floatArrayOf(0.0f, 0.0f, 0.0f) private val NO_STD_RGB = floatArrayOf(1.0f, 1.0f, 1.0f) + // 1. 定义你的模型固定输入尺寸 (根据你的tflite模型修改,比如224x224) + private const val MODEL_INPUT_WIDTH = 300 + private const val MODEL_INPUT_HEIGHT = 300 const val DEFAULT_FOOD_INDEX = -1 const val BAG_RATE = 0.05 @@ -48,49 +53,64 @@ object FoodModule { }.start() } - fun uri2FloatArray(uri: Uri): FloatArray? { - return MyApp.instance?.let { context -> - ImageUtil.uriToBitmap(context, uri)?.let { - bitmap2FloatArray(it) +// fun uri2FloatArray(uri: Uri): FloatArray? { +// return MyApp.instance?.let { context -> +// ImageUtil.uriToBitmap(context, uri)?.let { +// bitmap2FloatArray(it) +// } +// } +// } + + fun bitmap2FloatArray(originBitmap: Bitmap): FloatArray? { + var rgb565Bitmap: Bitmap?=null + try { + val scaledBitmap = originBitmap.scale(MODEL_INPUT_WIDTH, MODEL_INPUT_HEIGHT) + originBitmap.recycle() + rgb565Bitmap = scaledBitmap.copy(Bitmap.Config.RGB_565, false) + scaledBitmap.recycle() + val inputTensor = TensorImageUtils.bitmapToFloat32Tensor( + rgb565Bitmap, + NO_MEAN_RGB, // [0.485, 0.456, 0.406] TORCHVISION_NORM_MEAN_RGB + NO_STD_RGB // [0.229, 0.224, 0.225] TORCHVISION_NORM_STD_RGB + ) + val outputTensor = module_mobile.forward(IValue.from(inputTensor)).toTensor() + return outputTensor.dataAsFloatArray + } catch (e: OutOfMemoryError) { + e.printStackTrace() + } finally { + if (rgb565Bitmap != null && rgb565Bitmap.isRecycled.not()) { + rgb565Bitmap.recycle() } + //System.gc() + //System.runFinalization() } + return null } - fun bitmap2FloatArray(bitmap: Bitmap): FloatArray { - val inputTensor = TensorImageUtils.bitmapToFloat32Tensor( - bitmap, - NO_MEAN_RGB, // [0.485, 0.456, 0.406] TORCHVISION_NORM_MEAN_RGB - NO_STD_RGB // [0.229, 0.224, 0.225] TORCHVISION_NORM_STD_RGB - ) - val outputTensor = module_mobile.forward(IValue.from(inputTensor)).toTensor() -// return floatArrayOf() - val floatArray = outputTensor.dataAsFloatArray - return floatArray - } +// fun queryFood(uri: Uri, queryCount: Int = 15): List? { +// return uri2FloatArray(uri)?.let { +// queryFood(it, queryCount) +// } +// } - fun queryFood(uri: Uri, queryCount: Int = 15): List? { - return uri2FloatArray(uri)?.let { - queryFood(it, queryCount) - } - } +// /** +// * 返回识别物品名称列表 +// */ +// fun queryFood(bitmap: Bitmap, queryCount: Int = 15): List { +// val floatArray = bitmap2FloatArray(bitmap) +// return queryFood(floatArray, queryCount) +// } - /** - * 返回识别物品名称列表 - */ - fun queryFood(bitmap: Bitmap, queryCount: Int = 15): List { - val floatArray = bitmap2FloatArray(bitmap) - return queryFood(floatArray, queryCount) - } +// /** +// * 返回识别物品IdNameScore对象列表 +// */ +// fun queryFoodNameScore(bitmap: Bitmap, queryCount: Int = 15): List { +// val floatArray = bitmap2FloatArray(bitmap) +// return queryFoodNameScore(floatArray, queryCount) +// } - /** - * 返回识别物品IdNameScore对象列表 - */ - fun queryFoodNameScore(bitmap: Bitmap, queryCount: Int = 15): List { - val floatArray = bitmap2FloatArray(bitmap) - return queryFoodNameScore(floatArray, queryCount) - } - - fun queryFoodNameScore(floatArray: FloatArray, queryCount: Int = 15): List { + fun queryFoodNameScore(floatArray: FloatArray?, queryCount: Int = 15): List { + if (floatArray == null) return emptyList() val query: Query = box.query(Food_.foodVector.nearestNeighbors(floatArray, queryCount)).build() //查询比较分数 @@ -106,7 +126,7 @@ object FoodModule { } fun getFoodScoreList(bitmap: Bitmap, queryCount: Int = 15): List { val floatArray = bitmap2FloatArray(bitmap) -// return emptyList() + if (floatArray == null) return emptyList() val nameScoreList = queryFoodNameScore(floatArray, queryCount) if (nameScoreList.isEmpty()) { return emptyList() @@ -170,7 +190,9 @@ object FoodModule { val classIdx = labelsList[index] val foodName = foodMap["$classIdx"] val array = floatList.toFloatArray() - box.put(Food(name = foodName, foodVector = array, foodIdx = DEFAULT_FOOD_INDEX)) + ObjectBox.boxStore.runInTx { + box.put(Food(name = foodName, foodVector = array, foodIdx = DEFAULT_FOOD_INDEX)) + } } action() } @@ -184,47 +206,75 @@ object FoodModule { * * 不可能反正SD */ - fun copyAssetToCache(context: Context, fileName: String): String? { - // 此app的缓存目录 --> 会默认在 cache目录...,可以自己去看看哦 - val cacheDir = context.getCacheDir() - if (!cacheDir.exists()) { - cacheDir.mkdirs() // TODO 如果没有缓存目录,就创建 - } - val outPath = File(cacheDir, fileName) // TODO 创建输出的文件位置 - if (outPath.exists()) { - outPath.delete() // TODO 如果该文件已经存在,就删掉 - } - var `is`: InputStream? = null // 读取 - var fos: FileOutputStream? = null // 写入 - try { - // 创建文件,如果创建成功,就返回true - val res = outPath.createNewFile() - if (res) { - `is` = context.getAssets().open(fileName) // 拿到main/assets目录的输入流,用于读取字节 - fos = FileOutputStream(outPath) // 读取出来的字节最终写到outPath - val buf = ByteArray(`is`.available()) // 缓存区 - var byteCount: Int +// fun copyAssetToCache(context: Context, fileName: String): String? { +// // 此app的缓存目录 --> 会默认在 cache目录...,可以自己去看看哦 +// val cacheDir = context.getCacheDir() +// if (!cacheDir.exists()) { +// cacheDir.mkdirs() // TODO 如果没有缓存目录,就创建 +// } +// val outPath = File(cacheDir, fileName) // TODO 创建输出的文件位置 +// if (outPath.exists()) { +// outPath.delete() // TODO 如果该文件已经存在,就删掉 +// } +// var `is`: InputStream? = null // 读取 +// var fos: FileOutputStream? = null // 写入 +// try { +// // 创建文件,如果创建成功,就返回true +// val res = outPath.createNewFile() +// if (res) { +// `is` = context.getAssets().open(fileName) // 拿到main/assets目录的输入流,用于读取字节 +// fos = FileOutputStream(outPath) // 读取出来的字节最终写到outPath +// val buf = ByteArray(`is`.available()) // 缓存区 +// var byteCount: Int +// +// // 开始循环读取 +// while ((`is`.read(buf).also { byteCount = it }) != -1) { +// fos.write(buf, 0, byteCount) +// } +// return outPath.getAbsolutePath() +// } +// } catch (e: IOException) { +// e.printStackTrace() +// } finally { +// try { +// // TODO 一定要记得关闭资源,为了不去性能的磨损 +// fos?.flush() +// `is`?.close() +// fos?.close() +// } catch (e: IOException) { +// e.printStackTrace() +// } +// } +// return null +// } - // 开始循环读取 - while ((`is`.read(buf).also { byteCount = it }) != -1) { - fos.write(buf, 0, byteCount) - } - return outPath.getAbsolutePath() + fun copyAssetToCache(context: Context, fileName: String): String? { + val cacheFile = File(context.cacheDir, fileName) + val buffer = ByteArray(8 * 1024) + var inputStream: InputStream? = null + var outputStream: FileOutputStream? = null + + try { + inputStream = context.assets.open(fileName) + outputStream = FileOutputStream(cacheFile) + var byteCount: Int + while (inputStream.read(buffer).also { byteCount = it } != -1) { + outputStream.write(buffer, 0, byteCount) } + outputStream.channel.force(true) // 强制物理落盘,比flush更彻底 + return cacheFile.absolutePath } catch (e: IOException) { e.printStackTrace() - } finally { - try { - // TODO 一定要记得关闭资源,为了不去性能的磨损 - fos?.flush() - `is`?.close() - fos?.close() - } catch (e: IOException) { - e.printStackTrace() + Timber.tag("FoodModule").d("文件拷贝失败 fileName=$fileName, error=${e.message}") + // 拷贝失败时删除残缺文件,避免下次读取到损坏文件 + if (cacheFile.exists()) { + cacheFile.delete() } + } finally { + outputStream?.close() + inputStream?.close() } return null } - } \ No newline at end of file diff --git a/app/src/main/java/com/sw/inbound/objbox/ObjectBox.kt b/app/src/main/java/com/sw/inbound/objbox/ObjectBox.kt index 25e9f94..6a6f38c 100644 --- a/app/src/main/java/com/sw/inbound/objbox/ObjectBox.kt +++ b/app/src/main/java/com/sw/inbound/objbox/ObjectBox.kt @@ -55,13 +55,15 @@ object ObjectBox { private set fun init(context: Context) { + // 优化:调大事务回收超时时间 + System.setProperty("objectbox.finalizerTimeout", "30000"); // On Android make sure to pass a Context when building the Store. boxStore = try { MyObjectBox.builder() .androidContext(context.applicationContext) .build() } catch (e: DbException) { - if (e.javaClass.equals(DbException::class.java) || e is FileCorruptException) { + if (e.javaClass == DbException::class.java || e is FileCorruptException) { // Failed to build BoxStore due to database file issue, store message; // checked in NoteListActivity to notify user. dbExceptionMessage = e.toString()