You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多次调用Dialog是否会导致Flutter应用内存泄漏?

多次点击删除按钮后应用卡顿无响应的排查与解决

问题场景

列表内多个按钮绑定同一事件处理器,点击后执行以下流程:

  1. 向Bloc添加删除事件
  2. Bloc调用服务发起网络请求
  3. 网络请求成功后,Bloc emit新状态
  4. UI同步更新

初始几次点击运行正常,但多次操作后应用逐渐卡顿,最终完全无响应,需强制重启,怀疑存在内存泄漏。

相关代码

1. 点击删除按钮的事件绑定

child: GestureDetector(
  onTap: addressState.deletingAddress
      ? null
      : () async {
          bool shouldDelete =
              (await _showDeleteAddressDialog(address.title))!;
          if (shouldDelete && mounted) {
            context.read<AddressBloc>().add(
                  AddressEvent.deleteAddress(
                    addressId: address.id,
                    onError: (errorMessage) =>
                        snackbars.showSnackBar(
                      context: globalScaffoldKey.currentContext!,
                      text: errorMessage,
                    ),
                  ),
                );
          }
        },
  child: Container(
    color: Colors.transparent,
    width: 80.sp,
    height: 50.sp,
  ),
),

2. Bloc事件处理器(加载地址逻辑)

Future<void> _handleLoadAddresses(_LoadAddresses event, Emitter emit) async {
  if (state.loadingAddresses) return;

  emit(state.copyWith(loadingAddresses: true));

  final Either<String, List<Address>> result =
      await _addressManagementService.getAllAddresses();

  result.fold(
    (errorMessage) {
      emit(
        state.copyWith(
          loadingAddresses: false,
          loadingAddressesErrorMessage: errorMessage,
        ),
      );
      event.onError?.call(errorMessage);
    },
    (addresses) => emit(
      state.copyWith(
        addresses: UnmodifiableListView(
          addresses,
        ),
        loadingAddresses: false,
        loadingAddressesErrorMessage: null,
      ),
    ),
  );
}

3. 网络请求删除地址方法

Future<Either<String, void>> deleteAddress(int addressId) async {
    // ... 网络请求逻辑
    // return ...
} catch (e) {
    // return ...
}

4. 删除确认Dialog代码

Future<bool?> _showDeleteAddressDialog(String addressTitle) async =>
    dialogs.showActionDialog(
      context: context,
      // ... Dialog配置
      onBarrierDismissed: (context) => GoRouter.of(context).pop(false),
      onRightButtonPressed: (context) => GoRouter.of(context).pop(true),
      onLeftButtonPressed: (context) => GoRouter.of(context).pop(false),
    );

注:showActionDialog仅封装Flutter内置showDialog方法。

已做排查

  • 初次使用DevTools未发现明显内存飙升,仅存在调试模式常见帧卡顿
  • 重新排查后发现:多次点击删除按钮(打开Dialog并确认删除)后,GC进入持续触发循环,内存先增加约8MB,触发GC后回落相同幅度;内存占用最高的类为_List、_ListIterator和_WhereIterator,怀疑与Bloc删除事件处理器中使用List和where过滤列表有关。

排查与解决建议

  • 检查Bloc删除事件处理器逻辑:确认删除操作后过滤地址列表的代码是否每次都生成大量临时迭代器或未优化的列表对象,比如反复使用where而未将结果转换为固定列表,导致迭代器持续占用资源
  • 优化列表操作:对过滤后的列表提前缓存,或使用不可变集合的高效操作,减少临时_ListIterator、_WhereIterator的生成频率,降低GC压力
  • 检查上下文引用:确认globalScaffoldKey、Dialog上下文是否存在未释放的引用,比如onError回调中使用globalScaffoldKey.currentContext!是否导致上下文被长期持有
  • 禁用重复操作:确保deletingAddress状态正确控制按钮点击状态,避免重复触发删除事件,减少并发请求和状态更新带来的资源消耗
  • 内存快照对比:使用DevTools内存快照功能,对比多次点击前后的对象实例数量,跟踪_List、_ListIterator类的实例增长情况,定位具体泄漏点
  • 检查Bloc订阅:确认UI层是否存在未取消的Bloc状态订阅,每次点击都新增订阅会导致资源累积,最终引发卡顿

内容的提问来源于stack exchange,提问作者HYM

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 20:42:01