2. CameraX 与 Activity 生命周期集成:各阶段相机行为

CameraX 的核心优势之一是其内建的生命周期感知能力。通过将 CameraX 绑定到 Activity(或 Fragment)的 LifecycleOwner,相机资源的创建、激活、暂停和释放由库自动管理。然而,开发者仍需在特定生命周期回调中实施正确的初始化与清理逻辑,以避免内存泄漏、预览冻结或“相机已被占用”等运行时错误。

2.1 生命周期与相机行为对应关系

下表列出了标准 Activity 生命周期回调中 CameraX 的推荐行为和潜在风险。

生命周期回调 CameraX 行为 注意事项
onCreate() 创建 ProcessCameraProvider 实例,配置 Preview、ImageCapture、ImageAnalysis 用例,但不绑定到生命周期。 避免在此时绑定,因为 Activity 尚未进入前台,过早绑定可能导致 Surface 无效。
onStart() 可选:预打开相机硬件或预热 CameraXConfig。一般无需额外操作,CameraX 内部会在此阶段准备内部资源。 不要在此处重复创建 Provider,否则可能触发 CameraProvider 多次初始化。
onResume() 执行绑定:通过 cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, *useCases) 将用例绑定到当前 LifecycleOwner。CameraX 自动激活相机流。
此时 PreviewView 的 Surface 已可用。
确保 bindToLifecycle 只调用一次,或在每次 onResume 时重新绑定(若 onPause 中已解绑)。推荐在 onResume 中绑定,在 onPause 中解绑。
onPause() 解绑用例:调用 cameraProvider.unbindAll() 或 cameraProvider.unbind(*useCases) 以释放相机硬件和内存资源。 解绑后 CameraX 会停止所有用例,关闭相机。若不解绑,当 Activity 进入后台时相机仍被占用,可能导致其他应用无法使用相机。
onStop() CameraX 内部自动将 Lifecycle 状态置为 STOPPED,所有绑定到该 LifecycleOwner 的用例被暂停。无需额外代码。 若在 onPause 中已解绑,此处无需重复操作。若未解绑,此处 CameraX 会安全挂起,但资源未完全释放。
onDestroy() 释放 ProcessCameraProvider 引用,清理监听器。如果 Provider 由 CameraX.bindToLifecycle 管理,通常不需要手动关闭。但若有自定义 CameraXConfig,需在此处 shutdown。 避免持有 Camera 对象(例如 CameraX.camera)的强引用,防止 Activity 泄漏。

2.2 核心实现流程

推荐的绑定/解绑模式是 "Resume-Bind / Pause-Unbind"。流程图如下:

Activity 生命周期与 CameraX 绑定/解绑流程 onCreate() 创建 Provider & 配置用例 onStart() 预热(可选) onResume() bindToLifecycle onPause() unbindAll() onStop() 状态暂停(自动) onDestroy() 释放 Provider 引用 再次回到前台 说明 • 灰色虚线表示“后台 -> 前台”再次触发 onStart/onResume 的循环。 • 推荐在 onResume 中调用 bindToLifecycle,在 onPause 中调用 unbindAll。 • CameraX 内部自动处理 onStop 暂停,但 onPause 解绑可更早释放硬件资源。 • 若使用 ProcessCameraProvider.getInstance(this),需确保只调用一次,可通过生命周期范围管理。

2.3 核心 API 与配置

关键 API 调用点如下:

API 用途 调用时机
ProcessCameraProvider.getInstance(context) 异步获取 CameraX Provider 实例。 onCreate 或更早(应用初始化时)。
cameraProvider.bindToLifecycle(lifecycleOwner, selector, useCases) 将用例绑定到 LifecycleOwner,生命周期跟随变化。 onResume。
cameraProvider.unbindAll() 解除所有绑定,释放相机。 onPause。
cameraProvider.unbind(*useCases) 解除指定用例。 需动态替换用例时(如切换摄像头)。

2.4 典型集成代码(Kotlin + Activity)

class CameraActivity : AppCompatActivity() {
    private lateinit var cameraProvider: ProcessCameraProvider
    private lateinit var previewView: PreviewView
    private var camera: Camera? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_camera)
        previewView = findViewById(R.id.previewView)

        // 1. 异步获取 CameraX Provider(仅在 onResume 前准备好即可)
        cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        cameraProviderFuture.addListener(Runnable {
            cameraProvider = cameraProviderFuture.get()
        }, ContextCompat.getMainExecutor(this))
    }

    override fun onResume() {
        super.onResume()
        // 2. 确保 Provider 已就绪 & PreviewView Surface 已创建
        if (::cameraProvider.isInitialized) {
            bindCamera()
        }
    }

    override fun onPause() {
        super.onPause()
        // 3. 解绑所有用例,释放相机硬件
        if (::cameraProvider.isInitialized) {
            cameraProvider.unbindAll()
            camera = null
        }
    }

    private fun bindCamera() {
        val preview = Preview.Builder().build()
        val imageCapture = ImageCapture.Builder().build()

        // 将 Preview 与 PreviewView 的表面提供器关联
        preview.surfaceProvider = previewView.surfaceProvider

        camera = cameraProvider.bindToLifecycle(
            this,                 // LifecycleOwner
            CameraSelector.DEFAULT_BACK_CAMERA,
            preview,
            imageCapture
        )
    }
}

2.5 调试与验证

开启 CameraX 日志可观察生命周期状态变化:

// 在 Application 中启用详细日志
CameraX.setLogLevel(Log.VERBOSE)

运行 App 后,观察 Logcat 过滤 CameraX 或 Lifecycle 标签:

D/CameraXLifecycle: Lifecycle status changed to ON_START
D/CameraXLifecycle: Lifecycle status changed to ON_RESUME
D/CameraX: Active use case: Preview, ImageCapture
D/CameraXLifecycle: Lifecycle status changed to ON_PAUSE
D/CameraXLifecycle: Unbinding all use cases
D/CameraXLifecycle: Lifecycle status changed to ON_STOP

通过日志确认绑定与解绑的配对:每次 ON_RESUME 后应有 Active use case 输出;ON_PAUSE 后应有 Unbinding all use cases。若出现多次绑定未解绑,日志中可能显示 Camera is already bound 警告。

2.6 常见错误与避坑

错误1:在 onCreate 中过早绑定。

后果:PreviewView 的 Surface 尚未准备好,导致预览黑屏或抛出 Surface not yet available 异常。

修正:将绑定推迟到 onResume,并确保 PreviewView 已通过 View.post() 或 setOnAttachStateChangeListener 确认 Surface 可用。

错误2:忘记在 onPause 中解绑。

后果:Activity 进入后台后相机硬件被持续占用,其他应用无法访问相机,系统可能强制关闭您的应用。

修正:在 onPause 中无条件调用 cameraProvider.unbindAll()。

错误3:屏幕旋转导致重复绑定。

现象:旋转屏幕时 Activity 重建,onCreate 再次获取 Provider,onResume 再次绑定,而旧 Provider 未被释放。

修正:在 onDestroy 中调用 ProcessCameraProvider.getInstance(this).get().unbindAll()(注意线程安全),或使用 lifecycle.addObserver 统一管理。推荐在清单中配置 android:configChanges="orientation|screenSize" 避免重建(仅针对简单场景)。

2.7 性能与稳定性优化

  • 延迟 Provider 初始化:在 onCreate 中触发异步获取,但不要在 onCreate 内直接 .get() 阻塞主线程。
  • 复用 Provider:整个 Activity 生命周期内只调用一次 ProcessCameraProvider.getInstance(),避免每次绑定都创建新实例。
  • 使用 Lifecycle 观察者模式:通过 lifecycle.addObserver 替代手动回调,让 CameraX 完全接管生命周期转换,减少遗漏。
  • 减少不必要的 unbind/bind 次数:若只是切换前后摄像头,使用 cameraProvider.unbind(currentUseCases) 后重新绑定新 CameraSelector,而非 unbindAll。
  • 内存泄漏检测:在 onDestroy 中将 cameraProvider 引用置为 null,并确保 PreviewView 不再持有 Activity 的强引用(若使用 Fragment 委托生命周期)。