Android Kotlin权限启动器在SurfaceView未就绪时触发过快,导致扫码器首次启动黑屏
Android Kotlin权限启动器在SurfaceView未就绪时触发过快,导致扫码器首次启动黑屏
嘿,我来帮你搞定这个首次启动黑屏的问题!核心原因其实很简单:你在权限授权通过后立刻启动相机,但这时候PreviewView的Surface还没完全准备好,相机绑定的时候找不到可用的Surface,自然就显示黑屏了;第二次进入Fragment时,Surface已经初始化完成,所以相机能正常工作。
下面是具体的问题分析和修复步骤:
问题到底出在哪?
当你在onViewCreated里调用checkAndRequestCameraPermission(),哪怕用了view.post,权限请求是异步的——用户点授权的速度可能比PreviewView完成布局、创建Surface的速度快。这时候startCamera()被调用,但相机绑定到生命周期时,Surface还没就绪,相机没法输出画面,就会黑屏。第二次进入时,Surface已经存在,所以相机一绑定就正常启动了。
修复方案:等Surface就绪再搞权限和相机
我们要调整逻辑,必须等PreviewView完全布局好、Surface创建完成后,再去检查权限和启动相机,不能操之过急。
1. 调整权限检查的触发时机
把原来在onViewCreated里直接调用checkAndRequestCameraPermission()的逻辑,改成监听PreviewView的布局完成事件,确保Surface已经准备好:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) previewView = binding.scannerPreviewView scannerProgressBar = binding.scannerProgressBar scannerTextView = binding.scannerTextView previewView.previewStreamState.observe(viewLifecycleOwner) { state -> when (state) { PreviewView.StreamState.IDLE -> { scannerProgressBar.visibility = View.VISIBLE scannerTextView.visibility = View.GONE } PreviewView.StreamState.STREAMING -> { scannerProgressBar.visibility = View.GONE scannerTextView.visibility = View.VISIBLE } } } // 监听PreviewView布局完成,Surface就绪后再处理权限 previewView.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { override fun onGlobalLayout() { // 移除监听,避免重复触发 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { previewView.viewTreeObserver.removeOnGlobalLayoutListener(this) } else { @Suppress("DEPRECATION") previewView.viewTreeObserver.removeGlobalOnLayoutListener(this) } // 现在Surface肯定就绪了,检查权限 checkAndRequestCameraPermission() } }) }
2. 优化相机绑定的容错逻辑
在startCamera()里加个异常捕获,方便后续调试,同时确保CameraProvider完全初始化后再绑定用例:
private fun startCamera() { scannerProgressBar.visibility = View.VISIBLE val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext()) cameraProviderFuture.addListener({ cameraProvider = cameraProviderFuture.get() val previewUseCase = Preview.Builder() .build() .also { it.setSurfaceProvider(previewView.surfaceProvider) } analysisUseCase = ImageAnalysis.Builder() .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888) .build() .also { useCase -> useCase.setAnalyzer(cameraExecutor) { imageProxy -> processImageProxy(imageProxy) } } val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA try { cameraProvider.unbindAll() cameraProvider.bindToLifecycle( viewLifecycleOwner, cameraSelector, previewUseCase, analysisUseCase ) } catch(exc: Exception) { Log.e("QrScanner", "相机绑定失败:", exc) } }, ContextCompat.getMainExecutor(requireContext())) }
3. 最后检查几个细节
- 确认Manifest里的相机权限没写错:
<uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" android:required="false" /> registerForActivityResult的时机是对的,你作为成员变量初始化没问题,因为它会在Fragment的onCreate阶段完成注册。- 确保
viewLifecycleOwner正确传递给bindToLifecycle,你现在的写法是对的,用Fragment的View生命周期比Fragment本身更准确。
这样修改后,首次进入Fragment时,Surface已经完全就绪,权限通过后启动相机就能正常渲染画面,不会再出现黑屏的情况了!
内容来源于stack exchange
相关产品推荐
相关产品推荐

