3. CameraX与Fragment生命周期集成:Fragment的lifecycle参数传递、使用ViewLifecycleOwner与Fragment生命周期差异
3.1 本章聚焦
Fragment 的生命周期比 Activity 更复杂——它既有 Fragment 自身的生命周期(onCreate、onStart、onResume…),又有 Fragment 内 View 的生命周期(onCreateView、onDestroyView)。当使用 CameraX 的 ProcessCameraProvider.bindToLifecycle() 时,传入的 LifecycleOwner 决定了相机实例何时开始预览、何时停止、何时销毁。选择错误的 LifecycleOwner 会导致相机提前释放、黑屏、内存泄漏甚至崩溃。
本章目标:掌握在 Fragment 中正确传递 lifecycle 参数的三种模式,理解 ViewLifecycleOwner 与 Fragment 自身生命周期差异的现场规则,并通过实际代码避免常见的集成陷阱。
3.2 原理:两个生命周期,一个绑定点
CameraX 通过 Lifecycle 感知组件状态变化。绑定到某个 LifecycleOwner 后,CameraX 自动执行以下动作:
| Lifecycle 状态 | CameraX 行为 |
|---|---|
ON_CREATE | 注册内部回调,准备资源 |
ON_START | 打开相机设备 |
ON_RESUME | 启动预览流/图像分析 |
ON_PAUSE | 停止预览流,关闭图像分析 |
ON_STOP | 关闭相机设备 |
ON_DESTROY | 释放所有 CameraX 内部资源 |
Fragment 拥有两个相互独立但有交集的 LifecycleOwner:
- Fragment 自身:生命周期范围从
onAttach()到onDetach(),覆盖整个 Fragment 实例存活期。 - Fragment 的 View 生命周期(ViewLifecycleOwner):生命周期范围从
onCreateView()返回非空 View 开始,到onDestroyView()结束时终止。每次视图重建(例如横竖屏切换、被 FragmentManager 移除后重新添加)都会触发一个新的ViewLifecycleOwner实例。
关键差异:当 Fragment 进入 onDestroyView() 但尚未 onDetach() 时,Fragment.this 仍处于 CREATED 状态(不会进入 DESTROYED),而 ViewLifecycleOwner 已进入 DESTROYED。如果 CameraX 绑定到 Fragment.this,在视图销毁后相机设备仍然存活,可以跨视图重建保持预览流——但如果你在 onDestroyView() 中解绑了 PreviewView,就会导致“相机已打开,但无视图可渲染”的异常。
PreviewView,且该 View 会随 Fragment 视图重建而重新创建(推荐方案),则始终使用 ViewLifecycleOwner;若你需要跨配置变更保持相机实例(如保留分析器状态),则应绑定到 Fragment.this,并手动处理 View 的重新附着。
3.3 实现流程:两种典型做法
以标准 CameraX 预览用例为例,展示两种传参模式。
3.3.1 方案一:使用 ViewLifecycleOwner(推荐)
- 在
onCreateView()中 inflate 布局,获取PreviewView。 - 在
onViewCreated()中启动 CameraX 绑定,传入viewLifecycleOwner。 - 无需在
onDestroyView()中手动解绑,CameraX 随ViewLifecycleOwner自动进入DESTROYED而关闭。
class CameraFragment : Fragment() {
private lateinit var previewView: PreviewView
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_camera, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
previewView = view.findViewById(R.id.previewView)
startCamera()
}
private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder().build().also {
it.setSurfaceProvider(previewView.surfaceProvider)
}
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
cameraProvider.unbindAll()
// 关键:传入 viewLifecycleOwner
cameraProvider.bindToLifecycle(
viewLifecycleOwner, cameraSelector, preview
)
} catch (e: Exception) {
Log.e(TAG, "Camera binding failed", e)
}
}, ContextCompat.getMainExecutor(requireContext()))
}
}
3.3.2 方案二:使用 this(Fragment 自身生命周期)
onDestroyView() 中手动解除 PreviewView 的 surfaceProvider,并在 onCreateView() 重建后重新设置,否则会触发 Surface has been released 错误。
class PersistentCameraFragment : Fragment() {
private lateinit var previewView: PreviewView
private var cameraProvider: ProcessCameraProvider? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_camera, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
previewView = view.findViewById(R.id.previewView)
// 如果相机已绑定,只需重新设置 surfaceProvider
if (cameraProvider != null) {
reattachSurfaceProvider()
} else {
startCamera()
}
}
private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
cameraProviderFuture.addListener({
cameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder().build().also {
it.setSurfaceProvider(previewView.surfaceProvider)
}
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
cameraProvider?.unbindAll()
// 使用 Fragment 自身的生命周期
cameraProvider?.bindToLifecycle(
this, cameraSelector, preview // this 指 Fragment
)
} catch (e: Exception) {
Log.e(TAG, "Camera binding failed", e)
}
}, ContextCompat.getMainExecutor(requireContext()))
}
override fun onDestroyView() {
super.onDestroyView()
// 必须主动移除 surfaceProvider,防止绑定已销毁的 Surface
cameraProvider?.let { provider ->
// 通过 unbind 所有 UseCase 来停止相机
provider.unbindAll()
}
}
private fun reattachSurfaceProvider() {
// 重新绑定新的 PreviewView
val preview = Preview.Builder().build().also {
it.setSurfaceProvider(previewView.surfaceProvider)
}
cameraProvider?.bindToLifecycle(this, CameraSelector.DEFAULT_BACK_CAMERA, preview)
}
}
3.4 概念对比:Fragment自身 vs ViewLifecycleOwner
| 维度 | Fragment.this | viewLifecycleOwner |
|---|---|---|
| 生命周期范围 | onAttach ~ onDetach | onCreateView 返回非空 View ~ onDestroyView |
| 在 onDestroyView 后的状态 | CREATED(存活) | DESTROYED |
| 横竖屏切换时是否重建相机 | 不会(相机实例保持) | 会(自动关闭再打开) |
| 焦点切换(如跳转其他 Fragment) | 暂停但未销毁 | 触发 DESTROYED → 相机释放 |
| 适合场景 | 需跨配置变更保留分析数据、音频流 | 标准预览、拍照、图像分析(推荐 99% 场景) |
3.5 调试与常见错误
3.5.1 IllegalStateException: Camera is not bound yet
原因:在 CameraX 完成绑定前试图操作 UseCase(如 setSurfaceProvider)。确保所有 CameraX 方法在 ListenableFuture 的回调中执行。
3.5.2 Surface has been released / already destroyed
场景:绑定到 Fragment.this 且没有在 onDestroyView() 中解绑,导致之前的 PreviewView 的 Surface 已销毁,但 CameraX 仍尝试向它推送帧。
- 解决方案:在
onDestroyView()中调用cameraProvider.unbindAll(),或重新绑定新的PreviewView的 surfaceProvider。
3.5.3 CameraFragment not attached to a base context
原因:在 Fragment 尚未附加到 Activity 时调用 requireContext()。使用 viewLifecycleOwner 时,onViewCreated 已保证 Fragment 处于 onAttach 后的状态,因此很少出现;若使用 Fragment.this 并延迟初始化,需检查 isAdded 标志。
3.5.4 生命周期日志参考
在开发阶段打印 Fragment 和 ViewLifecycleOwner 的状态变化,快速定位问题:
lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
Log.d(TAG, "LifecycleOwner ON_START")
}
override fun onDestroy(owner: LifecycleOwner) {
Log.d(TAG, "LifecycleOwner ON_DESTROY")
}
})
3.6 性能与稳定性优化
- 避免不必要的 unbindAll:每次
bindToLifecycle都会触发重新配置相机硬件。若只需切换分析器配置,应使用camera.cameraControl动态更新,而非重新绑定。 - 使用 ViewLifecycleOwner 减少生命周期冲突:在
FragmentPagerAdapter或ViewPager2中,Fragment 可能被保留但视图被销毁。绑定到viewLifecycleOwner可以自动适应视图销毁/重建,避免CameraX在无 View 时仍持有硬件。 - 在
onViewCreated中延迟初始化 CameraX:避免在onCreateView中绑定,因为此时PreviewView可能尚未完成布局,surfaceProvider可能不可用。 - 使用
camx.debug.lifecycle标志:在 AndroidManifest 的application标签中添加android:name="androidx.camera.camera2"的 meta-data 来启用内部日志(具体参考 CameraX 文档)。
3.7 生命周期传递关系图
3.8 本章要点速查
- 默认选择:在
onViewCreated()中使用viewLifecycleOwner传递 lifecycle。 - 特殊场景:使用
Fragment.this时必须在onDestroyView()中unbindAll(),并在onViewCreated()中重新绑定 surface。 - 调试技巧:为 lifecycleOwner 添加
DefaultLifecycleObserver打印状态日志。 - 性能建议:避免频繁
unbindAll()/bindToLifecycle();使用CameraControl动态调整参数。