起因
bugly捕捉到大量系统异常,所有方法都在render层,无法定位问题代码。
- 异常1
1 [uid: 10423443 - uuid: fe027965f990c232] #0 RenderBox.size (package:flutter/src/rendering/box.dart:1962)
2 #1 RenderPadding.performLayout (package:flutter/src/rendering/shifted_box.dart:230)
3 #2 RenderObject.layout (package:flutter/src/rendering/object.dart:1784)
4 #3 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118)
5 #4 RenderObject.layout (package:flutter/src/rendering/object.dart:1784)
6 #5 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118)
7 #6 RenderObject.layout (package:flutter/src/rendering/object.dart:1784)
8 #7 RenderSliverMultiBoxAdaptor.insertAndLayoutChild (package:flutter/src/rendering/sliver_multi_box_adaptor.dart:487)
9 #8 RenderSliverList.performLayout.advance (package:flutter/src/rendering/sliver_list.dart:239)
10 #9 RenderSliverList.performLayout (package:flutter/src/rendering/sliver_list.dart:282)
11 #10 RenderObject.layout (package:flutter/src/rendering/object.dart:1784)
12 #11 RenderSliverEdgeInsetsPadding.performLayout (package:flutter/src/rendering/sliver_padding.dart:137)
13 #12 RenderSliverPadding.performLayout (package:flutter/src/rendering/sliver_padding.dart:371)
14 #13 RenderObject.layout (package:flutter/src/rendering/object.dart:1784)
15 #14 RenderViewportBase.layoutChildSequence (package:flutter/src/rendering/viewport.dart:512)
16 #15 RenderViewport._attemptLayout (package:flutter/src/rendering/viewport.dart:1570)
17 #16 RenderViewport.performLayout (package:flutter/src/rendering/viewport.dart:1479)
18 #17 RenderObject._layoutWithoutResize (package:flutter/src/rendering/object.dart:1641)
19 #18 PipelineOwner.flushLayout (package:flutter/src/rendering/object.dart:884)
20 #19 RendererBinding.drawFrame (package:flutter/src/rendering/binding.dart:453)
- 异常2
1 [uid: 10423443 - uuid: fe027965f990c232] #0 RenderBox.size (package:flutter/src/rendering/box.dart:1962)
2 #1 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:119)
3 #2 RenderObject.layout (package:flutter/src/rendering/object.dart:1784)
4 #3 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118)
5 #4 RenderObject.layout (package:flutter/src/rendering/object.dart:1784)
6 #5 RenderSliverMultiBoxAdaptor.insertAndLayoutChild (package:flutter/src/rendering/sliver_multi_box_adaptor.dart:487)
7 #6 RenderSliverList.performLayout.advance (package:flutter/src/rendering/sliver_list.dart:239)
8 #7 RenderSliverList.performLayout (package:flutter/src/rendering/sliver_list.dart:282)
9 #8 RenderObject.layout (package:flutter/src/rendering/object.dart:1784)
10 #9 RenderSliverEdgeInsetsPadding.performLayout (package:flutter/src/rendering/sliver_padding.dart:137)
11 #10 RenderSliverPadding.performLayout (package:flutter/src/rendering/sliver_padding.dart:371)
12 #11 RenderObject.layout (package:flutter/src/rendering/object.dart:1784)
13 #12 RenderViewportBase.layoutChildSequence (package:flutter/src/rendering/viewport.dart:512)
14 #13 RenderViewport._attemptLayout (package:flutter/src/rendering/viewport.dart:1570)
15 #14 RenderViewport.performLayout (package:flutter/src/rendering/viewport.dart:1479)
16 #15 RenderObject._layoutWithoutResize (package:flutter/src/rendering/object.dart:1641)
17 #16 PipelineOwner.flushLayout (package:flutter/src/rendering/object.dart:884)
18 #17 RendererBinding.drawFrame (package:flutter/src/rendering/binding.dart:453)
19 #18 WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:874)
20 #19 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:319)
- 异常3
1 [uid: 10423443 - uuid: fe027965f990c232] #0 RenderBox.size (package:flutter/src/rendering/box.dart:1962)
2 #1 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:119)
3 #2 RenderObject.layout (package:flutter/src/rendering/object.dart:1784)
4 #3 RenderSliverMultiBoxAdaptor.insertAndLayoutChild (package:flutter/src/rendering/sliver_multi_box_adaptor.dart:487)
5 #4 RenderSliverList.performLayout.advance (package:flutter/src/rendering/sliver_list.dart:239)
6 #5 RenderSliverList.performLayout (package:flutter/src/rendering/sliver_list.dart:282)
7 #6 RenderObject.layout (package:flutter/src/rendering/object.dart:1784)
8 #7 RenderSliverEdgeInsetsPadding.performLayout (package:flutter/src/rendering/sliver_padding.dart:137)
9 #8 RenderSliverPadding.performLayout (package:flutter/src/rendering/sliver_padding.dart:371)
10 #9 RenderObject.layout (package:flutter/src/rendering/object.dart:1784)
11 #10 RenderViewportBase.layoutChildSequence (package:flutter/src/rendering/viewport.dart:512)
12 #11 RenderViewport._attemptLayout (package:flutter/src/rendering/viewport.dart:1570)
13 #12 RenderViewport.performLayout (package:flutter/src/rendering/viewport.dart:1479)
14 #13 RenderObject._layoutWithoutResize (package:flutter/src/rendering/object.dart:1641)
15 #14 PipelineOwner.flushLayout (package:flutter/src/rendering/object.dart:884)
16 #15 RendererBinding.drawFrame (package:flutter/src/rendering/binding.dart:453)
17 #16 WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:874)
18 #17 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:319)
19 #18 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1144)
20 #19 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1082)
- 异常4
1 [uid: 10423443 - uuid: fe027965f990c232] #0 ParagraphBuilder.addText (dart:ui/text.dart:2178)
2 #1 TextSpan.build (package:flutter/src/painting/text_span.dart:204)
3 #2 TextSpan.build (package:flutter/src/painting/text_span.dart:208)
4 #3 TextSpan.build (package:flutter/src/painting/text_span.dart:208)
5 #4 TextPainter.layout (package:flutter/src/painting/text_painter.dart:569)
6 #5 RenderParagraph._layoutText (package:flutter/src/rendering/paragraph.dart:515)
7 #6 RenderParagraph._layoutTextWithConstraints (package:flutter/src/rendering/paragraph.dart:538)
8 #7 RenderParagraph.performLayout (package:flutter/src/rendering/paragraph.dart:651)
9 #8 RenderObject.layout (package:flutter/src/rendering/object.dart:1784)
10 #9 RenderPositionedBox.performLayout (package:flutter/src/rendering/shifted_box.dart:430)
11 #10 RenderObject.layout (package:flutter/src/rendering/object.dart:1784)
12 #11 ChildLayoutHelper.layoutChild (package:flutter/src/rendering/layout_helper.dart:54)
13 #12 RenderFlex._computeSizes (package:flutter/src/rendering/flex.dart:897)
14 #13 RenderFlex.performLayout (package:flutter/src/rendering/flex.dart:932)
15 #14 RenderObject.layout (package:flutter/src/rendering/object.dart:1784)
16 #15 RenderPadding.performLayout (package:flutter/src/rendering/shifted_box.dart:226)
17 #16 RenderObject.layout (package:flutter/src/rendering/object.dart:1784)
18 #17 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118)
19 #18 RenderObject.layout (package:flutter/src/rendering/object.dart:1784)
20 #19 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118)
排查
异常的堆栈都在renderObject层,无法定位哪行代码出的问题。
异常捕获:为什么有的在build层能定位,为什么有的在renderObject层?
performRebuild
在performRebuild中调用build()和updateChild()方法。在StatelessElement和StatefulElement中通过Widget build() => state.build(this);调用到自定义widget中的build,其中build发生异常,被try...catch捕捉到堆栈,定位到具体的代码行数。
//framework.dart
abstract class ComponentElement extends Element {
@override
void performRebuild() {
...
Widget? built;
try {
...
built = build();
...
} catch (e, stack) {
built = ErrorWidget.builder(
_debugReportException(
ErrorDescription('building $this'),
e,
stack,
informationCollector: () sync* {
yield DiagnosticsDebugCreator(DebugCreator(this));
},
),
);
} finally {
_dirty = false;
}
try {
_child = updateChild(_child, built, slot);
} catch (e, stack) {
built = ErrorWidget.builder(
_debugReportException(
ErrorDescription('building $this'),
e,
stack,
informationCollector: () sync* {
yield DiagnosticsDebugCreator(DebugCreator(this));
},
),
);
_child = updateChild(null, built, slot);
}
...
}
}
performLayout
performLayout在RenderObject层调用,用try...catch捕捉。RenderObejct是由element管理,在framework层面,不经过开发者。捕捉到的堆栈在renderObejct层。
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
void _layoutWithoutResize() {
try {
performLayout();
markNeedsSemanticsUpdate();
} catch (e, stack) {
_debugReportException('performLayout', e, stack);
}
_needsLayout = false;
markNeedsPaint();
}
}
小结:flutter异常多数都是null value,例如build方法中出现数组越界,空异常等;performLayout中的size方法获取用了!等。
突破口\ud83d
- 在找Invalid argument(s): string is not well-formed UTF-16时查找到https://bugs.chromium.org/p/skia/issues/detail?id=12850。大概意思是:Text.rich渲染\ud83d会抛异常,测试代码如下:
TextSpan(
text: "\ud83d",
style: TextStyle(color: e.color, fontSize: 14,),
)
探索\ud83d
- \ud83d是什么?只知道是unicode编码,什么情况下会出现这个字符?
\ud83d是unicode的一个码。特殊字符(包含emoji)的梳理,\ud83d是表情组成部分。
一个表情是由两个unicode码组成的,如🖤的unicode码就是\uD83D\uDDA4。
- 会不会是平台兼容性问题?如iOS的表情到Android导致的。
- 会不会是第三方输入法导致?搜狗、科大讯飞、百度等。
以上问题经过测试,表情都是以两个unicode码成对出现的,不会出现问题。答案就在眼前,但不知道原因。查看退货查验代码,有两处使用Text.rich。一个是商品cell,一个是备注。
与后端沟通
考虑与后端沟通,根据bugly时间获取前后的时间的返回报文。
第一次:没有复现。
第二次思考信息更全面,同时也意识到不足。如扫码时是否能带上code码,方便排查。后期有userId,比较好过滤。同时对后端的过滤规则有一定了解。
这次把detail和keyword接口都查到了。在最新的代码没有复现。想到切到问题版本,问题必现了。
进入退货查验,滑到底部,开始出现大量异常。问题定位在备注,与上面的表情分析不谋而合。
原因
查看到TextSpan中的model中有×的符号。
查找来源,在高亮时发现了如下代码:
//2022-06-23 16:02:28 - 王太松200201 - 🖤 - 1
if (isKeywordNotEmpty && isRemarkNotEmpty) {
// 展开, 分割, 去重, toList, join
final k = words.join().split('').toSet().toList().join();
bloc.model?.remarkList?.forEach((e) {
final remark = e.content.split('').map<KeywordItem>((e) {
return KeywordItem(e, k.contains(e) ? Colors.red : Colors.black);
}).toList();
bloc.remarks.add(remark);
});
}
split('')会把一个表情切成两个unicode,而单独unicode被TextSpan渲染就出现异常。
总结
- 对异常捕获原理有很好的分析。
- 根据异常去看源码,有重点。
- 对于renderObject层报出的异常,找到一种新的解决方式。基本对flutter渲染有了全面的了解。
- 对于线上数据,客户端是无感知的,排查麻烦。与后端沟通是个很好的复现场景的方式。
- 对flutter渲染表情异常有深刻的认识。