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

CameraX:绑定LifecycleService后Activity预览停止如何解决?

问题原因

CameraX 的 ProcessCameraProvider 同一时间只能将同一个相机实例绑定到一个 LifecycleOwner。你当前分别将相机绑定到了 Activity 和 LifecycleService 两个不同的生命周期持有者,Service 的绑定操作会直接覆盖 Activity 的绑定,导致预览停止。

解决方案

根据你的需求,有两种可行的实现方式:

方式一:共享相机用例(推荐)

将 Preview 和 ImageCapture 同时绑定到 Activity 的生命周期,后台 Service 直接调用已绑定的 ImageCapture 实例完成捕获,避免重复绑定相机。

步骤1:Activity 中同时绑定预览和捕获用例

// 全局持有 ImageCapture 实例,用于给 Service 调用
private val imageCapture = ImageCapture.Builder()
    .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
    .build()

private fun bindCamera() {
    processCameraProvider.await().run {
        try {
            unbindAll()
            // 同时绑定 Preview 和 ImageCapture 到 Activity 生命周期
            bindToLifecycle(
                this@MainActivity,
                CameraSelector.DEFAULT_BACK_CAMERA,
                Preview.Builder().build().also {
                    it.setSurfaceProvider(binding.camera.surfaceProvider)
                },
                imageCapture
            )
            // 将 ImageCapture 存入单例,供 Service 获取
            CameraSharedHolder.instance.setImageCapture(imageCapture)
        } catch (e: Exception) {
            Timber.w(e)
        }
    }
    // 启动后台服务
    ContextCompat.startForegroundService(this, Intent(this, CaptureService::class.java))
}

// 单例类,用于跨组件共享 ImageCapture
object CameraSharedHolder {
    private var imageCapture: ImageCapture? = null

    fun setImageCapture(capture: ImageCapture) {
        imageCapture = capture
    }

    fun getImageCapture(): ImageCapture? = imageCapture
}

步骤2:Service 中直接调用捕获

不需要再绑定相机,直接从单例获取 ImageCapture 执行捕获操作:

class CaptureService : LifecycleService() {
    private val captureExecutor = ContextCompat.getMainExecutor(this)

    override fun onCreate() {
        super.onCreate()
        startForeground(1, createForegroundNotification())
        // 启动定时捕获任务(示例用 Handler 模拟)
        val handler = Handler(Looper.getMainLooper())
        handler.postDelayed(object : Runnable {
            override fun run() {
                takePicture()
                handler.postDelayed(this, 5000) // 每5秒捕获一次
            }
        }, 5000)
    }

    private fun takePicture() {
        CameraSharedHolder.getImageCapture()?.let { capture ->
            val outputFile = File(externalCacheDir, "${System.currentTimeMillis()}.jpg")
            val outputOptions = ImageCapture.OutputFileOptions.Builder(outputFile).build()
            
            capture.takePicture(
                outputOptions,
                captureExecutor,
                object : ImageCapture.OnImageSavedCallback {
                    override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
                        // 处理图片保存成功逻辑
                    }

                    override fun onError(exception: ImageCaptureException) {
                        Timber.w(exception, "捕获图片失败")
                    }
                }
            )
        }
    }

    private fun createForegroundNotification(): Notification {
        // 创建前台通知(必须实现,否则 Service 会被系统杀死)
        return NotificationCompat.Builder(this, "camera_capture_channel")
            .setContentTitle("后台捕获中")
            .setSmallIcon(R.drawable.ic_notification)
            .build()
    }
}

方式二:生命周期切换绑定

如果需要在 Activity 退到后台时,由 Service 接管相机捕获,可以通过生命周期回调切换相机绑定对象:

步骤1:Activity 中处理生命周期切换

override fun onResume() {
    super.onResume()
    // 通知 Service 解绑相机,Activity 重新绑定预览+捕获
    CaptureService.unbindCamera()
    bindCameraToActivity()
}

override fun onPause() {
    super.onPause()
    // Activity 退后台时解绑相机,让 Service 绑定
    processCameraProvider.await().unbindAll()
}

private fun bindCameraToActivity() {
    processCameraProvider.await().run {
        try {
            unbindAll()
            bindToLifecycle(
                this@MainActivity,
                CameraSelector.DEFAULT_BACK_CAMERA,
                Preview.Builder().build().also {
                    it.setSurfaceProvider(binding.camera.surfaceProvider)
                },
                imageCapture
            )
        } catch (e: Exception) {
            Timber.w(e)
        }
    }
}

步骤2:Service 中处理相机绑定

class CaptureService : LifecycleService() {
    companion object {
        fun unbindCamera() {
            runCatching {
                ProcessCameraProvider.getInstance(this@CaptureService).get().unbindAll()
            }
        }
    }

    private val imageCapture = ImageCapture.Builder().build()

    override fun onCreate() {
        super.onCreate()
        startForeground(1, createForegroundNotification())
        bindCameraToService()
    }

    private fun bindCameraToService() {
        processCameraProvider.await().run {
            try {
                unbindAll()
                bindToLifecycle(
                    this@CaptureService,
                    CameraSelector.DEFAULT_BACK_CAMERA,
                    imageCapture
                )
                // 启动定时捕获逻辑
            } catch (e: Exception) {
                Timber.w(e)
            }
        }
    }

    // 省略通知创建和捕获逻辑...
}

内容的提问来源于stack exchange,提问作者Pitel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 00:40:34