当前位置: 首页 > news >正文

了解并解决 Flutter 中的灰屏问题

生产中的 flutter 应用程序中的灰屏是一种通用占位符,当框架遇到问题无法渲染预期用户界面时就会显示。是的,所以基本上是出现问题时的后备指示器。

有趣的是,这只出现在发布模式下。在任何其他模式下运行都会显示红色错误屏幕,并说明导致错误的原因。 (检查此处以了解各种类型的构建模式。)此类错误的常见原因是:

  • 未处理的异常:这些是运行时发生的错误,未使用 try-catch 块捕获。
  • 渲染错误:这些是渲染布局时引起的问题,例如,在 ColumnRowFlex 小部件外部使用 Expanded 时引起的问题。

以下是可能导致灰屏的代码示例:

class HomeView extends HookWidget {const HomeView({super.key});Widget build(BuildContext context) {const widget = null;return Scaffold(appBar: AppBar(title: Text('Gallery',style: Theme.of(context).textTheme.headlineLarge,),),body: widget!,);}
}

在这里,我们犯了一个明显的错误,在我们知道的 null 小部件上使用了 bang 运算符(!),这导致在非发布模式下出现红屏,在发布模式下出现灰屏。

需要注意的是,我们不建议在不更新的情况下将部件明确设置为空值,空值错误是一个常见错误,而上述操作是重现该错误的简单方法。

调试模式下的红色错误屏幕(左)和发布模式下的灰色屏幕(右)的图像

自定义错误屏幕

为了显示更用户友好的消息而不是灰屏,我们将策略性地在 main 函数中放置一行代码。该行充当预防措施,确保每当发生未处理的异常时都会显示自定义错误屏幕。


void main() {ErrorWidget.builder = (_) => const AppErrorWidget(); // This line does the magic!runApp(MyApp());
}

有条件的红屏(可选):

也许您希望在开发过程中看到默认的红色错误屏幕以进行调试。您可以通过将 ErrorWidget.builder 赋值包装在检查当前构建模式的 if 语句中来实现此目的:

void main() {if (kReleaseMode) ErrorWidget.builder = (_) => const AppErrorWidget();runApp(MyApp());
}

下一步涉及创建 AppErrorWidget 本身的内容。该小部件将确定发生未处理的异常时用户看到的内容。

class AppErrorWidget extends StatelessWidget {const AppErrorWidget({super.key});Widget build(BuildContext context) {return const Material(color: Colors.white,child: Padding(padding: EdgeInsets.all(24),child: Column(mainAxisAlignment: MainAxisAlignment.center,mainAxisSize: MainAxisSize.min,children: [Icon(Icons.warning,size: 200,color: Colors.amber,),SizedBox(height: 48),Text('So... something funny happened',textAlign: TextAlign.center,style: TextStyle(fontSize: 24,fontWeight: FontWeight.bold,),),SizedBox(height: 16),Text('This error is crazy large it covers your whole screen. But no worries'' though, we\'re working to fix it.',textAlign: TextAlign.center,style: TextStyle(fontSize: 16,),),],),),);}
}

AppErrorWidget 小部件的结果显示

虽然鼓励自定义应用程序的体验,但 ErrorWidget.builder 上的 Flutter 文档提醒我们,调用错误小部件时视图处于不稳定状态。构建(可能还有布局)期间的异常会使系统处于脆弱状态。为了最大限度地减少进一步的问题,返回的小部件应该做最少的工作。 LeafRenderObjectWidget (如默认的 RenderErrorBox )非常适合处理意外约束。

ErrorWidget.builder 的幕后花絮

现在我们知道,当渲染预期 UI 的过程中发生错误时, ErrorWidget.builder 就会被调用,但是这到底是如何实现的呢?

如果我们深入研究 Flutter 的框架,我们会在构建或重建小部件时看到一个名为 _updateChild() 的方法。

void _updateChild() {try {final Widget child = (widget as _RawView).builder(this, _effectivePipelineOwner);_child = updateChild(_child, child, null);} catch (e, stack) {final FlutterErrorDetails details = FlutterErrorDetails(exception: e,stack: stack,library: 'widgets library',context: ErrorDescription('building $this'),informationCollector: !kDebugMode ? null : () => <DiagnosticsNode>[DiagnosticsDebugCreator(DebugCreator(this)),],);FlutterError.reportError(details);final Widget error = ErrorWidget.builder(details);_child = updateChild(null, error, slot);}
}

我们可以看到 ErrorWidget.builder 属性用于根据提供的 FlutterErrorDetails 检索自定义错误小部件;然后更新 _child 变量以显示自定义错误小部件而不是原始子小部件。

提升开发者体验

定制向用户呈现错误的方式是改善用户体验的关键一步。虽然 ErrorWidget.builder 帮助我们在出现错误时管理用户体验,但它并没有为生产环境中的开发人员提供有价值的见解。本地调试不再是一种选择,那么我们如何及时了解用户设备上发生的错误呢?

这就是我们利用 FlutterError.onError 回调的力量的地方。让我们看看这是如何完成的:

void main() {if (kReleaseMode) ErrorWidget.builder = (_) => const AppErrorWidget();FlutterError.onError = (details) {FlutterError.dumpErrorToConsole(details);if (!kReleaseMode) return;// 发送到您的 crashlytics 服务...};runApp(MyApp());
}

我们添加了一行新代码,它将新的回调函数分配给 FlutterError.onError 属性。每当使用 FlutterError.reportError 报告错误时都会调用此回调。

在回调内部, FlutterError.dumpErrorToConsole(details) 通过将错误详细信息转储到控制台来帮助我们了解幕后情况。这对于在部署或分阶段部署期间可能存在对用户设备的访问受限的调试目的非常有用。

最后的注释行 ( // 发送到您的 crashlytics 服务... ) 强调了这种方法的真正威力。在这里,您可以集成您选择的错误报告服务(例如 Crashlytics)以发送详细的错误报告以供分析。

注意:此行包含在 if 语句中,以确保它仅在调试或分析模式下执行 ( !kReleaseMode )。

避免灰屏的最佳错误处理实践

我们已经了解了导致灰屏的原因以及出现灰屏时如何更好地处理它;我们还应该介绍的一件事是,作为开发人员可以采取哪些措施来避免出现灰屏。其中一些是:

  • 拥抱 try-catch :将关键代码部分包装在 try-catch 块内。这允许您捕获潜在的异常并提供优雅的回退机制。
  • 少用 Bang 运算符 (!):bang 运算符 (!) 是 null 断言检查的快捷方式,但如果用于不确定是否为非 null 的值,可能会导致意外错误。更多地使用条件表达式 (??) 或 null 感知访问运算符 (?.)。
  • 彻底的应用程序测试:结合使用单元、小部件、集成和手动测试来帮助在问题出现在生产中之前识别和解决问题。
  • 尊重 Widget 约束:Flutter 中的每个 Widget 都有局限性和预期的使用模式;避免在其限制之外使用它们,例如在可滚动视图中使用 Spacer

结论:拥抱不可避免的事情

错误处理是任何编写良好的 Flutter 应用程序的重要组成部分。当您努力编写干净的代码并预测潜在问题时,异常情况必然会发生。通过实施 ErrorWidget.builder ,您可以确保即使发生意外情况,您的用户也会看到清晰且内容丰富的消息,而不是令人困惑的灰屏,并且通过 FlutterError.onError 您可以确保您记录这些意外错误,并且可以更轻松地调试和修复这些错误。

请记住,即使面对不可预见的障碍,一点准备对于保持积极的用户和开发人员体验也大有帮助。


原文:https://medium.com/@LordChris/understanding-and-addressing-the-grey-screen-in-flutter-5e72c31f408f

相关文章:

  • 瞬间将模型改为原来的60-200倍小
  • PHP框架详解 - CakePHP框架
  • 细说MCU输出互补型PWM波形时设置死区时间的作用
  • 大数据之Hadoop的特点是什么?有什么优缺点?有哪些发行版本?
  • 军用FPGA软件 Verilog语言的编码准测之触发器、锁存器
  • 各类存储器类型(RAM、ROM、FLASH、DRAM、SRAM)
  • Kafka之ISR机制的理解
  • Java程序设计语言的特点
  • 【Quartus 13.0】NIOS II 部署UART 和 PWM
  • phpStudy里面的MySQL启动不了
  • 这些已经死去的软件,依旧无可替代
  • 深度学习 - CNN
  • 基于Wireshark实现对FTP的抓包分析
  • 多目标跟踪中检测器和跟踪器如何协同工作的
  • JavaScript------const
  • [case10]使用RSQL实现端到端的动态查询
  • [译] 怎样写一个基础的编译器
  • CSS 三角实现
  • Sass 快速入门教程
  • SQLServer之创建数据库快照
  • Vim Clutch | 面向脚踏板编程……
  • 阿里研究院入选中国企业智库系统影响力榜
  • 扑朔迷离的属性和特性【彻底弄清】
  • 使用agvtool更改app version/build
  • 微服务核心架构梳理
  • 验证码识别技术——15分钟带你突破各种复杂不定长验证码
  • 原生 js 实现移动端 Touch 滑动反弹
  • 在GitHub多个账号上使用不同的SSH的配置方法
  • Java数据解析之JSON
  • NLPIR智能语义技术让大数据挖掘更简单
  • 专访Pony.ai 楼天城:自动驾驶已经走过了“从0到1”,“规模”是行业的分水岭| 自动驾驶这十年 ...
  • ​TypeScript都不会用,也敢说会前端?
  • ‌分布式计算技术与复杂算法优化:‌现代数据处理的基石
  • #中的引用型是什么意识_Java中四种引用有什么区别以及应用场景
  • (4.10~4.16)
  • (delphi11最新学习资料) Object Pascal 学习笔记---第8章第5节(封闭类和Final方法)
  • (二)十分简易快速 自己训练样本 opencv级联lbp分类器 车牌识别
  • (剑指Offer)面试题41:和为s的连续正数序列
  • (六)Hibernate的二级缓存
  • (十)T检验-第一部分
  • (四)汇编语言——简单程序
  • (算法)大数的进制转换
  • (算法设计与分析)第一章算法概述-习题
  • (幽默漫画)有个程序员老公,是怎样的体验?
  • (原)Matlab的svmtrain和svmclassify
  • (转)Oracle 9i 数据库设计指引全集(1)
  • .net web项目 调用webService
  • .NET 漏洞分析 | 某ERP系统存在SQL注入
  • .net 桌面开发 运行一阵子就自动关闭_聊城旋转门家用价格大约是多少,全自动旋转门,期待合作...
  • .sys文件乱码_python vscode输出乱码
  • @Autowired和@Resource装配
  • @EnableConfigurationProperties注解使用
  • @EnableWebSecurity 注解的用途及适用场景
  • @ModelAttribute使用详解
  • @PreAuthorize与@Secured注解的区别是什么?