Flutter 队列任务的实现
作者:BlackDream 发布时间:2023-07-07 17:25:14
前言
在电商的应用中,最常见的就是在首页或完成某事件之后,弹出一堆的活动/广告。假如重叠弹出,很丑,给用户的体验也不好,所以一般都会依次依条件的弹出。
下面讲讲我是怎么实现一个方便的队列任务管理。
队列
任务队列,那当然要有个队列。这个队列的任务内容应该是返回Future
的Function
,因为我需要得到他处理完成的结果,比如等待弹窗关闭时return
,才表示这个任务被完成。
typedef TaskEventFunction = Future Function();
class TaskQueue {
List<TaskEventFunction> _actionQueue = [];
}
添加任务进队列
然后是加入队列的方法add
void add(TaskEventFunction task) {
_actionQueue.add(task);
}
然后想到,队列是不是最好去重?或者可选去重。
那一个Function
如何去重呢,我们可以使用它的hashCode,同一个Function
的hashCode相同。
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
中,可能执行完之后又不满足了,根据具体情况考虑。
一般来说类似弹窗的return
或await showDialog
就可以等待弹窗页面结束,再进行下一个。
跨页面还是当前页面的控制,只需要考虑是全局实例TaskQueue
还是页面内实例TaskQueue
。
这样一个使用简单好用的任务队列就实现好了!
来源:https://juejin.cn/post/7108642127373926436


猜你喜欢
- 使用版本:spring-boot: 2.1.6.RELEASEsping: 5.1.8.RELEASEjava: openjdk 11.0.
- 短信是手机常见的功能,本文就以实例形式讲述了Android实现将已发送的短信写入短信数据库的方法。分享给大家供大家参考之用。具体如下:一般来
- 收费版本:Rainbow Brackets免费版本:Rainbow Brackets Lite介绍一款可以将 (圆括号) [方括号] {花括
- 前言Spring Data Jpa框架的目标是显著减少实现各种持久性存储的数据访问层所需的样板代码量。Spring Data Jpa存储库抽
- 1、使用JPA 的@Enumerated 注解 ,可以直接将Enum映射到数据库中。但是value的值只有两种方式选择,一种是使用枚举的序号
- 背景介绍在开发应用过程中经常会遇到显示一些不同的字体风格的信息犹如默认的LockScreen上面的时间和充电信息。对于类似的情况,可能第一反
- 1.查找文件find / -name filename.txt 根据名称查找/目录下的filename.txt文件。find . -name
- 简介Android动画主要包括视图动画和属性动画,视图动画包括Tween动画和Frame动画,Tween动画又包括渐变动画、平移动画、缩放动
- 以前的Java项目中充斥了太多不友好的代码:POJO的Getter/Setter/toString等等,这些代码由于没有什么技术含量,影响了
- Spark_SQL性能调优众所周知,正确的参数配置对提升Spark的使用效率具有极大助力,帮助相关数据开发、分析人员更高效地使用Spark进
- 场景既然要搞懂Redis分布式锁,那肯定要有一个需要它的场景。高并发售票问题就是一个经典案例。搭建环境准备redis服务,设置redis的键
- 如果需要使用同一类型的多个对象,可以使用数组和集合(后面介绍)。C#用特殊的记号声明,初始化和使用数组。Array类在后台发挥作用,它为数组
- GPS定位是目前很多手机都有的功能,且非常实用。本文以实例形式讲述了Android中GPS定位的用法。分享给大家供大家参考之用。具体方法如下
- 前言消息队列中间件是分布式系统中重要的组件,主要解决应用耦合、异步消息、流量削锋等问题,实现高性能、高可用、可伸缩和最终一致性架构,是大型分
- 按官方修改的示例:#MidServerClient.javaimport feign.Param;import org.springfram
- 1.非静态成员变量当成员变量为非静态成员变量且对当前类进行实例化时,将会产生死循环例子:public class ConstructorCl
- Java 读取外部资源的方法详解在Java代码中经常有读取外部资源的要求:如配置文件等等,通常会把配置文件放在classpath下或者在we
- 一、概述最近需要用进度条,秉着不重复造轮子的原则,上github上搜索了一番,看了几个觉得比较好看的ProgressBar,比如:daima
- 在web开发中,我们可能会有这样的需求,为了便于前台的JS的处理,我们需要将查询出的数据源格式比如:List<T>、DataTa
- 前言今天我们继续聊聊在SprinBoot中如何集成参数校验Validator,以及参数校验的高阶技巧(自定义校验,分组校验)。&ld