fix(camera): 解决相机拍照功能的竞态条件和重试机制问题

- 修复了相机回调中的竞态条件,使用isShowCamera标识查找空闲槽位
- 将图片处理逻辑移至IO线程,避免主线程阻塞
- 添加了照片拍摄失败回调处理函数
- 简化了相机拍照调用方式,直接传递回调函数
- 在FoodCollectionAdapter中使用Glide加载图片uri
- 添加了相机绑定失败时的自动重试机制,包含延迟重试逻辑
- 增加了详细的错误日志记录和异常处理
This commit is contained in:
2026-06-30 15:54:45 +08:00
parent 088aabc535
commit b6ed47ff87
3 changed files with 32 additions and 27 deletions
@@ -98,15 +98,16 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
} }
private val cameraCallback: (Uri) -> Unit = cameraCallback@{ uri -> private val cameraCallback: (Uri) -> Unit = { uri ->
try { try {
val index = foodCollectionList.indexOfFirst { it.imageFile == null }
if (index == -1) {
ToastUtils.showToast("每次只允许保存${MAX_COUNT}条数据")
settingActivity?.hideWaitingDialog()
return@cameraCallback
}
activity?.lifecycleScope?.launch(Dispatchers.Main) { activity?.lifecycleScope?.launch(Dispatchers.Main) {
// 主线程原子操作:查找空闲槽位并立即标记,避免竞态
val index = foodCollectionList.indexOfFirst { it.isShowCamera }
if (index == -1) {
ToastUtils.showToast("每次只允许保存${MAX_COUNT}条数据")
settingActivity?.hideWaitingDialog()
return@launch
}
foodCollectionList[index].let { foodCollectionList[index].let {
it.imageVector = null it.imageVector = null
it.bitmap = null it.bitmap = null
@@ -115,11 +116,11 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
it.imageUri = uri it.imageUri = uri
} }
collectionAdapter.notifyItemChanged(index) collectionAdapter.notifyItemChanged(index)
} // IO线程处理bitmap
// settingActivity?.hideWaitingDialog() launch(Dispatchers.IO) {
activity?.lifecycleScope?.launch(Dispatchers.IO) { ImageUtil.uriToBitmap(requireActivity(), uri)?.let { bitmap ->
ImageUtil.uriToBitmap(requireActivity(), uri)?.let { bitmap -> getImageVector(index, bitmap)
getImageVector(index, bitmap) }
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
@@ -130,6 +131,10 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
} }
} }
private val photoFailCallback: (String) -> Unit = { errMsg ->
settingActivity?.hideWaitingDialog()
}
private fun getImageVector(index: Int, bitmap: Bitmap) { private fun getImageVector(index: Int, bitmap: Bitmap) {
// val bitmap = BitmapCropper.cropCenter( // val bitmap = BitmapCropper.cropCenter(
// original = srcBmp, // original = srcBmp,
@@ -171,13 +176,7 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
} }
settingActivity?.showWaitingDialog("采集中……") settingActivity?.showWaitingDialog("采集中……")
cameraUtils.takePhoto(succCallback = { uri -> cameraUtils.takePhoto(succCallback = cameraCallback, failCallback = photoFailCallback)
settingActivity?.lifecycleScope?.launch((Dispatchers.IO)) {
cameraCallback(uri)
}
}, failCallback = { errMsg ->
settingActivity?.hideWaitingDialog()
})
} }
private var cameraErrorCount = 0 private var cameraErrorCount = 0
@@ -40,7 +40,7 @@ class FoodCollectionAdapter (var list: MutableList<FoodCollectionBean>) :
//setImageURI(it.imageUri) //setImageURI(it.imageUri)
//setImageBitmap(it.bitmap) //setImageBitmap(it.bitmap)
if (it.imageUri!=null) { if (it.imageUri!=null) {
setImageURI(it.imageUri) load(it.imageUri)
} else { } else {
load(it.imageFile) load(it.imageFile)
} }
@@ -2,6 +2,8 @@ package com.sw.dualscreen.utils
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import android.os.Handler
import android.os.Looper
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.camera.core.ImageCapture import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCaptureException import androidx.camera.core.ImageCaptureException
@@ -42,7 +44,8 @@ class PhotoCaptureHelper(
*/ */
fun takePhoto( fun takePhoto(
fileNamePrefix: String = "IMG_", fileNamePrefix: String = "IMG_",
fileExtension: String = ".jpg" fileExtension: String = ".jpg",
isRetry: Boolean = false
) { ) {
Timber.d("开始拍照采集") Timber.d("开始拍照采集")
@@ -70,13 +73,16 @@ class PhotoCaptureHelper(
} }
override fun onError(exception: ImageCaptureException) { override fun onError(exception: ImageCaptureException) {
Timber.e(exception)
val errorMsg = "拍照失败: ${exception.message}" val errorMsg = "拍照失败: ${exception.message}"
if (errorMsg.contains("Not bound to a valid Camera")) { if (errorMsg.contains("Not bound to a valid Camera") && !isRetry) {
if (bindCamera != null) { Timber.w(exception, "相机未绑定,尝试重新绑定后重试")
bindCamera?.invoke() bindCamera?.invoke()
//takePhoto() // 延迟重试,等待相机异步绑定完成
} Handler(Looper.getMainLooper()).postDelayed({
takePhoto(fileNamePrefix, fileExtension, isRetry = true)
}, 300)
return
} }
Timber.e(exception, errorMsg) Timber.e(exception, errorMsg)
onError(errorMsg) onError(errorMsg)