生产中的 flutter 应用程序中的灰屏是一种通用占位符,当框架遇到问题无法渲染预期用户界面时就会显示。是的,所以基本上是出现问题时的后备指示器。
有趣的是,这只出现在发布模式下。在任何其他模式下运行都会显示红色错误屏幕,并说明导致错误的原因。 (检查此处以了解各种类型的构建模式。)此类错误的常见原因是:
- 未处理的异常:这些是运行时发生的错误,未使用
try-catch
块捕获。 - 渲染错误:这些是渲染布局时引起的问题,例如,在
Column
、Row
或Flex
小部件外部使用Expanded
时引起的问题。
以下是可能导致灰屏的代码示例:
dart
class HomeView extends HookWidget {
const HomeView({super.key});
@override
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
函数中放置一行代码。该行充当预防措施,确保每当发生未处理的异常时都会显示自定义错误屏幕。
dart
void main() {
ErrorWidget.builder = (_) => const AppErrorWidget(); // This line does the magic!
runApp(MyApp());
}
有条件的红屏(可选):
也许您希望在开发过程中看到默认的红色错误屏幕以进行调试。您可以通过将 ErrorWidget.builder
赋值包装在检查当前构建模式的 if
语句中来实现此目的:
dart
void main() {
if (kReleaseMode) ErrorWidget.builder = (_) => const AppErrorWidget();
runApp(MyApp());
}
下一步涉及创建 AppErrorWidget
本身的内容。该小部件将确定发生未处理的异常时用户看到的内容。
dart
class AppErrorWidget extends StatelessWidget {
const AppErrorWidget({super.key});
@override
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,
),
),
],
),
),
);
}
}
虽然鼓励自定义应用程序的体验,但 ErrorWidget.builder
上的 Flutter 文档提醒我们,调用错误小部件时视图处于不稳定状态。构建(可能还有布局)期间的异常会使系统处于脆弱状态。为了最大限度地减少进一步的问题,返回的小部件应该做最少的工作。 LeafRenderObjectWidget
(如默认的 RenderErrorBox
)非常适合处理意外约束。
ErrorWidget.builder 的幕后花絮
现在我们知道,当渲染预期 UI 的过程中发生错误时, ErrorWidget.builder
就会被调用,但是这到底是如何实现的呢?
如果我们深入研究 Flutter 的框架,我们会在构建或重建小部件时看到一个名为 _updateChild()
的方法。
dart
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
回调的力量的地方。让我们看看这是如何完成的:
dart
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