【问题标题】:How to stop camera from working when it no longer visible in the compisition?当相机在构图中不再可见时如何停止相机工作?
【发布时间】:2023-02-19 02:29:05
【问题描述】:

我有一个 bottomSheetScaffold,其中包含一个 BottomSheet

BottomSheet 使用设备的摄像头,我将 CameraX 与 Google 的 MLkit 一起用于条形扫描

让我们考虑接受许可 发生了什么(不正确):一旦我向上展开底页,我就会显示 CameraPreview、显示相机预览和分析预览图像的 ImageAnalyzer。

现在 bottomSheet 已展开,相机预览可见并按预期工作 然后我折叠 bottomSheet,但相机仍在工作(分析仪也是如此, imageAnalysis.clearAnalyzer()清除分析部分)

结果:不是我想要的正确行为

那么如何在 bottomSheetState 折叠后停止相机工作和使用资源,并且仅在 bottomSheetState 展开时才允许相机

它是如何工作的(错误):

我遇到的问题是,相机绑定到活动的生命周期,而不是可组合本身,当重新组合发生时,它仍然认为相机是活动的,因为它没有附加到组合生命周期

Composition 是如何工作的:

代码:

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun BottomSheetContent(
    modifier: Modifier = Modifier,
    bottomSheetState: BottomSheetState
) {
    Column(
        modifier = modifier
            .fillMaxWidth()
            .fillMaxHeight(0.8f)
    ) {
        PeekBar()
        ScanningSerialTextTitle(modifier)

        if (bottomSheetState.isExpanded) {
            CameraBox(modifier)
        } else {

            EmptyBox()
        }
    }
}


@Composable
fun EmptyBox(modifier: Modifier = Modifier) {
    Box(
        modifier = modifier
            .fillMaxSize()
            .background(color = Color.DarkGray)
    )
}

@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun CameraBox(modifier: Modifier = Modifier) {
    val cameraPermissionState = rememberPermissionState(permission = Manifest.permission.CAMERA)



    val lifeCycleOwner = LocalLifecycleOwner.current
    DisposableEffect(key1 = lifeCycleOwner, effect = {
        val observer = LifecycleEventObserver { _, event ->
            if (event == Lifecycle.Event.ON_START) {
                cameraPermissionState.launchPermissionRequest()
            }
        }
        lifeCycleOwner.lifecycle.addObserver(observer)
        onDispose { lifeCycleOwner.lifecycle.removeObserver(observer) }
    })

    cameraPermissionState.handlePermissionCases(
        ShouldShowRationaleContent = {
            ShouldShowRationaleContent(cameraPermissionState = cameraPermissionState)
        },
        PermissionDeniedPermanentlyContent = {
            PermissionDeniedPermanentContent()
        }) {
        val context = LocalContext.current
        val barCodeVal = remember { mutableStateOf("") }
        CameraPreview(onBarcodeDetected = { barcodes ->
            barcodes.forEach { barcode ->
                barcode.rawValue?.let { barcodeValue ->
                    barCodeVal.value = barcodeValue
                    Toast.makeText(context, barcodeValue, Toast.LENGTH_SHORT).show()
                }
            }
        }, onBarcodeFailed = {}, onBarcodeNotFound = {})
    }


}


@Composable
fun CameraPreview(
    modifier: Modifier = Modifier,
    onBarcodeDetected: (barcodes: List<Barcode>) -> Unit,
    onBarcodeFailed: (exception: Exception) -> Unit,
    onBarcodeNotFound: (text: String) -> Unit,
) {
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current

    AndroidView(
        modifier = modifier.fillMaxSize(),
        factory = { androidViewContext -> initPreviewView(androidViewContext) },
        update = { previewView: PreviewView ->
            val cameraSelector: CameraSelector = buildCameraSelector(CameraSelector.LENS_FACING_BACK)
            val cameraExecutor: ExecutorService = Executors.newSingleThreadExecutor()
            val cameraProviderFuture: ListenableFuture<ProcessCameraProvider> =
                ProcessCameraProvider.getInstance(context)


            val preview = buildPreview().also {
                it.setSurfaceProvider(previewView.surfaceProvider)
            }


            val barcodeAnalyser = BarCodeAnalyser(
                onBarcodeDetected = onBarcodeDetected,
                onBarcodeFailed = onBarcodeFailed,
                onBarCodeNotFound = onBarcodeNotFound
            )
            val imageAnalysis: ImageAnalysis =
                buildImageAnalysis(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST).also {
                    it.setAnalyzer(cameraExecutor, barcodeAnalyser)
                }

            cameraProviderFuture.addListener({
                val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
                try {
                    cameraProvider.unbindAll() //Make sure we only use 1 usecase related to camera
                    val camera = cameraProvider.bindToLifecycle(
                        lifecycleOwner,
                        cameraSelector,
                        preview,
                        imageAnalysis
                    )
                    camera.cameraControl.enableTorch(true)

                } catch (e: Exception) {
                    Log.d("TAG", "CameraPreview: ${e.localizedMessage}")
                }
            }, ContextCompat.getMainExecutor(context))
        }
    )
}


private fun initPreviewView(androidViewContext: Context): PreviewView {
     val previewView = PreviewView(androidViewContext).apply {
        implementationMode = PreviewView.ImplementationMode.COMPATIBLE
    }
    return previewView
}

private fun buildPreview(): Preview {
    return Preview.Builder().build()
}

private fun buildImageAnalysis(imageAnalysisStrategy: Int): ImageAnalysis {
    return ImageAnalysis.Builder()
        .setBackpressureStrategy(imageAnalysisStrategy)
        .build()
}

private fun buildCameraSelector(cameraLens: Int): CameraSelector {
    return CameraSelector.Builder()
        .requireLensFacing(cameraLens)
        .build()
}

我尝试了什么: 我尝试将 BottomSheetState 的状态传递给可组合项,并检查状态,这应该会触发重新组合,但由于我使用 Android 的相机作为视图,这并不能解决问题

【问题讨论】:

    标签: android kotlin android-jetpack-compose android-camera bottom-sheet


    【解决方案1】:

    第一的CameraPreview代码中的可组合函数上,定义一个ProcessCameraProvider类型的变量,并将其分配给空值

    var cameraProvider: ProcessCameraProvider? = null
    

    然后您将定义一个DisposableEffect,密钥为cameraProvider,当它分解时,您将关闭相机

    DisposableEffect(key1 = cameraProvider) {
        onDispose {
            cameraProvider?.let { it.unbindAll() } // closes the camera
        }
    }
    

    代替你的旧代码行

    val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
    

    我们的新cameraProvider

    cameraProvider = cameraProviderFuture.get()
    

    然后在你的 try-catch 块中,因为我们使用的是空值,当需要检查它是否为空时,所以我们将使用 let

    try {
        cameraProvider?.let {
            it.unbindAll() //Make sure we only use 1 usecase related to camera
    
            val camera = it.bindToLifecycle(
                lifecycleOwner, cameraSelector, preview, imageAnalysis
            )
    
            camera.cameraControl.enableTorch(true) // TODO: Debug mode only
        }
    
    
    } catch (e: Exception) {
        Log.d("TAG", "CameraPreview: ${e.localizedMessage}")
    }
    

    完整代码:

    @Composable
    fun CameraPreview(
        modifier: Modifier = Modifier,
        onBarcodeDetected: (barcodes: List<Barcode>) -> Unit,
        onBarcodeFailed: (exception: Exception) -> Unit,
        onBarcodeNotFound: (text: String) -> Unit,
    ) {
        val context = LocalContext.current
        val lifecycleOwner = LocalLifecycleOwner.current
    
        var cameraProvider: ProcessCameraProvider? = null
        DisposableEffect(key1 = cameraProvider) {
            onDispose {
                cameraProvider?.let { it.unbindAll() }
            }
        }
    
        AndroidView(
            modifier = modifier.fillMaxSize(),
            factory = { androidViewContext -> initPreviewView(androidViewContext) },
            update = { previewView: PreviewView ->
                val cameraSelector: CameraSelector =
                    buildCameraSelector(CameraSelector.LENS_FACING_BACK)
                val cameraExecutor: ExecutorService = Executors.newSingleThreadExecutor()
                val cameraProviderFuture: ListenableFuture<ProcessCameraProvider> =
                    ProcessCameraProvider.getInstance(context)
    
    
                cameraProviderFuture.addListener({
                    cameraProvider = cameraProviderFuture.get()
    
                    val preview = buildPreview().also {
                        it.setSurfaceProvider(previewView.surfaceProvider)
                    }
    
    
                    val barcodeAnalyser = BarCodeAnalyser(
                        onBarcodeDetected = onBarcodeDetected,
                        onBarcodeFailed = onBarcodeFailed,
                        onBarCodeNotFound = onBarcodeNotFound
                    )
                    val imageAnalysis: ImageAnalysis =
                        buildImageAnalysis(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST).also {
                            it.setAnalyzer(cameraExecutor, barcodeAnalyser)
                        }
    
    
    
                    try {
                        cameraProvider?.let {
                            it.unbindAll() //Make sure we only use 1 usecase related to camera
    
                            val camera = it.bindToLifecycle(
                                lifecycleOwner, cameraSelector, preview, imageAnalysis
                            )
    
                            camera.cameraControl.enableTorch(true) // TODO: Debug mode only
                        }
    
    
                    } catch (e: Exception) {
                        Log.d("TAG", "CameraPreview: ${e.localizedMessage}")
                    }
                }, ContextCompat.getMainExecutor(context))
            }
        )
    
    
    }
    

    【讨论】:

    • 非常感谢,你启发了我的解决方案
    【解决方案2】:

    我找到了一个解决方案,当可组合项从组合中移除时,我使用 DisposableEffect 关闭相机

    首先在代码中的CameraPreview Composable function 上,定义一个ProcessCameraProvider 类型的变量,并将其赋值为空值

    var cameraProvider: ProcessCameraProvider? = null
    

    然后你将定义一个DisposableEffect,键为cameraProvider,当可组合分解时,你将关闭相机

    DisposableEffect(key1 = cameraProvider) {
        onDispose {
            cameraProvider?.let { it.unbindAll() } // closes the camera
        }
    }
    

    替换你的旧代码行

    val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
    

    用我们的新cameraProvider

    cameraProvider = cameraProviderFuture.get()
    

    然后在你的 try-catch 块中,因为我们使用的是空值,当需要检查它是否为空时,所以我们将使用 let

    try {
        cameraProvider?.let {
            it.unbindAll() //Make sure we only use 1 usecase related to camera
    
            val camera = it.bindToLifecycle(
                lifecycleOwner, cameraSelector, preview, imageAnalysis
            )
    
            camera.cameraControl.enableTorch(true) // TODO: Debug mode only
        }
    
    
    } catch (e: Exception) {
        Log.d("TAG", "CameraPreview: ${e.localizedMessage}")
    }
    

    完整代码:

    @Composable
    fun CameraPreview( 
        modifier: Modifier = Modifier,
        onBarcodeDetected: (barcodes: List<Barcode>) -> Unit,
        onBarcodeFailed: (exception: Exception) -> Unit,
        onBarcodeNotFound: (text: String) -> Unit,
    ) {
        val context = LocalContext.current
        val lifecycleOwner = LocalLifecycleOwner.current
    
        var cameraProvider: ProcessCameraProvider? = null
        DisposableEffect(key1 = cameraProvider) {
            onDispose {
                cameraProvider?.let { it.unbindAll() }
            }
        }
    
        AndroidView(
            modifier = modifier.fillMaxSize(),
            factory = { androidViewContext -> initPreviewView(androidViewContext) },
            update = { previewView: PreviewView ->
                val cameraSelector: CameraSelector =
                    buildCameraSelector(CameraSelector.LENS_FACING_BACK)
                val cameraExecutor: ExecutorService = Executors.newSingleThreadExecutor()
                val cameraProviderFuture: ListenableFuture<ProcessCameraProvider> =
                    ProcessCameraProvider.getInstance(context)
    
    
                cameraProviderFuture.addListener({
                    cameraProvider = cameraProviderFuture.get()
    
                    val preview = buildPreview().also {
                        it.setSurfaceProvider(previewView.surfaceProvider)
                    }
    
    
                    val barcodeAnalyser = BarCodeAnalyser(
                        onBarcodeDetected = onBarcodeDetected,
                        onBarcodeFailed = onBarcodeFailed,
                        onBarCodeNotFound = onBarcodeNotFound
                    )
                    val imageAnalysis: ImageAnalysis =
                        buildImageAnalysis(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST).also {
                            it.setAnalyzer(cameraExecutor, barcodeAnalyser)
                        }
    
    
    
                    try {
                        cameraProvider?.let {
                            it.unbindAll() //Make sure we only use 1 usecase related to camera
    
                            val camera = it.bindToLifecycle(
                                lifecycleOwner, cameraSelector, preview, imageAnalysis
                            )
    
                            camera.cameraControl.enableTorch(true) // TODO: Debug mode only
                        } 
    
    
                    } catch (e: Exception) {
                        Log.d("TAG", "CameraPreview: ${e.localizedMessage}")
                    }
                }, ContextCompat.getMainExecutor(context))
            }
        )
    
    
    }
    

    【讨论】:

      猜你喜欢
      • 2018-10-04
      • 1970-01-01
      • 1970-01-01
      • 2016-06-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多