From 061680b32b7184ee4cf68e1bcfe257e2553d8d93 Mon Sep 17 00:00:00 2001 From: mazengfei <331023091@qq.com> Date: Thu, 4 Jun 2026 17:25:10 +0800 Subject: [PATCH] =?UTF-8?q?fix(objectbox):=20=E4=BF=AE=E5=A4=8D32=E4=BD=8D?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E5=90=91=E9=87=8F=E6=9F=A5=E8=AF=A2=E5=B4=A9?= =?UTF-8?q?=E6=BA=83=EF=BC=8C=E7=BB=9F=E4=B8=80=E4=BD=BF=E7=94=A8findIdsWi?= =?UTF-8?q?thScores=E9=81=BF=E5=85=8D=E5=8A=A0=E8=BD=BD=E5=A4=A7=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- .../activity/CollectedFoodActivity.kt | 2 +- .../sw/dualscreen/activity/MainActivity.kt | 8 +- .../com/sw/dualscreen/objbox/FoodModule.kt | 20 +-- .../com/sw/dualscreen/objbox/ObjectBox.kt | 115 ++++++++++++++++-- 4 files changed, 120 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/com/sw/dualscreen/activity/CollectedFoodActivity.kt b/app/src/main/java/com/sw/dualscreen/activity/CollectedFoodActivity.kt index 44230e5..2dcc349 100644 --- a/app/src/main/java/com/sw/dualscreen/activity/CollectedFoodActivity.kt +++ b/app/src/main/java/com/sw/dualscreen/activity/CollectedFoodActivity.kt @@ -132,7 +132,7 @@ class CollectedFoodActivity : BaseActivity() { foodName = searchName, onSuccess = { items -> runOnUiThread { - loadFoodList(items.onEach { it.foodCount = 1 }) + loadFoodList(items) } }, onFailure = { diff --git a/app/src/main/java/com/sw/dualscreen/activity/MainActivity.kt b/app/src/main/java/com/sw/dualscreen/activity/MainActivity.kt index 36aab30..a51c408 100644 --- a/app/src/main/java/com/sw/dualscreen/activity/MainActivity.kt +++ b/app/src/main/java/com/sw/dualscreen/activity/MainActivity.kt @@ -1232,7 +1232,9 @@ class MainActivity : BaseActivity() { list.forEach { vo -> if (vo.faceDeleted == true) { //删除数据 - FaceDatabase.getInstance(this).faceDao().deleteFaceById(vo.userId) + lifecycleScope.launch(Dispatchers.IO) { + FaceDatabase.getInstance(this@MainActivity).faceDao().deleteFaceById(vo.userId) + } } else { //保存数据 val faceEntity = FaceEntity( @@ -1250,7 +1252,9 @@ class MainActivity : BaseActivity() { } try { if (faceList.isNotEmpty()) { - FaceDatabase.getInstance(this).faceDao().insert(faceList) + lifecycleScope.launch(Dispatchers.IO) { + FaceDatabase.getInstance(this@MainActivity).faceDao().insert(faceList) + } } recognizeViewModel.refreshFaceList(); } catch (e: Exception) { diff --git a/app/src/main/java/com/sw/dualscreen/objbox/FoodModule.kt b/app/src/main/java/com/sw/dualscreen/objbox/FoodModule.kt index 9264267..3ffd434 100644 --- a/app/src/main/java/com/sw/dualscreen/objbox/FoodModule.kt +++ b/app/src/main/java/com/sw/dualscreen/objbox/FoodModule.kt @@ -124,7 +124,7 @@ object FoodModule { suspend fun queryFoodNameScore( floatArray: FloatArray?, - queryCount: Int = 50 + queryCount: Int = 15 ): List { if (floatArray == null) return emptyList() val startTime = System.currentTimeMillis() @@ -133,25 +133,17 @@ object FoodModule { //查询比较分数 // val tempList = query.findWithScores().sortedBy { it.score }.map { "${it.get().name}|${it.get().foodIdx}|${it.score}" } // val idScoreList = query.findIdsWithScores() - val objScoreList = ObjectBox.query(floatArray, queryCount) - Timber.tag(TAG).d("idScoreList:${GsonUtils.toJson(objScoreList)}") - val nameScoreList = mutableListOf() - objScoreList.forEach { - val food = it.get() - nameScoreList.add( - IdNameScore( - id = food.id, - name = food.foodName ?: "", - score = it.score - ) - ) + val tripleList = ObjectBox.query(floatArray, queryCount) + Timber.tag(TAG).d("idScoreList:${GsonUtils.toJson(tripleList)}") + val nameScoreList = tripleList.map { (id, name, score) -> + IdNameScore(id = id, name = name ?: "", score = score) } val nameScoreData = GsonUtils.toJson(nameScoreList) Timber.tag(TAG).d("queryFood,耗时:${System.currentTimeMillis() - startTime},数据:$nameScoreData") return nameScoreList } - suspend fun getFoodScoreList(bitmap: Bitmap, queryCount: Int = 50): List { + suspend fun getFoodScoreList(bitmap: Bitmap, queryCount: Int = 15): List { val startTime = System.currentTimeMillis() val floatArray = withContext(Dispatchers.IO) { bitmap2FloatArray(bitmap, false) diff --git a/app/src/main/java/com/sw/dualscreen/objbox/ObjectBox.kt b/app/src/main/java/com/sw/dualscreen/objbox/ObjectBox.kt index 0537555..d3ee1c6 100644 --- a/app/src/main/java/com/sw/dualscreen/objbox/ObjectBox.kt +++ b/app/src/main/java/com/sw/dualscreen/objbox/ObjectBox.kt @@ -152,19 +152,118 @@ object ObjectBox { } suspend fun query(floatArray: FloatArray, queryCount: Int) = safeDbOp { - val query: Query? = getBox()?.query() - ?.equal(Food_.isDel, false) - ?.and() - ?.nearestNeighbors(Food_.foodVector, floatArray, queryCount) - ?.build() + val box = getBox() ?: return@safeDbOp emptyList>() + + val query: Query = box.query() + .equal(Food_.isDel, false) + .and() + .nearestNeighbors(Food_.foodVector, floatArray, queryCount) + .build() try { - query?.findWithScores() + // 统一使用 findIdsWithScores,避免在 32 位系统上因加载大对象而崩溃 + val idScoreList = query.findIdsWithScores() + if (idScoreList.isNotEmpty()) { + // 根据 ID 查询完整对象,仅提取 id 和 name,丢弃 foodVector 以节省内存 + val foodMap = box.get(idScoreList.map { it.id }).associateBy { it.id } + idScoreList.map { scoreId -> + val food = foodMap[scoreId.id] + Triple(scoreId.id, food?.foodName, scoreId.score) + } + } else { + emptyList() + } + } catch (e: DbException) { + Timber.tag(TAG).e(e, "向量查询失败") + emptyList() } finally { - // 先关闭Query,释放Cursor - query?.close() + query.close() } } ?: emptyList() +// suspend fun queryWithScore(floatArray: FloatArray, queryCount: Int) = safeDbOp { +// val box = getBox() ?: return@safeDbOp emptyList() +// +// val query: Query? = box.query() +// ?.equal(Food_.isDel, false) +// ?.and() +// ?.nearestNeighbors(Food_.foodVector, floatArray, queryCount) +// ?.build() +// +// try { +// // 检测是否为 32 位系统 +// val is32Bit = Build.SUPPORTED_ABIS.any { +// it.contains("armeabi") && !it.contains("arm64") +// } +// +// if (is32Bit) { +// // 32位系统:使用 findWithScoresAndIds 替代 findWithScores +// val scoreIds = query?.findWithScoresAndIds() +// if (scoreIds != null && scoreIds.isNotEmpty()) { +// // 如果需要分数,返回 ScoreId 对象 +// // 注意:这需要修改返回类型或使用其他方式传递分数 +// scoreIds.toList() +// } else { +// emptyList() +// } +// } else { +// // 64位系统:正常使用 findWithScores +// query?.findWithScores() ?: emptyList() +// } +// } catch (e: DbException) { +// // 如果仍然失败,记录日志并返回空列表 +// Timber.tag(TAG).e(e, "Vector query with score failed, returning empty list") +// emptyList() +// } finally { +// query?.close() +// } +// } ?: emptyList() + +// suspend fun query(floatArray: FloatArray, queryCount: Int) = safeDbOp { +// val box = getBox() ?: return@safeDbOp emptyList() +// +// val query: Query? = box.query() +// ?.equal(Food_.isDel, false) +// ?.and() +// ?.nearestNeighbors(Food_.foodVector, floatArray, queryCount) +// ?.build() +// +// try { +// // 检测是否为 32 位系统 +// val is32Bit = Build.SUPPORTED_ABIS.any { +// it.contains("armeabi") && !it.contains("arm64") +// } +// +// if (is32Bit) { +// // 32位系统:先获取带分数的IDs,再批量获取完整对象 +// val scoreIds = query?.findWithScoresAndIds() +// if (scoreIds != null && scoreIds.isNotEmpty()) { +// // 提取 IDs +// val ids = scoreIds.map { it.id }.toLongArray() +// // 批量获取完整对象 +// val foods = box.get(ids.toList()).associateBy { it.id } +// // 按照分数顺序组装结果(保持排序) +// scoreIds.mapNotNull { scoreId -> +// foods[scoreId.id] +// } +// } else { +// emptyList() +// } +// } else { +// // 64位系统:直接使用 findWithScores +// query?.findWithScores() ?: emptyList() +// } +// } catch (e: DbException) { +// // 如果仍然失败,记录日志并返回空列表 +// Timber.tag(TAG).e(e, "Vector query failed, returning empty list") +// emptyList() +// } finally { +// query?.close() +// } +// } ?: emptyList() +// ... existing code ... + + + suspend fun get(id: Long) = safeDbOp { getBox()?.get(id) }