fix(objectbox): 修复32位系统向量查询崩溃,统一使用findIdsWithScores避免加载大对象
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
5c3e51626c
commit
061680b32b
@@ -132,7 +132,7 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
|
|||||||
foodName = searchName,
|
foodName = searchName,
|
||||||
onSuccess = { items ->
|
onSuccess = { items ->
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
loadFoodList(items.onEach { it.foodCount = 1 })
|
loadFoodList(items)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onFailure = {
|
onFailure = {
|
||||||
|
|||||||
@@ -1232,7 +1232,9 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
list.forEach { vo ->
|
list.forEach { vo ->
|
||||||
if (vo.faceDeleted == true) {
|
if (vo.faceDeleted == true) {
|
||||||
//删除数据
|
//删除数据
|
||||||
FaceDatabase.getInstance(this).faceDao().deleteFaceById(vo.userId)
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
FaceDatabase.getInstance(this@MainActivity).faceDao().deleteFaceById(vo.userId)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
//保存数据
|
//保存数据
|
||||||
val faceEntity = FaceEntity(
|
val faceEntity = FaceEntity(
|
||||||
@@ -1250,7 +1252,9 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (faceList.isNotEmpty()) {
|
if (faceList.isNotEmpty()) {
|
||||||
FaceDatabase.getInstance(this).faceDao().insert(faceList)
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
FaceDatabase.getInstance(this@MainActivity).faceDao().insert(faceList)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
recognizeViewModel.refreshFaceList();
|
recognizeViewModel.refreshFaceList();
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ object FoodModule {
|
|||||||
|
|
||||||
suspend fun queryFoodNameScore(
|
suspend fun queryFoodNameScore(
|
||||||
floatArray: FloatArray?,
|
floatArray: FloatArray?,
|
||||||
queryCount: Int = 50
|
queryCount: Int = 15
|
||||||
): List<IdNameScore> {
|
): List<IdNameScore> {
|
||||||
if (floatArray == null) return emptyList()
|
if (floatArray == null) return emptyList()
|
||||||
val startTime = System.currentTimeMillis()
|
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 tempList = query.findWithScores().sortedBy { it.score }.map { "${it.get().name}|${it.get().foodIdx}|${it.score}" }
|
||||||
// val idScoreList = query.findIdsWithScores()
|
// val idScoreList = query.findIdsWithScores()
|
||||||
val objScoreList = ObjectBox.query(floatArray, queryCount)
|
val tripleList = ObjectBox.query(floatArray, queryCount)
|
||||||
Timber.tag(TAG).d("idScoreList:${GsonUtils.toJson(objScoreList)}")
|
Timber.tag(TAG).d("idScoreList:${GsonUtils.toJson(tripleList)}")
|
||||||
val nameScoreList = mutableListOf<IdNameScore>()
|
val nameScoreList = tripleList.map { (id, name, score) ->
|
||||||
objScoreList.forEach {
|
IdNameScore(id = id, name = name ?: "", score = score)
|
||||||
val food = it.get()
|
|
||||||
nameScoreList.add(
|
|
||||||
IdNameScore(
|
|
||||||
id = food.id,
|
|
||||||
name = food.foodName ?: "",
|
|
||||||
score = it.score
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
val nameScoreData = GsonUtils.toJson(nameScoreList)
|
val nameScoreData = GsonUtils.toJson(nameScoreList)
|
||||||
Timber.tag(TAG).d("queryFood,耗时:${System.currentTimeMillis() - startTime},数据:$nameScoreData")
|
Timber.tag(TAG).d("queryFood,耗时:${System.currentTimeMillis() - startTime},数据:$nameScoreData")
|
||||||
return nameScoreList
|
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 startTime = System.currentTimeMillis()
|
||||||
val floatArray = withContext(Dispatchers.IO) {
|
val floatArray = withContext(Dispatchers.IO) {
|
||||||
bitmap2FloatArray(bitmap, false)
|
bitmap2FloatArray(bitmap, false)
|
||||||
|
|||||||
@@ -152,19 +152,118 @@ object ObjectBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun query(floatArray: FloatArray, queryCount: Int) = safeDbOp {
|
suspend fun query(floatArray: FloatArray, queryCount: Int) = safeDbOp {
|
||||||
val query: Query<Food>? = getBox<Food>()?.query()
|
val box = getBox<Food>() ?: return@safeDbOp emptyList<Triple<Long, String?, Double>>()
|
||||||
?.equal(Food_.isDel, false)
|
|
||||||
?.and()
|
val query: Query<Food> = box.query()
|
||||||
?.nearestNeighbors(Food_.foodVector, floatArray, queryCount)
|
.equal(Food_.isDel, false)
|
||||||
?.build()
|
.and()
|
||||||
|
.nearestNeighbors(Food_.foodVector, floatArray, queryCount)
|
||||||
|
.build()
|
||||||
try {
|
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 {
|
} finally {
|
||||||
// 先关闭Query,释放Cursor
|
query.close()
|
||||||
query?.close()
|
|
||||||
}
|
}
|
||||||
} ?: emptyList()
|
} ?: 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 {
|
suspend fun get(id: Long) = safeDbOp {
|
||||||
getBox<Food>()?.get(id)
|
getBox<Food>()?.get(id)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user