From a14c2c3348984d3dc16458ccd67b1f954039cee8 Mon Sep 17 00:00:00 2001 From: lvmeng <848755140@qq.com> Date: Fri, 6 Mar 2026 16:52:19 +0800 Subject: [PATCH] =?UTF-8?q?refactor(utils):=20=E7=A7=BB=E9=99=A4=20onSucce?= =?UTF-8?q?ss=20=E5=86=97=E4=BD=99=E5=9B=9E=E8=B0=83=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=20ObjectBox=20=E6=95=B0=E6=8D=AE=E5=BA=93=E6=93=8D?= =?UTF-8?q?=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PhotoCaptureHelper: 删除构造参数 onSuccess,成功回调统一由 callbackList 分发 - CameraUtils: 同步移除 initCaptureHelper 中的 onSuccess = {} 空参数 - ObjectBox: 使用 QueryBuilder 替代全量内存过滤(count/filter/remove),提升查询性能 - ObjectBox: 修复 isStorageAvailable 错误检查外部存储状态,改为仅检查内部存储 - ObjectBox: boxStore 添加 @Volatile 注解保证多线程可见性 - ObjectBox: copyAndGzipDatabaseFileTo 增加 IOException 捕获,避免文件异常崩溃 - ObjectBox: safeDbOp 中使用安全调用替代 !! 断言,避免 NPE Co-Authored-By: Claude Sonnet 4.6 --- .../java/com/sw/inbound/objbox/ObjectBox.kt | 64 +++++++++---------- .../java/com/sw/inbound/utils/CameraUtils.kt | 1 - .../sw/inbound/utils/PhotoCaptureHelper.kt | 3 - 3 files changed, 30 insertions(+), 38 deletions(-) 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 e25d17d..6f78f96 100644 --- a/app/src/main/java/com/sw/inbound/objbox/ObjectBox.kt +++ b/app/src/main/java/com/sw/inbound/objbox/ObjectBox.kt @@ -18,7 +18,6 @@ package com.sw.inbound.objbox import android.annotation.SuppressLint import android.content.Context -import android.os.Environment import android.util.Log import com.sw.inbound.MyApp import com.sw.inbound.utils.LogSaveUtil @@ -32,7 +31,6 @@ import io.objectbox.query.IdWithScore import io.objectbox.query.Query import io.objectbox.sync.Sync import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext @@ -41,13 +39,6 @@ import java.io.FileInputStream import java.io.FileOutputStream import java.io.IOException import java.util.zip.GZIPOutputStream -import kotlin.collections.emptyList -import kotlin.io.copyTo -import kotlin.io.inputStream -import kotlin.io.outputStream -import kotlin.io.use -import kotlin.jvm.java -import kotlin.jvm.javaClass /** * Singleton to keep BoxStore reference and provide current list of Notes Objects. @@ -60,9 +51,10 @@ object ObjectBox { // 避免直接修改自动生成的MyObjectBox类 // 跨版本升级时使用boxStore.runInTx执行数据迁移 - private const val TAG = "ObjectBox" + // 添加 @Volatile 保证多线程下的可见性 + @Volatile var boxStore: BoxStore? = null /** @@ -111,6 +103,7 @@ object ObjectBox { /** * If the database file is not in use, compresses (GZIP) and copies it to the given [target]. + * 增加 IOException 捕获,避免文件异常导致崩溃。 */ fun copyAndGzipDatabaseFileTo(target: File, context: Context): Boolean { if (BoxStore.isDatabaseOpen(context, null)) { @@ -123,13 +116,18 @@ object ObjectBox { // If a name was given when building BoxStore use that instead of the default below. val dbName = BoxStoreBuilder.DEFAULT_NAME - File(context.filesDir, "objectbox/$dbName/data.mdb").inputStream().use { input -> - target.parentFile?.mkdirs() - GZIPOutputStream(target.outputStream()).use { output -> - input.copyTo(output, DEFAULT_BUFFER_SIZE) + return try { + File(context.filesDir, "objectbox/$dbName/data.mdb").inputStream().use { input -> + target.parentFile?.mkdirs() + GZIPOutputStream(target.outputStream()).use { output -> + input.copyTo(output, DEFAULT_BUFFER_SIZE) + } } + true + } catch (e: IOException) { + Log.e(TAG, "备份数据库文件失败", e) + false } - return true } @@ -145,9 +143,9 @@ object ObjectBox { // 操作中触发损坏,尝试重建数据库 e.printStackTrace() LogSaveUtil.saveLogFile("safeDbOp出现FileCorruptException异常:${e.message},${e.errorCode}") - //val context = boxStore.context - //deleteDbFiles(context) - init(MyApp.instance!!) + // 使用安全调用替代 !! 断言,避免 NPE + val app = MyApp.instance ?: return@withContext null + init(app) null } catch (e: Exception) { e.printStackTrace() @@ -183,36 +181,35 @@ object ObjectBox { box?.put(entities) } + // 使用 QueryBuilder 在数据库层计数,避免全量加载到内存 suspend fun count(name: String) = safeDbOp { - getBox()?.all?.count() { it.name == name } - } ?: 0 + getBox()?.query(Food_.name.equal(name))?.build()?.count() + } ?: 0L + // 使用 QueryBuilder 在数据库层过滤,避免全量加载到内存 suspend fun filter(name: String?) = safeDbOp { - val list = getBox()?.all?.distinctBy { it.name } - if (name.isNullOrBlank().not()) { - list?.filter { it.name?.contains(name) == true } + if (!name.isNullOrBlank()) { + getBox()?.query(Food_.name.contains(name))?.build()?.find() + ?.distinctBy { it.name } } else { - list + getBox()?.all?.distinctBy { it.name } } ?: emptyList() } + // 使用 QueryBuilder 在数据库层删除,避免全量加载到内存 suspend fun remove(name: String) = safeDbOp { - getBox()?.run { - val filterIdList = all.filter { it.name == name }.map { it.id } - removeByIds(filterIdList) - } + getBox()?.query(Food_.name.equal(name))?.build()?.remove() } private val dbMutex = Mutex() // 协程并发锁,保证写入操作原子性 private const val DB_DIR_NAME = "objectbox" // ObjectBox 默认数据库目录 private const val BACKUP_DIR_NAME = "objectbox_backup" // 备份目录 - // 检查存储是否可读写(操作数据库前调用) + // 检查内部存储是否可读写(操作数据库前调用) + // 修复:原逻辑错误地检查外部存储状态,数据库存于内部存储,与外部存储无关 fun isStorageAvailable(context: Context): Boolean { return try { - val state = Environment.getExternalStorageState() - val innerDir = context.filesDir - Environment.MEDIA_MOUNTED == state && innerDir.canRead() && innerDir.canWrite() + context.filesDir.canRead() && context.filesDir.canWrite() } catch (e: Exception) { false } @@ -290,5 +287,4 @@ object ObjectBox { boxStore = null } - -} \ No newline at end of file +} diff --git a/app/src/main/java/com/sw/inbound/utils/CameraUtils.kt b/app/src/main/java/com/sw/inbound/utils/CameraUtils.kt index d1b574c..7d004e7 100644 --- a/app/src/main/java/com/sw/inbound/utils/CameraUtils.kt +++ b/app/src/main/java/com/sw/inbound/utils/CameraUtils.kt @@ -37,7 +37,6 @@ class CameraUtils(private var activity: ComponentActivity) { photoCaptureHelper = PhotoCaptureHelper( context = activity, cameraController = cameraController!!, - onSuccess = {}, onError = { msg -> //toast(msg) failCallback?.invoke() diff --git a/app/src/main/java/com/sw/inbound/utils/PhotoCaptureHelper.kt b/app/src/main/java/com/sw/inbound/utils/PhotoCaptureHelper.kt index 09c9806..5956a8b 100644 --- a/app/src/main/java/com/sw/inbound/utils/PhotoCaptureHelper.kt +++ b/app/src/main/java/com/sw/inbound/utils/PhotoCaptureHelper.kt @@ -16,13 +16,11 @@ import java.io.File * 拍照工具类 * @param context Context 上下文 * @param cameraController CameraController 相机控制器 - * @param onSuccess (Uri) -> Unit 拍照成功回调 * @param onError (String) -> Unit 拍照失败回调 */ class PhotoCaptureHelper( private val context: Context, private val cameraController: CameraController, - private val onSuccess: (Uri) -> Unit = {}, private val onError: (String) -> Unit = {} ) { private val callbackList: MutableList<(Uri) -> Unit> = mutableListOf() @@ -66,7 +64,6 @@ class PhotoCaptureHelper( override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { val photoUri = outputFileResults.savedUri ?: Uri.fromFile(photoFile) Timber.d("照片保存成功: $photoUri") - onSuccess(photoUri) callbackList.forEach { it(photoUri) }