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

使用flutter_zxing导致相机会话自动关闭的问题排查

Flutter相机会话异常关闭+扫码无响应+拍照崩溃问题解决

核心问题根源

你的代码存在三个关键错误:startCameraProcessing()调用时机错误、扫描逻辑未做并发控制、拍照与图像流资源冲突,最终导致相机会话被异常释放,扫码无响应,拍照时因Surface配置失败崩溃。

问题拆解与修复说明

1. 相机会话异常关闭的原因

你在初始化CameraController之前就调用了startCameraProcessing(),违反flutter_zxing的依赖逻辑——zxing的相机处理需要基于已初始化的相机上下文,提前调用会导致内部资源混乱,触发相机会话被强制关闭(对应日志中的CameraCaptureSession onClosed)。

2. 扫码无响应的原因

  • 图像流回调中直接连续调用异步的processCameraImage,导致大量并发任务阻塞zxing处理流程;
  • 未对扫描结果做去重处理,即使扫到内容也可能因重复输出被忽略。

3. 拍照崩溃的原因

相机处于图像流模式时,takePicture()需要会话同时支持预览和拍照的Surface,但你的代码未提前配置相机输出格式,导致调用拍照时会话找不到可用Surface,抛出IllegalArgumentException。


修复后的完整代码

import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter_zxing/flutter_zxing.dart';
import 'dart:async';
import 'dart:io';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final cameras = await availableCameras();
  final firstCamera = cameras.first;

  runApp(
    MaterialApp(
      theme: ThemeData.dark(),
      home: TakePictureScreen(camera: firstCamera),
    ),
  );
}

class TakePictureScreen extends StatefulWidget {
  const TakePictureScreen({
    super.key,
    required this.camera,
  });

  final CameraDescription camera;

  @override
  TakePictureScreenState createState() => TakePictureScreenState();
}

class TakePictureScreenState extends State<TakePictureScreen> {
  CameraController? _controller;
  late Future<void> _initializeControllerFuture;
  bool _isScanning = false; // 防止并发扫描阻塞
  String? _lastScannedText; // 扫描结果去重

  @override
  void initState() {
    super.initState();
    setUp();
  }

  Future<void> setUp() async {
    _controller = CameraController(
      widget.camera,
      ResolutionPreset.high,
      // 配置图像格式组,支持预览流与拍照同时工作
      imageFormatGroup: ImageFormatGroup.yuv420,
    );

    // 先初始化相机,再启动zxing处理
    _initializeControllerFuture = _controller!.initialize().then((_) async {
      if (!mounted) return;
      setState(() {});

      await startCameraProcessing();

      _controller!.startImageStream((image) async {
        if (_isScanning) return;
        _isScanning = true;

        try {
          CodeResult result = await processCameraImage(image);
          if (result.isValidBool && result.textString != _lastScannedText) {
            _lastScannedText = result.textString;
            debugPrint('扫码结果:$_lastScannedText');
            // 可在此添加扫码成功后的业务逻辑(弹窗/跳转等)
          }
        } catch (e) {
          debugPrint('扫码错误:$e');
        } finally {
          _isScanning = false;
        }
      });
    });
  }

  @override
  void dispose() {
    _controller?.stopImageStream();
    stopCameraProcessing();
    _controller?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('扫码+拍照')),
      body: FutureBuilder<void>(
        future: _initializeControllerFuture,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.done) {
            return CameraPreview(_controller!);
          } else {
            return const Center(child: CircularProgressIndicator());
          }
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          try {
            await _initializeControllerFuture;

            // 拍照前临时停止图像流,避免资源冲突
            await _controller!.stopImageStream();
            final image = await _controller!.takePicture();
            
            // 拍照完成后重启图像流与扫描
            _controller!.startImageStream((image) async {
              if (_isScanning) return;
              _isScanning = true;
              try {
                CodeResult result = await processCameraImage(image);
                if (result.isValidBool && result.textString != _lastScannedText) {
                  _lastScannedText = result.textString;
                  debugPrint('扫码结果:$_lastScannedText');
                }
              } catch (e) {
                debugPrint('扫码错误:$e');
              } finally {
                _isScanning = false;
              }
            });

            if (!mounted) return;
            await Navigator.of(context).push(
              MaterialPageRoute(
                builder: (context) => DisplayPictureScreen(imagePath: image.path),
              ),
            );
          } catch (e) {
            debugPrint('拍照错误:$e');
          }
        },
        child: const Icon(Icons.camera_alt),
      ),
    );
  }
}

class DisplayPictureScreen extends StatelessWidget {
  final String imagePath;

  const DisplayPictureScreen({super.key, required this.imagePath});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('查看照片')),
      body: Image.file(File(imagePath)),
    );
  }
}

关键修改点

  1. 调整startCameraProcessing()调用时机:必须在相机初始化完成后调用,确保zxing获取有效相机资源;
  2. 添加imageFormatGroup配置:指定ImageFormatGroup.yuv420,让相机支持预览流与拍照同时输出;
  3. 扫描逻辑优化:用_isScanning防止并发扫描阻塞,_lastScannedText实现结果去重;
  4. 拍照资源冲突处理:拍照前临时停止图像流,完成后重启,避免资源竞争导致崩溃。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 04:05:21