0x00 前言
開發Flutter的時候,肯定都會遇到Flutter錯誤頁面,可以讓我們在開發的時候很清楚的知道程式碼有異常,但是,如果釋出出去的APP出現了異常,那麼這個錯誤頁面就很不友好,其實這個錯誤頁面是可以自定義的,本篇文章告訴你如何自定義錯誤頁面!
0x01 Flutter錯誤頁面
這是我們經常看到的錯誤頁面:
0x02 自定義Flutter錯誤頁面
要想Flutter的錯誤頁面顯示成自定義的頁面,只要設定ErrorWidget
的builder
就行。
程式碼如下:
ErrorWidget.builder = (FlutterErrorDetails flutterErrorDetails){
print(flutterErrorDetails.toString());
return Center(
child: Text("Flutter 走神了"),
);
};
複製程式碼
ErrorWidget.builder
返回一個Widget,當Flutter出錯的時候就會顯示這個Widget,
下圖就是我們自定義的錯誤頁面,比Flutter的友好多了:
0x03 github
本篇文章所涉及的程式碼: github.com/koudle/GDG_…
github地址:github.com/koudle/GDG_…
0x04 ErrorWidget原始碼分析
ErrorWidget
的原始碼在framework.dart
的3581行-3630行,很簡單,ErrorWidget
的建構函式的引數是exception的物件,然後返回一個內容是exception message資訊的RenderBox,我們看到的Flutter的錯誤頁面就是這個RenderBox。
class ErrorWidget extends LeafRenderObjectWidget {
/// 建立一個顯示error message的Widget,exception是建構函式的引數。
ErrorWidget(Object exception) : message = _stringify(exception),
super(key: UniqueKey());
//ErrorWidgetBuilder.builder的預設設定是ErrorWidget,我們可以設定成自己的
static ErrorWidgetBuilder builder = (FlutterErrorDetails details) => ErrorWidget(details.exception);
/// The message to display.
final String message;
//將exception物件轉換成string
static String _stringify(Object exception) {
try {
return exception.toString();
} catch (e) { } // ignore: empty_catches
return 'Error';
}
//返回一個內容是exception message資訊的RenderBox
@override
RenderBox createRenderObject(BuildContext context) => RenderErrorBox(message);
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(StringProperty('message', message, quoted: false));
}
}
複製程式碼
0x05 呼叫ErrorWidget的原始碼分析
前面看了ErrorWidget
的原始碼,只是建立一個Widget,那麼是哪裡呼叫ErrorWidget
顯示的呢?
呼叫ErrorWidget
的程式碼總共有三處,這三處都有一個共同點,就是在build Widget的過程中,如果出現異常,則返回一個ErrorWidget顯示,具體的原始碼如下:
- ComponentElement(framework.dart)
@override
void performRebuild() {
assert(() {
if (debugProfileBuildsEnabled)
Timeline.startSync('${widget.runtimeType}', arguments: timelineWhitelistArguments);
return true;
}());
assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true));
Widget built;
try {
built = build();
debugWidgetBuilderValue(widget, built);
} catch (e, stack) {
built = ErrorWidget.builder(_debugReportException('building $this', e, stack));
} finally {
// We delay marking the element as clean until after calling build() so
// that attempts to markNeedsBuild() during build() will be ignored.
_dirty = false;
assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false));
}
try {
_child = updateChild(_child, built, slot);
assert(_child != null);
} catch (e, stack) {
built = ErrorWidget.builder(_debugReportException('building $this', e, stack));
_child = updateChild(null, built, slot);
}
assert(() {
if (debugProfileBuildsEnabled)
Timeline.finishSync();
return true;
}());
}
複製程式碼
- RenderObjectToWidgetElement(binding.dart)
void _rebuild() {
try {
_child = updateChild(_child, widget.child, _rootChildSlot);
assert(_child != null);
} catch (exception, stack) {
final FlutterErrorDetails details = FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'widgets library',
context: 'attaching to the render tree'
);
FlutterError.reportError(details);
final Widget error = ErrorWidget.builder(details);
_child = updateChild(null, error, _rootChildSlot);
}
}
複製程式碼
- _LayoutBuilderElement (layout_builder.dart)
void _layout(BoxConstraints constraints) {
owner.buildScope(this, () {
Widget built;
if (widget.builder != null) {
try {
built = widget.builder(this, constraints);
debugWidgetBuilderValue(widget, built);
} catch (e, stack) {
built = ErrorWidget.builder(_debugReportException('building $widget', e, stack));
}
}
try {
_child = updateChild(_child, built, null);
assert(_child != null);
} catch (e, stack) {
built = ErrorWidget.builder(_debugReportException('building $widget', e, stack));
_child = updateChild(null, built, slot);
}
});
}
複製程式碼