You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter应用拍照时随机崩溃问题排查求助

问题描述

我正在开发一款包含拍照并上传至后端的Flutter应用,整体功能正常。为确保向服务器传输数据的安全性,我实现了App从Paused/Exited状态切换至Resumed状态时验证连接token有效性的逻辑。

但在三星S20 FE(Android 12)设备上测试时,拍照会随机导致应用崩溃,Android Studio捕获到的错误信息如下:

[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Error During Communication: Error occured while Communication with Server with StatusCode: 404

奇怪的是,我们仅在按下按钮后才会上传图片,而崩溃却发生在拍照完成瞬间。我怀疑是生命周期状态检测代码的问题——该代码会调用后端验证token,且已用.then和.onError包裹,理论上应能捕获网络错误。

这个问题在OnePlus 6(Android 11)及模拟器上均无法复现。以下是相关功能代码,求问问题原因及修复方案:

App生命周期函数

@override
Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
  print("State changed to: $state");
  switch(state){
    case AppLifecycleState.resumed:
      final LoginManager loginManager = locator<LoginManager>();
      final userToken = await loginManager.getUserTokenFromStorage();
      if (userToken != null) {
        final Future<LoginResponse?> loginResponse = locator<LoginManager>().verifyToken(refreshToken);
        loginResponse.then((value) {
          if(value == null) {
            _showTimeoutDialog(navigatorKey.currentContext!);
          }
        }).onError((error, stackTrace) {
          _showTimeoutDialog(navigatorKey.currentContext!);
        });
      }
      break;
    case AppLifecycleState.inactive:
      // TODO: Handle this case.
      break;
    case AppLifecycleState.paused:
      // TODO: Handle this case.
      break;
    case AppLifecycleState.detached:
      // TODO: Handle this case.
      break;
  }
}

注:showTimeoutDialog用于显示弹窗并正常断开用户连接,不会传输任何信息。

拍照函数

Future<File?> _getPhotoFromCamera(bool isSelfie) async {
  return ImagePicker()
      .pickImage(
    source: ImageSource.camera,
    preferredCameraDevice:
    isSelfie ? CameraDevice.front : CameraDevice.rear,
  )
      .then((value) => value != null ? File(value.path) : null);
}

使用flutter image_picker包。


问题原因及修复方案

核心原因

  1. Android 12生命周期行为差异:三星S20 FE运行Android 12,调用系统相机时,App可能被系统彻底暂停甚至销毁(而非仅进入inactive/paused状态)。拍照完成返回App触发resumed状态时,navigatorKey.currentContext可能尚未完全初始化,直接用!强制非空会导致上下文为空,进而引发未捕获异常——你看到的404是网络请求结果,但真正崩溃的原因是后续调用弹窗时上下文为空,只是异常栈被网络错误覆盖。
  2. 未完全捕获的异步异常:verifyToken内部可能存在未被捕获的异常,或者refreshToken未正确初始化(比如为null),导致请求URL错误返回404,且该异常未被链式调用完全捕获。

修复步骤

1. 确保上下文非空再调用弹窗

修改resumed状态逻辑,先判断上下文是否就绪,避免强制非空:

case AppLifecycleState.resumed:
  final LoginManager loginManager = locator<LoginManager>();
  final userToken = await loginManager.getUserTokenFromStorage();
  final context = navigatorKey.currentContext;
  if (userToken != null) {
    if (context == null) {
      // 上下文未就绪,延迟重试
      Future.delayed(const Duration(milliseconds: 500), () {
        _verifyTokenAndShowDialog();
      });
      break;
    }
    locator<LoginManager>().verifyToken(refreshToken)
        .then((value) {
          if(value == null) {
            _showTimeoutDialog(context);
          }
        })
        .onError((error, stackTrace) {
          _showTimeoutDialog(context);
        });
  }
  break;

新增辅助方法:

void _verifyTokenAndShowDialog() async {
  final LoginManager loginManager = locator<LoginManager>();
  final userToken = await loginManager.getUserTokenFromStorage();
  final context = navigatorKey.currentContext;
  if (userToken != null && context != null) {
    locator<LoginManager>().verifyToken(refreshToken)
        .then((value) {
          if(value == null) {
            _showTimeoutDialog(context);
          }
        })
        .onError((error, stackTrace) {
          _showTimeoutDialog(context);
        });
  }
}

2. 校验refreshToken有效性

确保调用verifyToken时refreshToken不为null,避免因参数错误导致404:

if (userToken != null && refreshToken != null) {
  // 执行验证逻辑
}

3. 用try-catch增强异常捕获

替换链式调用为try-catch结构,更直观捕获所有异常:

case AppLifecycleState.resumed:
  final LoginManager loginManager = locator<LoginManager>();
  final userToken = await loginManager.getUserTokenFromStorage();
  final context = navigatorKey.currentContext;
  if (userToken != null && context != null && refreshToken != null) {
    try {
      final LoginResponse? value = await locator<LoginManager>().verifyToken(refreshToken);
      if(value == null) {
        _showTimeoutDialog(context);
      }
    } catch (e, stackTrace) {
      print("Token验证异常: $e\n$stackTrace");
      _showTimeoutDialog(context);
    }
  }
  break;

4. 适配Android 12相机生命周期

在AndroidManifest.xml中添加配置,降低App被系统销毁的概率:

<application
  ...
  android:allowBackup="true"
  android:persistent="false">
  <uses-permission android:name="android.permission.CAMERA"/>
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
</application>

内容的提问来源于stack exchange,提问作者Tom

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 07:15:42