转写说明

本文基于已校验的公开原文进行结构化转写与事实梳理,非原文转载。 转写保留可核验的技术事实,并将工程建议与来源观点明确分开。

核心结论

该问题根因并非图片解码耗时,而是 Dart 单线程事件循环被高频通知队列阻塞。WebSocket 未读数更新每秒触发多次全局 notifyListeners(),导致 UI 层的缩略图渲染通知被挤压到队列后方。实测场景中,Picker 返回后缩略图出现前的空档期达 17 秒,而同一时段未读数已跳动 10 次。

次生问题包括:架构拆分时中介者遗漏部分监听注册、筛选型 Notifier 在 build 中重复创建导致监听器泄漏、iOS 模拟器忽略 maxWidth 参数且 Flutter 隐式下采样行为不可靠。

能力机制

全链路埋点法:沿四层架构在每个跨层边界设置 Entry/Exit 日志,通过统一前缀过滤实现快速定位。调用链从 UI 点击延伸至系统相册调用,再回到 UI 渲染,共覆盖 9 个节点。

细粒度通知筛选:实现专用 ChangeNotifier 监听目标字段(selectedImages、语音状态等),隔离与 UI 渲染无关的状态变化,避免 WebSocket 未读数等高频事件触发不必要的 rebuild。

事件节流:使用 100ms 窗口合并通知,在首帧立即通知保证及时性,窗口内累积变更补发一次保证最终一致性。

显式图片下采样:弃用 Image.filecacheWidth 参数,改用 ResizeImage 强制指定解码尺寸,不依赖引擎隐式行为。

快速开始

日志前缀约定:在 Debug Console 使用 Check-images-bug 前缀过滤全链路日志。

节流实现模板

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
Timer? _notifyThrottleTimer;
bool _pendingNotify = false;
static const _throttleDuration = Duration(milliseconds: 100);

void _throttledNotifyListeners() {
  if (_notifyThrottleTimer == null) {
    notifyListeners();
    _notifyThrottleTimer = Timer(_throttleDuration, () {
      _notifyThrottleTimer = null;
      if (_pendingNotify && !_disposed) {
        _pendingNotify = false;
        notifyListeners();
      }
    });
  } else {
    _pendingNotify = true;
  }
}

显式下采样写法

1
2
3
4
5
6
7
8
9
Image(
  image: ResizeImage(
    FileImage(File(path)),
    width: 144,
    height: 144,
    allowUpscaling: false,
  ),
  fit: BoxFit.cover,
)

Picker 侧设置 requestFullMetadata: false 可跳过 EXIF 读取,在模拟器场景节省约 1 秒。

适用边界

该方案针对 Dart 单线程模型下的事件队列竞争问题,适用于 Flutter 应用中同时存在高频状态更新(如 WebSocket 推送)与 UI 敏感操作(如图片预览)的场景。

iOS 模拟器配合 HEIC 格式时,pickMultiImagemaxWidth 参数可能不生效,仍返回全尺寸原图。Flutter 3.x 在 ImageCache 命中后存在绕过 instantiateImageCodec 下采样分支的情况,此时 cacheWidth 参数不可靠。

中介者模式拆分类时,必须确保 addListenerremoveListener 成对出现,否则会导致通知链路断裂或监听器泄漏。筛选型 Notifier 应作为 State 字段管理生命周期,避免在 build 方法中创建。

核验清单

架构拆分完成后,检查 AiChatCoordinator_setupCrossControllerListeners 方法中各子控制器的监听注册数量是否与 dispose 中的 removeListener 调用数量相等。

检查 InputBar 相关 Notifier 是否作为 State 字段单例存在,而非每次 build 时新建。若存在泄漏,可观察到同一通知被回调次数递增。

验证 SessionEventController 或类似高频通知源是否已实现节流机制。原始每秒 8-12 次通知经节流后应控制在 10 次以内。

验证缩略图加载路径是否使用 ResizeImage 显式指定尺寸,而非依赖 cacheWidth 参数隐式下采样。解码耗时目标应低于 50ms。

Picker 返回后缩略图出现前的空档期应控制在毫秒级,体感无等待。端到端耗时应控制在 5 秒以内,其中系统相册选图和文件拷贝时间不可控。

来源与核验

  • 原始文章
  • 页面事实以原始来源及其引用的官方资料为准;版本、星标和模型能力会随时间变化。
  • AI Stack 不公开抓取到的全文快照,只发布独立转写与来源入口。

站内链接

相关文章