fix(camera): 解决相机拍照功能的竞态条件和重试机制问题
- 修复了相机回调中的竞态条件,使用isShowCamera标识查找空闲槽位 - 将图片处理逻辑移至IO线程,避免主线程阻塞 - 添加了照片拍摄失败回调处理函数 - 简化了相机拍照调用方式,直接传递回调函数 - 在FoodCollectionAdapter中使用Glide加载图片uri - 添加了相机绑定失败时的自动重试机制,包含延迟重试逻辑 - 增加了详细的错误日志记录和异常处理
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user