You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 08:14:51