refactor(camera): 优化相机拍照功能实现
- 移除 PhotoCaptureHelper 中的测试模式和无用的 onSuccess 回调参数 - 将 CollectFragment 中的 UI 操作迁移到正确的协程调度器 (Main 和 IO) - 使用 lifecycleScope 替代 runOnUiThread 确保生命周期安全 - 重构 ImageUtil.uriToBitmap 方法,添加图片尺寸压缩逻辑以减少内存占用 - 从 InitActivity 中移除已注释的人脸识别相关初始化代码 - 修复 cameraCallback 中的拼写错误 (rerurn@ -> return@)
This commit is contained in:
@@ -14,7 +14,6 @@ import com.sw.dualscreen.ext.clickWithDebounce
|
||||
import com.sw.dualscreen.ext.invisible
|
||||
import com.sw.dualscreen.ext.visible
|
||||
import com.sw.dualscreen.objbox.FoodModule
|
||||
import com.sw.dualscreen.utils.FaceDbImporter
|
||||
import com.sw.dualscreen.utils.FoodVectorTool
|
||||
import com.sw.dualscreen.utils.L
|
||||
import com.sw.dualscreen.utils.NetworkUtils
|
||||
@@ -23,10 +22,7 @@ import com.sw.dualscreen.viewmodel.BaseViewModel
|
||||
import com.sw.dualscreen.viewmodel.NetViewModelV2
|
||||
import com.sw.plate.utils.AppUtil
|
||||
import com.sw.plate.utils.ToastUtils
|
||||
import com.sw.plate.utils.arcface.faceserver.FaceServer
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class InitActivity : BaseActivity<ActivityInitBinding>() {
|
||||
companion object {
|
||||
@@ -69,28 +65,6 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
|
||||
}
|
||||
showWaitingDialog("网络连接中...")
|
||||
checkNetworkTimer?.start()
|
||||
|
||||
// val isSuccess = viewModel.checkEquipmentInfo()
|
||||
// if (isSuccess) {
|
||||
// goMainActivity()
|
||||
// return
|
||||
// }
|
||||
// binding.imgQr.setImageBitmap(
|
||||
// QRCodeUtil.generateQRCode(
|
||||
// content = GlobalData.deviceId,
|
||||
// size = 200
|
||||
// )
|
||||
// )
|
||||
// binding.initButton.setOnClickListener {
|
||||
// viewModel.getDeviceToken()
|
||||
// }
|
||||
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
FaceServer.getInstance().clearAllFaces()
|
||||
loadTestDb()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun registerDataChange() {
|
||||
@@ -118,7 +92,6 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
|
||||
}
|
||||
}
|
||||
}
|
||||
checkCameraPermissionAndGo()
|
||||
}
|
||||
|
||||
private fun goMainActivity() {
|
||||
@@ -264,24 +237,24 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
|
||||
|
||||
|
||||
|
||||
private fun loadTestDb() {
|
||||
// 从测试库导入人脸数据
|
||||
FaceDbImporter.importFromAssets(this, object : FaceDbImporter.ImportCallback {
|
||||
public override fun onProgress(current: Int, total: Int) {
|
||||
showWaitingDialog("导入人脸数据 " + current + "/" + total)
|
||||
}
|
||||
|
||||
public override fun onError(message: String?) {
|
||||
hideWaitingDialog()
|
||||
ToastUtils.showToast("离线数据导入失败:" + message)
|
||||
}
|
||||
|
||||
public override fun onComplete() {
|
||||
hideWaitingDialog()
|
||||
ToastUtils.showToast("离线数据导入成功")
|
||||
// 后续流程会自动跳转
|
||||
}
|
||||
})
|
||||
}
|
||||
// private fun loadTestDb() {
|
||||
// // 从测试库导入人脸数据
|
||||
// FaceDbImporter.importFromAssets(this, object : FaceDbImporter.ImportCallback {
|
||||
// public override fun onProgress(current: Int, total: Int) {
|
||||
// showWaitingDialog("导入人脸数据 " + current + "/" + total)
|
||||
// }
|
||||
//
|
||||
// public override fun onError(message: String?) {
|
||||
// hideWaitingDialog()
|
||||
// ToastUtils.showToast("离线数据导入失败:" + message)
|
||||
// }
|
||||
//
|
||||
// public override fun onComplete() {
|
||||
// hideWaitingDialog()
|
||||
// ToastUtils.showToast("离线数据导入成功")
|
||||
// // 后续流程会自动跳转
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -34,6 +34,7 @@ import com.sw.dualscreen.utils.Debouncer
|
||||
import com.sw.dualscreen.utils.ImageUploader
|
||||
import com.sw.dualscreen.utils.ImageUtil
|
||||
import com.sw.plate.utils.ToastUtils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import timber.log.Timber
|
||||
@@ -97,15 +98,15 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
|
||||
}
|
||||
|
||||
|
||||
private val cameraCallback: (Uri) -> Unit = { uri ->
|
||||
private val cameraCallback: (Uri) -> Unit = cameraCallback@{ uri ->
|
||||
try {
|
||||
val index = foodCollectionList.indexOfFirst { it.imageFile == null }
|
||||
if (index == -1) {
|
||||
ToastUtils.showToast("每次只允许保存${MAX_COUNT}条数据")
|
||||
settingActivity?.hideWaitingDialog()
|
||||
rerurn@ cameraCallback
|
||||
return@cameraCallback
|
||||
}
|
||||
activity?.runOnUiThread {
|
||||
activity?.lifecycleScope?.launch(Dispatchers.Main) {
|
||||
foodCollectionList[index].let {
|
||||
it.imageVector = null
|
||||
it.bitmap = null
|
||||
@@ -116,7 +117,7 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
|
||||
collectionAdapter.notifyItemChanged(index)
|
||||
}
|
||||
// settingActivity?.hideWaitingDialog()
|
||||
activity?.lifecycleScope?.launch {
|
||||
activity?.lifecycleScope?.launch(Dispatchers.IO) {
|
||||
ImageUtil.uriToBitmap(requireActivity(), uri)?.let { bitmap ->
|
||||
getImageVector(index, bitmap)
|
||||
}
|
||||
@@ -149,19 +150,16 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
|
||||
)
|
||||
settingActivity?.log("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${file?.absolutePath}")
|
||||
|
||||
activity?.runOnUiThread {
|
||||
foodCollectionList[index].let {
|
||||
it.imageVector = imageVector
|
||||
it.bitmap = null
|
||||
it.isShowCamera = false
|
||||
it.imageFile = file
|
||||
}
|
||||
collectionAdapter.notifyItemChanged(index)
|
||||
foodCollectionList[index].let {
|
||||
it.imageVector = imageVector
|
||||
it.bitmap = null
|
||||
it.isShowCamera = false
|
||||
it.imageFile = file
|
||||
}
|
||||
settingActivity?.hideWaitingDialog()
|
||||
if (bitmap.isRecycled.not()) {
|
||||
bitmap.recycle()
|
||||
}
|
||||
settingActivity?.hideWaitingDialog()
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
@@ -171,19 +169,15 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
|
||||
ToastUtils.showToast("每次只允许保存${MAX_COUNT}条数据")
|
||||
return
|
||||
}
|
||||
|
||||
settingActivity?.showWaitingDialog("采集中……")
|
||||
cameraUtils.takePhoto(cameraCallback) { errMsg ->
|
||||
cameraUtils.takePhoto(succCallback = { uri ->
|
||||
settingActivity?.lifecycleScope?.launch((Dispatchers.IO)) {
|
||||
cameraCallback(uri)
|
||||
}
|
||||
}, failCallback = { errMsg ->
|
||||
settingActivity?.hideWaitingDialog()
|
||||
// ToastUtils.showToast("拍照异常,请重新操作")
|
||||
// if (cameraErrorCount >= 10) {
|
||||
// ToastUtils.showToast("拍照异常,请重新操作")
|
||||
// settingActivity?.log("拍照异常,请重新操作:$errMsg")
|
||||
// cameraErrorCount = 0
|
||||
// return@takePhoto
|
||||
// }
|
||||
// cameraErrorCount++
|
||||
// takePhoto()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private var cameraErrorCount = 0
|
||||
|
||||
@@ -153,7 +153,9 @@ object FoodModule {
|
||||
|
||||
suspend fun getFoodScoreList(bitmap: Bitmap, queryCount: Int = 50): List<IdNameScore> {
|
||||
val startTime = System.currentTimeMillis()
|
||||
val floatArray = bitmap2FloatArray(bitmap, false)
|
||||
val floatArray = withContext(Dispatchers.IO) {
|
||||
bitmap2FloatArray(bitmap, false)
|
||||
}
|
||||
Timber.tag(TAG).d("bitmap2FloatArray,耗时:${System.currentTimeMillis() - startTime}")
|
||||
|
||||
val nameScoreList = queryFoodNameScore(floatArray, queryCount)
|
||||
|
||||
@@ -34,7 +34,6 @@ class CameraUtils(private var activity: ComponentActivity) {
|
||||
photoCaptureHelper = PhotoCaptureHelper(
|
||||
context = activity,
|
||||
cameraController = cameraController!!,
|
||||
onSuccess = {},
|
||||
onError = { msg ->
|
||||
//toast(msg)
|
||||
failCallback?.invoke(msg)
|
||||
|
||||
@@ -15,17 +15,23 @@ object ImageUtil {
|
||||
|
||||
fun uriToBitmap(context: Context, uri: Uri): Bitmap? {
|
||||
return try {
|
||||
val options = BitmapFactory.Options()
|
||||
//options.inSampleSize = 2; // 这会将图片的尺寸缩小到原来的1/2
|
||||
options.inJustDecodeBounds = false
|
||||
// options.inPreferredConfig = Bitmap.Config.ARGB_8888
|
||||
options.inPreferredConfig = Bitmap.Config.RGB_565
|
||||
val options = BitmapFactory.Options().apply {
|
||||
inJustDecodeBounds = true
|
||||
}
|
||||
// 先只读尺寸,计算合适的采样率
|
||||
context.contentResolver.openInputStream(uri)?.use { stream ->
|
||||
BitmapFactory.decodeStream(stream, null, options)
|
||||
// BitmapFactory.decodeStream(stream)
|
||||
}
|
||||
// 目标最长边不超过 800px,减少内存占用
|
||||
val maxDim = maxOf(options.outWidth, options.outHeight)
|
||||
val sampleSize = (maxDim / 800).coerceAtLeast(1)
|
||||
options.apply {
|
||||
inJustDecodeBounds = false
|
||||
inSampleSize = sampleSize
|
||||
inPreferredConfig = Bitmap.Config.RGB_565
|
||||
}
|
||||
context.contentResolver.openInputStream(uri)?.use { stream ->
|
||||
BitmapFactory.decodeStream(stream)
|
||||
BitmapFactory.decodeStream(stream, null, options)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
|
||||
@@ -20,13 +20,8 @@ import java.io.File
|
||||
class PhotoCaptureHelper(
|
||||
private val context: Context,
|
||||
private val cameraController: CameraController,
|
||||
private val onSuccess: (Uri) -> Unit = {},
|
||||
private val onError: (String) -> Unit = {}
|
||||
) {
|
||||
companion object {
|
||||
/** 临时测试开关:true 时跳过拍照,直接返回固定图片Uri */
|
||||
const val TEST_MODE = true
|
||||
}
|
||||
|
||||
private val callbackList: MutableList<(Uri) -> Unit> = mutableListOf()
|
||||
fun addSuccessCallback(callback:(Uri) -> Unit) {
|
||||
@@ -49,28 +44,6 @@ class PhotoCaptureHelper(
|
||||
fileNamePrefix: String = "IMG_",
|
||||
fileExtension: String = ".jpg"
|
||||
) {
|
||||
// TODO: 测试完成后删除此段代码,并将 TEST_MODE 置为 false
|
||||
if (TEST_MODE) {
|
||||
val testDir = context.externalCacheDir ?: context.cacheDir
|
||||
val testFile = File(testDir, "test_photo_${System.currentTimeMillis()}.jpg")
|
||||
if (!testFile.exists()) {
|
||||
testFile.parentFile?.mkdirs()
|
||||
val bitmap = android.graphics.Bitmap.createBitmap(480, 640, android.graphics.Bitmap.Config.ARGB_8888)
|
||||
val canvas = android.graphics.Canvas(bitmap)
|
||||
canvas.drawColor(0xFFCCCCCC.toInt())
|
||||
testFile.outputStream().use { fos ->
|
||||
bitmap.compress(android.graphics.Bitmap.CompressFormat.JPEG, 95, fos)
|
||||
}
|
||||
bitmap.recycle()
|
||||
}
|
||||
val authority = "${context.packageName}.fileprovider"
|
||||
val testUri = FileProvider.getUriForFile(context, authority, testFile)
|
||||
Timber.d("测试模式:跳过拍照,返回 content://Uri: $testUri")
|
||||
onSuccess(testUri)
|
||||
callbackList.forEach { it(testUri) }
|
||||
return
|
||||
}
|
||||
|
||||
Timber.d("开始拍照采集")
|
||||
|
||||
try {
|
||||
@@ -91,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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user