第3章:生命周期异常导致的崩溃——onCreate/onResume时序错误、Activity重建、Fragment状态丢失

3.1 现象识别

生命周期异常导致的CameraX崩溃通常表现为以下几种典型场景,开发者应在第一时间识别:

  • 应用启动后闪退 —— 控制台输出 java.lang.NullPointerException,指向 CameraX.bindToLifecycle 或 PreviewView.getSurfaceProvider。
  • 设备旋转后崩溃 —— 屏幕旋转触发Activity重建,CameraX绑定状态丢失,提示 CameraInfoUnavailableException 或 IllegalStateException: Camera is not initialized。
  • Fragment切换/回退时崩溃 —— 调用 ProcessCameraProvider.getInstance 时抛出 IllegalStateException: LifecycleOwner is already stopped。
  • 后台切回前台黑屏 —— CameraX生命周期未正确恢复,预览SurfaceProvider已经销毁。
判断规则(初级筛选)
  1. 检查日志中是否包含“LifecycleOwner”或“onResume”相关异常。
  2. 检查崩溃堆栈是否涉及 CameraX.bindToLifecycle 或 PreviewView。
  3. 确认崩溃是否在屏幕旋转、Fragment切换、快速按下Home键恢复时出现。

3.2 数据采集

精确定位生命周期异常需要采集以下数据:

数据项采集方式参考样本
完整堆栈Logcat + 日志上报
FATAL EXCEPTION: main
Process: com.example.camera, PID: 1234
java.lang.IllegalStateException: Cannot process camera request because LifecycleOwner is in state DESTROYED.
    at androidx.camera.core.CameraX.bindToLifecycle(CameraX.java:285)
生命周期回调时序在Activity/Fragment关键方法中添加日志
Log.d("LifecycleTest", "onCreate called");
Log.d("LifecycleTest", "onResume called");
Log.d("LifecycleTest", "onPause called");
Log.d("LifecycleTest", "onDestroy called");
CameraX绑定日志启用CameraX内部日志
adb shell setprop log.tag.CameraX DEBUG
adb logcat -s CameraX:V
Fragment状态丢失检查FragmentManager日志
adb logcat -s FragmentManager:V
最佳实践:在开发阶段开启CameraX调试日志,并确保所有生命周期回调都打印唯一标签,便于事后回溯时序。

3.3 原因定位

生命周期异常的根本原因可抽象为一张故障树(Fault Tree),我们通过以下SVG展示核心分支:

生命周期异常崩溃故障树 CameraX 生命周期异常崩溃 onCreate/onResume 时序错误 Activity重建 生命周期丢失 Fragment状态丢失 状态未保存 CameraX在onCreate中绑定 而View尚未准备好 onResume重复绑定 导致双重注册 Configuration改变 默认销毁重建 未处理onSaveInstanceState CameraX状态丢失 Fragment嵌套场景 父Fragment回退时 未调用setRetainInstance 或未保存Provider引用

3.4 修复动作

3.4.1 修复onCreate/onResume时序错误

规则:CameraX 的 bindToLifecycle 调用必须发生在 LifecycleOwner(如Activity或Fragment)的 onStart 之后,且 PreviewView 已经完成测量。推荐在 onCreate 中只获取 ProcessCameraProvider,在 onResume 的 onResumeFragments 或 View.post 中执行绑定。

// 错误:在onCreate中直接绑定
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_camera);
    previewView = findViewById(R.id.previewView);
    // ❌ 此时PreviewView可能尚未布局,Lifecycle处于CREATED状态
    bindCamera(); // 可能崩溃
}

// 正确:延迟到onResume,并确保View已就绪
@Override
protected void onResume() {
    super.onResume();
    previewView.post(() -> {
        if (isResumed()) {
            bindCamera();
        }
    });
}
重要:在 onPause 中务必解绑 CameraX 或释放预览,避免后台占用相机导致其他应用崩溃。可使用 lifecycleScope 或 cameraProvider.unbindAll()。

3.4.2 修复Activity重建导致的生命周期丢失

方法:通过 manifest 中配置 android:configChanges,避免不必要的Activity重建;若必须重建,则必须在 onCreate 中恢复Camera状态。

<!-- AndroidManifest.xml 配置 -->
<activity
    android:name=".CameraActivity"
    android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
    android:screenOrientation="sensorPortrait"
    android:windowSoftInputMode="adjustResize"/>

如果无法避免重建,则使用 ViewModel 持有CameraProvider引用,使Activity重建时仍能复用:

public class CameraViewModel extends ViewModel {
    private ProcessCameraProvider cameraProvider;

    public void setCameraProvider(ProcessCameraProvider provider) {
        this.cameraProvider = provider;
    }

    public ProcessCameraProvider getCameraProvider() {
        return cameraProvider;
    }
}

// 在Activity中
CameraViewModel viewModel = new ViewModelProvider(this).get(CameraViewModel.class);
ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this);
cameraProviderFuture.addListener(() -> {
    try {
        ProcessCameraProvider provider = cameraProviderFuture.get();
        viewModel.setCameraProvider(provider);
        bindCamera(provider);
    } catch (ExecutionException | InterruptedException e) {
        // 处理异常
    }
}, ContextCompat.getMainExecutor(this));

3.4.3 修复Fragment状态丢失

核心原则:Fragment的生命周期独立,嵌套Fragment时,子Fragment的 LifecycleOwner 状态可能因父Fragment回退而提前变为 DESTROYED。修复措施包括:

  • 在 Fragment.onDestroyView 中调用 cameraProvider.unbindAll()。
  • 使用 setRetainInstance(true) 保留Fragment(不推荐混合CameraX,因为retain instance可能导致陈旧引用)。
  • 推荐统一使用 ViewModel 持有Camera状态,Fragment仅作为View层。
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    // 仅在Fragment可见时绑定
    if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
        startCamera();
    }
}

@Override
public void onDestroyView() {
    super.onDestroyView();
    if (cameraProvider != null) {
        cameraProvider.unbindAll(); // ✅ 主动解绑
    }
}

// 注意:不要依赖Fragment自动解绑,显式调用更安全

3.5 复测

修复完成后,必须执行以下复测清单:

测试项操作步骤预期结果
快速旋转屏幕连续旋转设备10次以上预览不中断,无异常日志
Fragment压栈/弹栈快速添加-回退多个Camera Fragment每个Fragment都能成功打开相机并释放
Home键切换按Home → 切回应用 (重复5次)预览恢复,不黑屏
紧凑时序压力通过adb模拟快速onPause-onResume循环无IllegalStateException
回退键在Camera Fragment中按返回相机立即释放,无ANR
自动化辅助:可使用 ActivityScenario 和 FragmentScenario 在单元测试中模拟生命周期变化,验证绑定是否正常。

3.6 预防

  1. 使用 camerax-lifecycle 组件:统一通过 ProcessCameraProvider.bindToLifecycle 管理,不要手动管理Camera生命周期。
  2. 避免在 onCreate 中绑定:始终在 onResume 或 onStart 后且View就绪时绑定。
  3. 建立生命周期监控:在开发阶段输出所有生命周期回调日志,形成时序图表。
  4. 禁止在非UI线程操作Lifecycle:CameraX 的 bind/unbind 必须在主线程中执行。
  5. 使用 ViewModel + LiveData 模式:将CameraProvider作为应用级单例缓存,避免因Activity重建重复获取。
每日检查规则(团队实践)
  • 每个Camera相关Activity/Fragment的 onResume 中增加 previewView.post(()->bindCamera())。
  • 每个Camera Fragment的 onDestroyView 中调用 cameraProvider.unbindAll()。
  • CI流水线中增加旋转、Fragment切换的UI自动化测试。