第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已经销毁。
判断规则(初级筛选)
- 检查日志中是否包含“LifecycleOwner”或“onResume”相关异常。
- 检查崩溃堆栈是否涉及
CameraX.bindToLifecycle或PreviewView。 - 确认崩溃是否在屏幕旋转、Fragment切换、快速按下Home键恢复时出现。
3.2 数据采集
精确定位生命周期异常需要采集以下数据:
| 数据项 | 采集方式 | 参考样本 |
|---|---|---|
| 完整堆栈 | Logcat + 日志上报 | |
| 生命周期回调时序 | 在Activity/Fragment关键方法中添加日志 | |
| CameraX绑定日志 | 启用CameraX内部日志 | |
| Fragment状态丢失 | 检查FragmentManager日志 | |
最佳实践:在开发阶段开启CameraX调试日志,并确保所有生命周期回调都打印唯一标签,便于事后回溯时序。
3.3 原因定位
生命周期异常的根本原因可抽象为一张故障树(Fault Tree),我们通过以下SVG展示核心分支:
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 预防
- 使用
camerax-lifecycle组件:统一通过ProcessCameraProvider.bindToLifecycle管理,不要手动管理Camera生命周期。 - 避免在
onCreate中绑定:始终在onResume或onStart后且View就绪时绑定。 - 建立生命周期监控:在开发阶段输出所有生命周期回调日志,形成时序图表。
- 禁止在非UI线程操作Lifecycle:CameraX 的 bind/unbind 必须在主线程中执行。
- 使用
ViewModel + LiveData模式:将CameraProvider作为应用级单例缓存,避免因Activity重建重复获取。
每日检查规则(团队实践)
- 每个Camera相关Activity/Fragment的
onResume中增加previewView.post(()->bindCamera())。 - 每个Camera Fragment的
onDestroyView中调用cameraProvider.unbindAll()。 - CI流水线中增加旋转、Fragment切换的UI自动化测试。