Flutter如何捕获平台原生错误防止应用崩溃(iOS视频播放器场景)
问题根因
Dart层编写的catchError、onError无法捕获崩溃的核心原因是:该崩溃并非发生在Dart运行时,而是iOS原生端video_player插件调用AVPlayer加载无效资源(不存在的asset、损坏的视频文件、不可访问的源)时,原生层直接触发未捕获的NSException,Dart层的异常传播链完全无法触达这类原生硬崩溃,应用会被iOS系统直接终止,没有任何Dart层日志输出,也无法触发预先写的兜底逻辑。
另外当前的异常处理代码本身也存在问题:在catchError和onError回调中二次手动抛出新的异常,且没有被上层的runZonedGuarded或组件级异常捕获承接,就算Dart层接到了初始化错误,二次抛出的未捕获异常依然会导致应用退出。
可行解决方案
1. 前置资源合法性校验(推荐优先实现,零侵入成本)
在初始化VideoPlayerController之前,先在Dart层完成资源合法性校验,从源头避免把无效参数传给原生插件:
- 加载asset视频前,先通过
rootBundle尝试读取资源字节,读取失败直接走错误兜底逻辑,不执行controller初始化 - 加载网络视频前,先发起HEAD请求校验URL可访问性、返回的Content-Type是否为视频格式,校验通过再初始化
示例代码:
bool isVideoResourceValid = false; try { // 校验asset资源是否存在 await rootBundle.load("my_video_asset_path"); isVideoResourceValid = true; } catch (e) { isVideoResourceValid = false; debugPrint("视频资源不存在,直接走错误兜底: $e"); // 此处更新状态,展示错误提示组件即可 } if (isVideoResourceValid) { controller = VideoPlayerController.asset("my_video_asset_path"); // 初始化时正常捕获Dart层可感知的异常,不要二次抛出未承接的异常 controller?.initialize().then((_) { // 初始化成功逻辑 }).catchError((err) { debugPrint("视频初始化失败: $err"); // 此处走错误兜底逻辑 }); }
2. 原生端补全异常拦截(覆盖所有崩溃场景)
前置校验只能拦截资源不存在的场景,如果资源存在但文件损坏、编码格式不支持,AVPlayer依然会在原生层触发崩溃。这种情况需要修改iOS端插件的原生代码,把可能触发异常的逻辑用@try-@catch包裹,将异常通过MethodChannel回传到Dart层:
- 打开项目iOS目录下
Pods/Development Pods/video_player/ios/Classes/FLTVideoPlayer.m文件(如果是pub缓存的插件,需要先把插件改成本地依赖或者用dependency_overrides方式引入,避免pub安装覆盖修改) - 找到
setupPlayer、setDataSource等涉及AVPlayer初始化、资源加载的方法,将核心逻辑包裹在@try块中 - 捕获到
NSException后,将错误信息封装成FlutterError,通过当前的MethodChannel/EventChannel回传给Dart层,不要让异常直接抛出到iOS系统层面
修改后Dart层的initialize().catchError就可以正常接收到所有初始化阶段的错误,不会再出现静默崩溃。
3. 全局原生异常兜底(临时方案)
如果暂时不想修改插件原生代码,可以在iOS端AppDelegate中注册全局NSSetUncaughtExceptionHandler,捕获所有未处理的原生异常,将异常信息转发到Dart层做兜底处理,避免应用直接无提示退出。但该方案仅作为最后一道防线,稳定性不如前两种方案。
内容的提问来源于stack exchange,提问作者JohnnyRainbow

