草庐IT

flutter 富文本渲染异常

某非著名程序员 2023-03-28 原文

起因

bugly捕捉到大量系统异常,所有方法都在render层,无法定位问题代码。

  1. 异常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)
  1. 异常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)
  1. 异常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)
  1. 异常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

  1. 在找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,),
)
  1. 新的SDK已修复这个问题,具体读者可自行测试。

探索\ud83d

  1. \ud83d是什么?只知道是unicode编码,什么情况下会出现这个字符?

\ud83d是unicode的一个码。特殊字符(包含emoji)的梳理,\ud83d是表情组成部分。
一个表情是由两个unicode码组成的,如?的unicode码就是\uD83D\uDDA4。

  1. 会不会是平台兼容性问题?如iOS的表情到Android导致的。
  2. 会不会是第三方输入法导致?搜狗、科大讯飞、百度等。

以上问题经过测试,表情都是以两个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渲染就出现异常。

总结

  1. 对异常捕获原理有很好的分析。
  2. 根据异常去看源码,有重点。
  3. 对于renderObject层报出的异常,找到一种新的解决方式。基本对flutter渲染有了全面的了解。
  4. 对于线上数据,客户端是无感知的,排查麻烦。与后端沟通是个很好的复现场景的方式。
  5. 对flutter渲染表情异常有深刻的认识。

有关flutter 富文本渲染异常的更多相关文章

  1. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  2. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  3. ruby-on-rails - Rails HTML 请求渲染 JSON - 2

    在我的Controller中,我通过以下方式在我的index方法中支持HTML和JSON:respond_todo|format|format.htmlformat.json{renderjson:@user}end在浏览器中拉起它时,它会自然地以HTML呈现。但是,当我对/user资源进行内容类型为application/json的curl调用时(因为它是索引方法),我仍然将HTML作为响应。如何获取JSON作为响应?我还需要说明什么? 最佳答案 您应该将.json附加到请求的url,提供的格式在routes.rb的路径中定义。这

  4. ruby-on-rails - Rails - 乐观锁定总是触发 StaleObjectError 异常 - 2

    我正在学习Rails,并阅读了关于乐观锁的内容。我已将类型为integer的lock_version列添加到我的articles表中。但现在每当我第一次尝试更新记录时,我都会收到StaleObjectError异常。这是我的迁移:classAddLockVersionToArticle当我尝试通过Rails控制台更新文章时:article=Article.first=>#我这样做:article.title="newtitle"article.save我明白了:(0.3ms)begintransaction(0.3ms)UPDATE"articles"SET"title"='dwdwd

  5. ruby - #之间? Cooper 的 *Beginning Ruby* 中的错误或异常 - 2

    在Cooper的书BeginningRuby中,第166页有一个我无法重现的示例。classSongincludeComparableattr_accessor:lengthdef(other)@lengthother.lengthenddefinitialize(song_name,length)@song_name=song_name@length=lengthendenda=Song.new('Rockaroundtheclock',143)b=Song.new('BohemianRhapsody',544)c=Song.new('MinuteWaltz',60)a.betwee

  6. ruby - 在 Ruby 中重新分配常量时抛出异常? - 2

    我早就知道Ruby中的“常量”(即大写的变量名)不是真正常量。与其他编程语言一样,对对象的引用是唯一存储在变量/常量中的东西。(侧边栏:Ruby确实具有“卡住”引用对象不被修改的功能,据我所知,许多其他语言都没有提供这种功能。)所以这是我的问题:当您将一个值重新分配给常量时,您会收到如下警告:>>FOO='bar'=>"bar">>FOO='baz'(irb):2:warning:alreadyinitializedconstantFOO=>"baz"有没有办法强制Ruby抛出异常而不是打印警告?很难弄清楚为什么有时会发生重新分配。 最佳答案

  7. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  8. SPI接收数据异常问题总结 - 2

    SPI接收数据左移一位问题目录SPI接收数据左移一位问题一、问题描述二、问题分析三、探究原理四、经验总结最近在工作在学习调试SPI的过程中遇到一个问题——接收数据整体向左移了一位(1bit)。SPI数据收发是数据交换,因此接收数据时从第二个字节开始才是有效数据,也就是数据整体向右移一个字节(1byte)。请教前辈之后也没有得到解决,通过在网上查阅前人经验终于解决问题,所以写一个避坑经验总结。实际背景:MCU与一款芯片使用spi通信,MCU作为主机,芯片作为从机。这款芯片采用的是它规定的六线SPI,多了两根线:RDY和INT,这样从机就可以主动请求主机给主机发送数据了。一、问题描述根据从机芯片手

  9. ruby-on-rails - rspec - 我怎样才能让 "pendings"有我的文本而不仅仅是 "No reason given" - 2

    我有这个代码:context"Visitingtheusers#indexpage."dobefore(:each){visitusers_path}subject{page}pending('iii'){shouldhave_no_css('table#users')}pending{shouldhavecontent('Youhavereachedthispageduetoapermissionic错误')}它会导致几个待处理,例如ManagingUsersGivenapractitionerloggedin.Visitingtheusers#indexpage.#Noreason

  10. ruby - 如何捕获 ruby​​ 中的所有异常? - 2

    我们如何捕获或/和处理ruby​​中所有未处理的异常?例如,这样做的动机可能是将某种异常记录到不同的文件或发送电子邮件给系统管理。在Java中我们会做Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandlerex);在Node.js中process.on('uncaughtException',function(error){/*code*/});在PHP中register_shutdown_function('errorHandler');functionerrorHandler(){$error=error_

随机推荐