欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

Flutter 队列任务的实现,

来源: 开发者 投稿于  被查看 32046 次 评论:282

Flutter 队列任务的实现,


目录
  • 前言
  • 队列
    • 添加任务进队列
    • 移除队列指定任务
    • 判断是否包含对应任务
  • 执行队列任务
    • 任务条件
      • 添加任务时加入条件
      • 执行任务前判断条件是否满足
    • 使用和总结

      前言

      在电商的应用中,最常见的就是在首页或完成某事件之后,弹出一堆的活动/广告。假如重叠弹出,很丑,给用户的体验也不好,所以一般都会依次依条件的弹出。

      下面讲讲我是怎么实现一个方便的队列任务管理

      队列

      任务队列,那当然要有个队列。这个队列的任务内容应该是返回FutureFunction,因为我需要得到他处理完成的结果,比如等待弹窗关闭时return,才表示这个任务被完成。

      typedef TaskEventFunction = Future Function();
      
      class TaskQueue {
          List<TaskEventFunction> _actionQueue = [];
      }

      添加任务进队列

      然后是加入队列的方法add

      void add(TaskEventFunction task) {
        _actionQueue.add(task);
      }

      然后想到,队列是不是最好去重?或者可选去重。

      那一个Function如何去重呢,我们可以使用它的hashCode,同一个FunctionhashCode相同

      taskQueue.add(testFunction);
      
      Future testFunction() async {
        await Future.delayed(const Duration(milliseconds: 1000));
        return Future.value(true);
      }

      即我们可以按上面这样定义,同一个类同一个实例下同一个Function,就是相同的hashCode。若是以下这种写法则hashCode不一样,每次add都相当于一个新的Function

      taskQueue.add(() async {
        await Future.delayed(const Duration(milliseconds: 1000));
        return Future.value(true);
      });

      假如不需要去重,可以用第二种。也可以主动指定taskId来选择哪些需要去重(即使内容不一样),哪些不需要。

      修改一下add,增加taskId。同时hashCode应该作为主要的key,修改一下队列类型。 最终如下:

      /// 任务编号队列
      List<String> _actionQueue = [];
      
      /// 任务队列
      Map<String, TaskEventFunction> _actionMap = {};
      
      /// 指定taskId 为 -1 则不用去重
      String add(TaskEventFunction task, {
        String? taskId,
      }) {
        String? id = taskId;
        id ??= task.hashCode.toString();
        bool isContains = false;
        if (taskId != '-1') {
          isContains = _actionQueue.contains(id);
        } else {
          id = task.hashCode.toString();
        }
        if (!isContains) {
          _actionQueue.add(id);
          _actionMap[id] = task;
        }
        return id;
      }

      -1时则不去重,也把最终的taskId返回。

      移除队列指定任务

      有添加任务的方法,那也需有移除任务的方法

      /// 这里需注意,add的时taskId为-1,那就直接把task传入,add时有返回,可以对应
      bool remove(TaskEventFunction task, {String? taskId}) {
        String? id = taskId;
        id ??= task.hashCode.toString();
        if (_actionQueue.contains(id)) {
          _actionMap.remove(id);
          return _actionQueue.remove(id);
        }
        return false;
      }

      taskId/hashCode为准。

      判断是否包含对应任务

      使用者可以自己判断是否已包含对应的任务

      /// 是否队列中包含该任务
      /// [task] 与 [taskId] 应该只传一个
      bool containers({TaskEventFunction? task, String? taskId}) {
        assert(task != null || taskId != null);
        String? id = taskId;
        id ??= task.hashCode.toString();
        return _actionQueue.contains(taskId);
      }

      执行队列任务

      任务队列的进出基本成型,开始处理任务。很简单,取出任务,然后执行任务,最后移除任务

      void startLoop() async {
        if (dealing || _actionQueue.isEmpty) {
          return;
        }
        dealing = true;
        String taskId = _actionQueue.first;
        TaskEventFunction? callback = _actionMap[taskId];
        if (callback == null) {
          _actionQueue.remove(taskId);
          return;
        }
      
        try {
          await callback();
        } catch (e) {
          log('_actionQueue 出错 $e');
        } finally {
          _actionQueue.remove(taskId);
          _actionMap.remove(taskId);
          dealing = false;
          if (_actionQueue.isNotEmpty) {
            startLoop();
          }
        }
      }

      这里加了个dealing,表示任务正在处理中的状态,正在处理的话,不允许执行下一个任务。

      在执行完成(或失败)后,自动触发下一个任务。add中也可以加入startLoop,使添加任务后自动启动任务循环。

      任务条件

      基本的任务队列已经完成。不过每个任务其实一般都会有个条件,确认符合当前场景才执行。比如说的确是新人且在首页,才显示新人优惠弹窗。

      条件可以是整个任务队列统一的条件,也可以是某个任务特定的条件

      typedef TaskConditionFunction = FutureOr<bool> Function();

      这里类型用FutureOr<bool>,有可能需要等待一下才能确认条件。任务对应的条件,也根据taskId来存储。

      /// 任务条件
      Map<String, TaskConditionFunction> _actionCondition = {};
      
      /// 总条件
      TaskConditionFunction? _condition;

      添加任务时加入条件

      TaskEventFunction add(TaskEventFunction task, {
        String? taskId,
        TaskConditionFunction? condition,
      }) {
          ...
          
          if (condition != null && !_actionCondition.containsKey(id)) {
            _actionCondition[id] = condition;
          }
      
      }

      设置总条件,或补充条件

      /// 设置允许执行条件
      void setAcceptConditions(TaskConditionFunction condition,
          {String? taskId}) {
        if (taskId == null) {
          _condition = condition;
        } else {
          _actionCondition[taskId] = condition;
        }
      }

      执行任务前判断条件是否满足

          bool canNext = await _nextConditions(taskId);
          if (canNext) {
            try {
              await callback();
            } catch (e) {
              log('_actionQueue 出错 $e');
            } finally {
              _actionQueue.remove(taskId);
              _actionMap.remove(taskId);
              dealing = false;
              if (_actionQueue.isNotEmpty) {
                startLoop();
              }
            }
          } else {
            // 不满足条件一般后续的也不会执行
            dealing = false;
          }
      Future<bool> _nextConditions([String? id]) async {
        String taskId = id ?? _actionQueue.first;
        bool canNext = true;
        var taskCondition = _actionCondition[taskId];
        if (_condition != null) {
          canNext = await _condition!.call();
        }
        if (canNext && taskCondition != null) {
          canNext = await taskCondition();
        }
      
        return Future.value(canNext);
      }

      原则上应该既满足总条件也满足任务条件

      使用和总结

      实例化TaskQueue

      TaskQueue taskQueue = TaskQueue();

      添加任务并开始任务

      taskQueue.add(testFunction, condition: () async {
        await Future.delayed(const Duration(milliseconds: 200));
        return Future.value(true);
      });
      taskQueue.startLoop();
      
      Future testFunction() async {
        return showDialog(...);
      }

      注意设置条件要返回bool

      还可以加入类似延时等操作,跟条件一样配置即可。太久的操作不要像上面一样写在condition中,可能执行完之后又不满足了,根据具体情况考虑。

      一般来说类似弹窗的returnawait showDialog就可以等待弹窗页面结束,再进行下一个。

      跨页面还是当前页面的控制,只需要考虑是全局实例TaskQueue还是页面内实例TaskQueue

      这样一个使用简单好用的任务队列就实现好了,更多相关Flutter 队列任务内容请搜索3672js教程以前的文章或继续浏览下面的相关文章希望大家以后多多支持3672js教程!

      您可能感兴趣的文章:
      • 详解Flutter自定义应用程序内键盘的实现方法
      • Flutter实现切换应用时隐藏应用预览
      • Flutter获取ListView当前正在显示的Widget信息(应用场景)
      • Android利用Flutter path绘制粽子的示例代码
      • 基于Flutter实现多边形和多角星组件
      • Flutter WebView 预加载实现方法(Http Server)
      • 基于flutter sound插件实现录音与播放功能
      • Flutter添加页面过渡动画实现步骤
      • flutter项目引入iconfont阿里巴巴图标

      用户评论