使用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)), ); } }
关键修改点
- 调整
startCameraProcessing()调用时机:必须在相机初始化完成后调用,确保zxing获取有效相机资源; - 添加
imageFormatGroup配置:指定ImageFormatGroup.yuv420,让相机支持预览流与拍照同时输出; - 扫描逻辑优化:用
_isScanning防止并发扫描阻塞,_lastScannedText实现结果去重; - 拍照资源冲突处理:拍照前临时停止图像流,完成后重启,避免资源竞争导致崩溃。
内容的提问来源于stack exchange,提问作者Stelios Papamichail
相关产品推荐
相关产品推荐

