fix(objectbox): 修复32位系统向量查询崩溃,统一使用findIdsWithScores避免加载大对象

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
mazengfei
2026-06-04 17:26:36 +08:00
co-authored by Claude Opus 4.6
parent 5c3e51626c
commit 061680b32b
4 changed files with 120 additions and 25 deletions
@@ -132,7 +132,7 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
foodName = searchName,
onSuccess = { items ->
runOnUiThread {
loadFoodList(items.onEach { it.foodCount = 1 })
loadFoodList(items)
}
},
onFailure = {
@@ -1232,7 +1232,9 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
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<ActivityMainBinding>() {
}
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) {
@@ -124,7 +124,7 @@ object FoodModule {
suspend fun queryFoodNameScore(
floatArray: FloatArray?,
queryCount: Int = 50
queryCount: Int = 15
): List<IdNameScore> {
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<IdNameScore>()
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<IdNameScore> {
suspend fun getFoodScoreList(bitmap: Bitmap, queryCount: Int = 15): List<IdNameScore> {
val startTime = System.currentTimeMillis()
val floatArray = withContext(Dispatchers.IO) {
bitmap2FloatArray(bitmap, false)
@@ -152,19 +152,118 @@ object ObjectBox {
}
suspend fun query(floatArray: FloatArray, queryCount: Int) = safeDbOp {
val query: Query<Food>? = getBox<Food>()?.query()
?.equal(Food_.isDel, false)
?.and()
?.nearestNeighbors(Food_.foodVector, floatArray, queryCount)
?.build()
val box = getBox<Food>() ?: return@safeDbOp emptyList<Triple<Long, String?, Double>>()
val query: Query<Food> = 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<Food>() ?: return@safeDbOp emptyList()
//
// val query: Query<Food>? = 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<Food>() ?: return@safeDbOp emptyList()
//
// val query: Query<Food>? = 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<Food>()?.get(id)
}