【Flutter】flutter_local_notifications并发下载任务通知实践

大概就是我在项目过程中并发下载任务弹通知遇到的一些问题,跟flutter_local_notifications关系也不大。

文章基本是AI生成,有些修改。

初始化

dart 复制代码
// 正确的初始化
import 'package:flutter_local_notifications/flutter_local_notifications.dart';

final _notifications = FlutterLocalNotificationsPlugin();

Future<void> initializeNotifications() async {
  // 引用 res/drawable/ic_notification.xml
  const AndroidInitializationSettings settings =
      AndroidInitializationSettings('ic_launcher'); //通知小图标

  await _notifications.initialize(InitializationSettings(android: settings));
}

这里有两个问题要注意,一是图标需要svg或者是png,其他的可能也行,不过webp的是显示不出来的,二是要在android目录下的,不是在flutter目录下的,我是放drawable里了,mipmap里应该也可能,没去试。以上都是经验之谈。

2. 实现下载进度条通知

展示下载进度是通知的一个核心应用场景,关键在于正确配置 AndroidNotificationDetails

  • 问题 :进度条的 progressmaxProgress 参数只接受 int 类型,而下载进度是 double,直接转换会导致进度条"跳跃"。
  • 解决方案提升精度 。将浮点数进度"放大"为整数。
    • 策略 :将每个文件的总进度视为100。N个文件的最大进度 (maxProgress) 就是 N * 100。当前总进度 (progress) 则是所有文件 double 进度之和再乘以100。
dart 复制代码
double currentTotalProgress; // 例如: 2.5 (表示完成了2个文件,第3个下载了一半)
int totalFiles; // 例如: 5

final int maxProgress = totalFiles * 100;
final int currentProgress = (currentTotalProgress * 100).toInt();

final androidDetails = AndroidNotificationDetails(
  'download_channel',
  '下载进度',
  importance: Importance.low, // 使用 low importance 避免打扰
  priority: Priority.low,
  onlyAlertOnce: true,        // 只在第一次显示时提醒
  showProgress: true,
  progress: currentProgress,
  maxProgress: maxProgress,
  ongoing: true,              // 设为"进行中",用户无法轻易划掉
);

其实onReceiveProgress的两个参数都是int。

dart 复制代码
await _dio.download(
            fileUrl,
            savePath,
            onReceiveProgress: (received, total) {
    ......
}

之所以说下载进度是 double,是因为我要将进度返回给UI,所以单个文件的进度是received / total 。

多个文件的总进度可以将各个文件的received用map存起来,然后相加/下载的文件总数,因为total一般就是1。

3. 处理并发下载任务的通知

当应用支持同时进行多个下载任务时,如何管理通知以避免混乱是一个挑战。

  • 问题1:多个下载任务的进度更新到了同一个通知上。

    • 原因 :并发任务共享了 Controller 中的成员变量作为通知ID和进度数据源,导致状态竞争和数据污染。
    • 解决方案状态隔离 。为每个下载批次创建一个唯一的、自增的 notificationId,并将该批次的所有状态(如进度Map、节流计时器)都作为局部变量 或封装在独立的状态对象中进行管理,杜绝共享。
  • 问题2:多个独立的通知因高频更新而在通知栏"上下跳动"。

    • 原因 :每次调用 notifications.show() 更新通知,系统都会刷新其时间戳,而通知列表是按时间倒序排列的。
    • 解决方案固定排序依据 。通过 AndroidNotificationDetailswhen 参数为每个通知批次设置一个固定的"伪时间戳"
      • 策略 :在第一个任务启动时记录一个基准时间戳 baseTimestamp。对于每个任务,其伪时间戳为 baseTimestamp - notificationId。由于ID递增,这个伪时间戳是递减的,从而实现了通知按ID升序的固定排序
dart 复制代码
// 在 Controller 中
static int _notificationIdCounter = 0;
int? _baseTimestamp;

// 在下载方法中
final int notificationId = ++_notificationIdCounter;
if (_baseTimestamp == null) {
  _baseTimestamp = DateTime.now().millisecondsSinceEpoch;
}
final int pseudoTimestamp = _baseTimestamp! - notificationId;

// 在创建 AndroidNotificationDetails 时
final androidDetails = AndroidNotificationDetails(
  // ...
  when: pseudoTimestamp,  // 固定排序依据
  showWhen: false,        // 隐藏这个"假"的时间戳,UI更干净
  // ...
);

// 所有下载任务结束后,重置 _baseTimestamp
if (all_tasks_are_finished) {
    _baseTimestamp = null;
}

就是每个通知都用跟这个通知绑定的局部变量,不要公用全局的。

ID不要用同一个,会直接更新替换前面的。

4. 性能优化:节流 (Throttling)

dio 等网络库的 onReceiveProgress 回调频率极高,直接在回调中更新通知会造成严重的性能问题和UI延迟。

  • 问题 :高频调用 notifications.show() 导致系统 NotificationManager 负担过重,UI响应延迟。
  • 解决方案 :在Dart层节流 ,控制通知的更新频率。
    • 策略 :使用一个时间戳记录上次更新通知的时间。只有当距离上次更新超过一定间隔(如250-500毫秒)时,才真正调用 notifications.show()
dart 复制代码
DateTime? lastUpdateTime; // 批次的局部节流计时器

// ... onReceiveProgress 回调 ...
final now = DateTime.now();
if (lastUpdateTime != null && 
    now.difference(lastUpdateTime!).inMilliseconds < 500 &&
    received < total) { // 确保最后一次(完成时)总能更新
  return; // 时间太短,跳过本次更新
}
lastUpdateTime = now;

// ... 执行真正的通知更新逻辑 ...

不节流的话,通知的下载进度条会比实际进度慢个百分之二三十,因为NotificationManager来不及处理消息队列。

相关推荐
程序员Ctrl喵16 小时前
异步编程:Event Loop 与 Isolate 的深层博弈
开发语言·flutter
前端不太难18 小时前
Flutter 如何设计可长期维护的模块边界?
flutter
小蜜蜂嗡嗡19 小时前
flutter列表中实现置顶动画
flutter
始持19 小时前
第十二讲 风格与主题统一
前端·flutter
始持19 小时前
第十一讲 界面导航与路由管理
flutter·vibecoding
始持19 小时前
第十三讲 异步操作与异步构建
前端·flutter
新镜20 小时前
【Flutter】 视频视频源横向、竖向问题
flutter
黄林晴20 小时前
Compose Multiplatform 1.10 发布:统一 Preview、Navigation 3、Hot Reload 三箭齐发
android·flutter
Swift社区21 小时前
Flutter 应该按功能拆,还是按技术层拆?
flutter
肠胃炎21 小时前
树形选择器组件封装
前端·flutter