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,就会导致“相机已打开,但无视图可渲染”的异常。

核心原则:若 Fragment 内仅有一个 PreviewView,且该 View 会随 Fragment 视图重建而重新创建(推荐方案),则始终使用 ViewLifecycleOwner;若你需要跨配置变更保持相机实例(如保留分析器状态),则应绑定到 Fragment.this,并手动处理 View 的重新附着。

3.3 实现流程:两种典型做法

以标准 CameraX 预览用例为例,展示两种传参模式。

3.3.1 方案一:使用 ViewLifecycleOwner(推荐)

  1. 在 onCreateView() 中 inflate 布局,获取 PreviewView。
  2. 在 onViewCreated() 中启动 CameraX 绑定,传入 viewLifecycleOwner。
  3. 无需在 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.thisviewLifecycleOwner
生命周期范围onAttach ~ onDetachonCreateView 返回非空 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 生命周期传递关系图

Fragment生命周期与ViewLifecycleOwner在CameraX绑定中的关系 Fragment.onAttach → onDetach Fragment.this 生命周期范围 onCreateView → onDestroyView ViewLifecycleOwner 生命周期范围 CameraX.bindToLifecycle(lifecycleOwner, ...) 传入不同的 lifecycleOwner 决定 CameraX 何时释放 传入 this 传入 viewLifecycleOwner 情况 A:Fragment.this • onDestroyView 后相机保持 • 需手动管理 PreviewView 重附着 • 适合跨配置变更保留分析器 情况 B:viewLifecycleOwner • onDestroyView 时自动关闭相机 • 无需手动管理 View 生命周期 • 推荐绝大多数场景 选择不当 常见错误:在 onDestroyView 后仍持有相机 → SurfaceFlinger 崩溃 / 黑屏

3.8 本章要点速查

  • 默认选择:在 onViewCreated() 中使用 viewLifecycleOwner 传递 lifecycle。
  • 特殊场景:使用 Fragment.this 时必须在 onDestroyView() 中 unbindAll(),并在 onViewCreated() 中重新绑定 surface。
  • 调试技巧:为 lifecycleOwner 添加 DefaultLifecycleObserver 打印状态日志。
  • 性能建议:避免频繁 unbindAll()/bindToLifecycle();使用 CameraControl 动态调整参数。