起因
bugly捕捉到大量系統(tǒng)異常,所有方法都在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中調(diào)用build()和updateChild()方法。在StatelessElement和StatefulElement中通過Widget build() => state.build(this);調(diào)用到自定義widget中的build,其中build發(fā)生異常,被try...catch捕捉到堆棧,定位到具體的代碼行數(shù)。
//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層調(diào)用,用try...catch捕捉。RenderObejct是由element管理,在framework層面,不經(jīng)過開發(fā)者。捕捉到的堆棧在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();
}
}
小結(jié):flutter異常多數(shù)都是null value,例如build方法中出現(xiàn)數(shù)組越界,空異常等;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編碼,什么情況下會出現(xiàn)這個字符?
\ud83d是unicode的一個碼。特殊字符(包含emoji)的梳理,\ud83d是表情組成部分。
一個表情是由兩個unicode碼組成的,如??的unicode碼就是\uD83D\uDDA4。
- 會不會是平臺兼容性問題?如iOS的表情到Android導(dǎo)致的。
- 會不會是第三方輸入法導(dǎo)致?搜狗、科大訊飛、百度等。
以上問題經(jīng)過測試,表情都是以兩個unicode碼成對出現(xiàn)的,不會出現(xiàn)問題。答案就在眼前,但不知道原因。查看退貨查驗代碼,有兩處使用Text.rich。一個是商品cell,一個是備注。
與后端溝通
考慮與后端溝通,根據(jù)bugly時間獲取前后的時間的返回報文。
第一次:沒有復(fù)現(xiàn)。
第二次思考信息更全面,同時也意識到不足。如掃碼時是否能帶上code碼,方便排查。后期有userId,比較好過濾。同時對后端的過濾規(guī)則有一定了解。
這次把detail和keyword接口都查到了。在最新的代碼沒有復(fù)現(xiàn)。想到切到問題版本,問題必現(xiàn)了。
進入退貨查驗,滑到底部,開始出現(xiàn)大量異常。問題定位在備注,與上面的表情分析不謀而合。
原因
查看到TextSpan中的model中有×的符號。
查找來源,在高亮?xí)r發(fā)現(xiàn)了如下代碼:
//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渲染就出現(xiàn)異常。
總結(jié)
- 對異常捕獲原理有很好的分析。
- 根據(jù)異常去看源碼,有重點。
- 對于renderObject層報出的異常,找到一種新的解決方式。基本對flutter渲染有了全面的了解。
- 對于線上數(shù)據(jù),客戶端是無感知的,排查麻煩。與后端溝通是個很好的復(fù)現(xiàn)場景的方式。
- 對flutter渲染表情異常有深刻的認(rèn)識。