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 {
val index = foodCollectionList.indexOfFirst { it.imageFile == null }
if (index == -1) {
ToastUtils.showToast("每次只允许保存${MAX_COUNT}条数据")
settingActivity?.hideWaitingDialog()
return@cameraCallback
}
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 {
it.imageVector = null
it.bitmap = null
@@ -115,11 +116,11 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
it.imageUri = uri
}
collectionAdapter.notifyItemChanged(index)
}
// settingActivity?.hideWaitingDialog()
activity?.lifecycleScope?.launch(Dispatchers.IO) {
ImageUtil.uriToBitmap(requireActivity(), uri)?.let { bitmap ->
getImageVector(index, bitmap)
// IO线程处理bitmap
launch(Dispatchers.IO) {
ImageUtil.uriToBitmap(requireActivity(), uri)?.let { bitmap ->
getImageVector(index, bitmap)
}
}
}
} 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) {
// val bitmap = BitmapCropper.cropCenter(
// original = srcBmp,
@@ -171,13 +176,7 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
}
settingActivity?.showWaitingDialog("采集中……")
cameraUtils.takePhoto(succCallback = { uri ->
settingActivity?.lifecycleScope?.launch((Dispatchers.IO)) {
cameraCallback(uri)
}
}, failCallback = { errMsg ->
settingActivity?.hideWaitingDialog()
})
cameraUtils.takePhoto(succCallback = cameraCallback, failCallback = photoFailCallback)
}
private var cameraErrorCount = 0
@@ -40,7 +40,7 @@ class FoodCollectionAdapter (var list: MutableList<FoodCollectionBean>) :
//setImageURI(it.imageUri)
//setImageBitmap(it.bitmap)
if (it.imageUri!=null) {
setImageURI(it.imageUri)
load(it.imageUri)
} else {
load(it.imageFile)
}
@@ -2,6 +2,8 @@ package com.sw.dualscreen.utils
import android.content.Context
import android.net.Uri
import android.os.Handler
import android.os.Looper
import androidx.core.content.FileProvider
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCaptureException
@@ -42,7 +44,8 @@ class PhotoCaptureHelper(
*/
fun takePhoto(
fileNamePrefix: String = "IMG_",
fileExtension: String = ".jpg"
fileExtension: String = ".jpg",
isRetry: Boolean = false
) {
Timber.d("开始拍照采集")
@@ -70,13 +73,16 @@ class PhotoCaptureHelper(
}
override fun onError(exception: ImageCaptureException) {
Timber.e(exception)
val errorMsg = "拍照失败: ${exception.message}"
if (errorMsg.contains("Not bound to a valid Camera")) {
if (bindCamera != null) {
bindCamera?.invoke()
//takePhoto()
}
if (errorMsg.contains("Not bound to a valid Camera") && !isRetry) {
Timber.w(exception, "相机未绑定,尝试重新绑定后重试")
bindCamera?.invoke()
// 延迟重试,等待相机异步绑定完成
Handler(Looper.getMainLooper()).postDelayed({
takePhoto(fileNamePrefix, fileExtension, isRetry = true)
}, 300)
return
}
Timber.e(exception, errorMsg)
onError(errorMsg)