Flutter ARCore Plugin二次进入崩溃及Overlay无法关闭问题排查
增强图像识别APP问题排查与修复
问题清单
- AR页面首次访问可正常扫描图像获取结果,但退出后再次进入崩溃
- 点击Overlay关闭按钮后,Overlay无法消失
报错核心信息
E/AndroidRuntime( 7710): FATAL EXCEPTION: main E/AndroidRuntime( 7710): java.lang.RuntimeException: Unable to resume activity {com.example.shopping_mall_app/com.example.shopping_mall_app.MainActivity}: java.lang.NullPointerException: null cannot be cast to non-null type android.view.View E/AndroidRuntime( 7710): Caused by: java.lang.NullPointerException: null cannot be cast to non-null type android.view.View E/AndroidRuntime( 7710): at com.difrancescogianmarco.arcore_flutter_plugin.BaseArCoreView.getView(BaseArCoreView.kt:88)
问题分析
崩溃原因:
- ARCoreController未彻底释放,退出页面后残留会话状态导致再次进入时重复初始化,触发空指针异常
- 页面嵌套MaterialApp引发路由管理混乱,退出时未彻底清理AR视图资源
- dispose方法未清空controller的回调监听,后台仍有状态更新尝试
Overlay无法关闭原因:
_handleOnTrackingImage方法中,每次追踪到图像都会无条件调用setState(() => showOverlay = true),直接覆盖用户的关闭操作
修复代码
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:arcore_flutter_plugin/arcore_flutter_plugin.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:shopping_mall_app/app/pages/main_menu.dart'; class ARScan extends StatefulWidget { @override _ARScan createState() => _ARScan(); } class _ARScan extends State<ARScan> { ArCoreController? arCoreController; Map<int, ArCoreAugmentedImage> augmentedImagesMap = {}; bool showOverlay = false; List<Map<String, dynamic>> productList = []; bool _isDisposed = false; // 标记页面是否已销毁 @override Widget build(BuildContext context) { // 移除嵌套MaterialApp,直接返回Scaffold(上层已有MaterialApp) return Scaffold( appBar: AppBar( title: const Text('AugmentedPage'), leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () async { // 退出前提前释放AR资源 await arCoreController?.dispose(); Navigator.pushAndRemoveUntil( context, MaterialPageRoute(builder: (context) => MainMenu()), (route) => false, ); }, ), ), body: Stack( children: [ ArCoreView( onArCoreViewCreated: _onArCoreViewCreated, type: ArCoreViewType.AUGMENTEDIMAGES, ), if (showOverlay) _buildOverlay(), ], ), ); } Widget _buildOverlay() { return Positioned( bottom: 20, left: 20, right: 20, child: Material( color: Colors.white.withOpacity(0.8), borderRadius: BorderRadius.circular(10), child: Padding( padding: const EdgeInsets.all(10.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'Detected Deals!', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), IconButton( icon: const Icon(Icons.close), onPressed: () { setState(() { showOverlay = false; }); }, ), ], ), const SizedBox(height: 10), ...productList.map((product) => ListTile( leading: product['ImageURL'] != null ? Image.network(product['ImageURL'], width: 50) : const Icon(Icons.image), title: Text(product['Product Name'] ?? 'Unnamed Product'), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Cost: ${product['Enter Product Cost'] ?? 'N/A'}'), Text('Discount: ${product['Enter Discount'] ?? 'N/A'}'), Text('Points: ${product['Enter Points'] ?? 'N/A'}'), ], ), )), ], ), ), ), ); } void _onArCoreViewCreated(ArCoreController controller) async { if (_isDisposed) return; // 页面已销毁则跳过初始化 arCoreController = controller; arCoreController?.onTrackingImage = _handleOnTrackingImage; await loadSingleImage(); await fetchProductsFromFirebase(); } Future<void> loadSingleImage() async { if (_isDisposed || arCoreController == null) return; final ByteData bytes = await rootBundle.load('Assets/testings.jpg'); await arCoreController?.loadSingleAugmentedImage( bytes: bytes.buffer.asUint8List(), ); } Future<void> fetchProductsFromFirebase() async { final querySnapshot = await FirebaseFirestore.instance .collection('users') .doc('7r6AWJR9HlaHZQ05GE4bmKtkjE13') .collection('products') .get(); if (!_isDisposed) { // 页面未销毁才更新状态 setState(() { productList = querySnapshot.docs.map((doc) => doc.data()).toList(); }); } } void _handleOnTrackingImage(ArCoreAugmentedImage augmentedImage) { if (_isDisposed) return; if (!augmentedImagesMap.containsKey(augmentedImage.index)) { augmentedImagesMap[augmentedImage.index] = augmentedImage; } // 仅当Overlay未显示时才打开,避免覆盖用户关闭操作 if (!showOverlay) { setState(() { showOverlay = true; }); } } @override void dispose() { _isDisposed = true; // 标记页面已销毁 arCoreController?.onTrackingImage = null; // 清空回调监听 arCoreController?.dispose(); // 释放AR资源 super.dispose(); } }
关键修改点
修复崩溃问题:
- 移除嵌套的
MaterialApp,解决路由管理冲突 - 新增
_isDisposed标志位,防止页面销毁后执行无效操作 - 退出页面时提前调用
arCoreController?.dispose(),彻底清理AR会话 - 在
dispose方法中清空onTrackingImage回调,避免后台触发状态更新
- 移除嵌套的
修复Overlay无法关闭问题:
- 修改
_handleOnTrackingImage方法,仅当showOverlay为false时才设置为true,不再覆盖用户的关闭操作
- 修改
内容的提问来源于stack exchange,提问作者srbjeet singh
相关产品推荐
相关产品推荐

