Flutter中因为节流函数差点引发的血案

200 阅读3分钟

有一天,测试吴找到我说:“蔡工,这里有你的bug,麻烦看一下”。然后在手机上操作了一通,是我正在开发的app中某个搜索页面,按钮点击偶尔会失灵的问题。

我按照他的步骤也跟着操作了一遍,并没有问题,说道:“我操作怎么没问题,你确定这里有bug?”。

“肯定是有问题的,我再操作一次给你看,先这样……,然后再这样……”。测试吴又操作了一遍,然而,这一次bug没有如期而至,他尴尬的看着我:“这个bug不是必现的,具有偶发性,但我确定这里是有问题的,我再试给你看”。

看着测试吴在手机上捣鼓了十几分钟还没有想走的意思,我从抽屉中,默默的抽出了一把40厘米的大刀,往桌上一放,面带微笑的说:“没事,我有时间,我等你……”

只见测试吴宛如闪电般,从我的视野中消失了。看着他离去的身影,我满意的点了点头,收起了桌子上的大刀。

这个场面是否如何熟悉?没错,这就是开发与测试的日常。当然,作为一名品格高尚的资深码农,我当然不会抽出40厘米的大刀,我会非常非常耐心的排查每一个bug……

回到正题,今日的bug有些棘手,要反复查看代码才能发现端倪。问题如下图,点击按钮1,再快速点击按钮2,就会出现按钮2无法点击的问题

image.pngimage.png

按钮1和按钮2都是使用了一个叫AppButton的组件。在onPressed事件中,某师兄为了解决按钮重复点击问题,调用了Utils.throttle节流函数。从而引发了此次bug。

class AppButton extends ButtonStyleButton {
  AppButton({
    Key? key,
    required VoidCallback? onPressed,
    VoidCallback? onLongPress,
    ValueChanged<bool>? onHover,
    ValueChanged<bool>? onFocusChange,
    FocusNode? focusNode,
    bool autofocus = false,
    Clip clipBehavior = Clip.none,
    this.textStyle,
    this.padding,
    this.buttonStyle = AppButtonStyle.primary,
    required String text,
  }) : super(
          key: key,
          /// 解决按钮重复点击,调用了Utils.throttle节流函数
          onPressed: Utils.throttle(onPressed!),
          onLongPress: onLongPress,
          onHover: onHover,
          onFocusChange: onFocusChange,
          style: null,
          focusNode: focusNode,
          autofocus: autofocus,
          clipBehavior: clipBehavior,
          child: Text(
            text,
           ),
          ),
        );
    /// ...
}
/// 功能工具类
class Utils {
  static var enable = true;

  ///防止重复点击  规定时间内只触发一次
  ///func 要执行的方法
  static Function() throttle(
    Function func, {
    Duration delay = const Duration(milliseconds: 1000),
  }) {
    return () {
      if (enable) {
        enable = false;
        func();
        Future.delayed(delay, () {
          enable = true;
        });
      }
    };
  }
}

最终把问题锁定在Utils.throttle函数,因为按钮1和按钮2都调用了同一个函数,按钮1点击时,1s的时间内enable变量还处于false状态,按钮2就会无法执行func方法,要等1s过后才能再次点击。

所以最大的问题就是两个不相干的按钮,却因为同一个throttle函数引发了按钮1节流了按钮2。

知道了问题的原因,就能对症下药,解决的思路也很简单,就是判断多次点击是否来自同一个按钮,不同按钮当然就让它们不相互干扰。

/// 功能工具类
class Utils {
  static var enable = true;

  /// [bug修复]:
  /// 问题:如果两个按钮A和B同时调用了throttle方法,它们就会相互影响,
  /// 就会出现按钮A点击后[delay]时间内无法点击B,因为它们共用了[enable]。
  ///
  /// 解决:增加[_hashCode]变量,将[func]的hashCode缓存起来,不同的func hashCode视为不同按钮,
  /// 不同按钮重置enable,同个hashCode刚不受此规则影响。
  ///
  /// 注:hashCode是什么?
  /// hashCode是dart为每个对象生成的随机值,代表这个对象运行时的身份编码,具有唯一性。
  static int? _hashCode;

  ///防止重复点击  规定时间内只触发一次
  ///func 要执行的方法
  static Function() throttle(
    Function func, {
    Duration delay = const Duration(milliseconds: 1000),
  }) {
    return () {
      if (_hashCode != func.hashCode) {
        enable = true;
        _hashCode = func.hashCode;
      }
      if (enable) {
        enable = false;
        func();
        Future.delayed(delay, () {
          enable = true;
        });
      }
    };
  }
}

解决完bug,测试吴向我竖起了大拇指,给了我一波赞,然后高高兴兴的离开了。看着他离去的背影,我默默的把抽屉关紧,心里暗想:“还好把问题解决了,不然就要晾出40厘米的大刀,才能把事情摆平……”。