我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Flutter实现资源下载断点续传的示例代码

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Flutter实现资源下载断点续传的示例代码

协议梳理

一般情况下,下载的功能模块,至少需要提供如下基础功能:资源下载、取消当前下载、资源是否下载成功、资源文件的大小、清除缓存文件。而断点续传主要体现在取消当前下载后,再次下载时能在之前已下载的基础上继续下载。这个能极大程度的减少我们服务器的带宽损耗,而且还能为用户减少流量,避免重复下载,提高用户体验。

前置条件:资源必须支持断点续传。如何确定可否支持?看看你的服务器是否支持Range请求即可

实现步骤

1.定好协议。我们用的http库是dio;通过校验md5检测文件缓存完整性;关于代码中的subDir,设计上认为资源会有多种:音频、视频、安装包等,每种资源分开目录进行存储。

import 'package:dio/dio.dart';

typedef ProgressCallBack = void Function(int count, int total);

typedef CancelTokenProvider = void Function(CancelToken cancelToken);

abstract class AssetRepositoryProtocol {
  /// 下载单一资源
  Future<String> downloadAsset(String url,
      {String? subDir,
      ProgressCallBack? onReceiveProgress,
      CancelTokenProvider? cancelTokenProvider,
      Function(String)? done,
      Function(Exception)? failed});

  /// 取消下载,Dio中通过CancelToken可控制
  void cancelDownload(CancelToken cancelToken);

  /// 获取文件的缓存地址
  Future<String?> filePathForAsset(String url, {String? subDir});

  /// 检查文件是否缓存成功,简单对比md5
  Future<String?> checkCachedSuccess(String url, {String? md5Str});
  
  /// 查看缓存文件的大小
  Future<int> cachedFileSize({String? subDir});

  /// 清除缓存
  Future<void> clearCache({String? subDir});
}

2.实现抽象协议,其中HttpManagerProtocol内部封装了dio的相关请求。

class AssetRepository implements AssetRepositoryProtocol {
  AssetRepository(this.httpManager);

  final HttpManagerProtocol httpManager;

  @override
  Future<String> downloadAsset(String url,
      {String? subDir,
      ProgressCallBack? onReceiveProgress,
      CancelTokenProvider? cancelTokenProvider,
      Function(String)? done,
      Function(Exception)? failed}) async {
    CancelToken cancelToken = CancelToken();
    if (cancelTokenProvider != null) {
      cancelTokenProvider(cancelToken);
    }

    final savePath = await _getSavePath(url, subDir: subDir);
    try {
      httpManager.downloadFile(
          url: url,
          savePath: savePath + '.temp',
          onReceiveProgress: onReceiveProgress,
          cancelToken: cancelToken,
          done: () {
            done?.call(savePath);
          },
          failed: (e) {
            print(e);
            failed?.call(e);
          });
      return savePath;
    } catch (e) {
      print(e);
      rethrow;
    }
  }

  @override
  void cancelDownload(CancelToken cancelToken) {
    try {
      if (!cancelToken.isCancelled) {
        cancelToken.cancel();
      }
    } catch (e) {
      print(e);
    }
  }

  @override
  Future<String?> filePathForAsset(String url, {String? subDir}) async {
    final path = await _getSavePath(url, subDir: subDir);
    final file = File(path);
    if (!(await file.exists())) {
      return null;
    }
    return path;
  }

  @override
  Future<String?> checkCachedSuccess(String url, {String? md5Str}) async {
    String? path = await _getSavePath(url, subDir: FileType.video.dirName);
    bool isCached = await File(path).exists();
    if (isCached && (md5Str != null && md5Str.isNotEmpty)) {
      // 存在但是md5验证不通过
      File(path).readAsBytes().then((Uint8List str) {
        if (md5.convert(str).toString() != md5Str) {
          path = null;
        }
      });
    } else if (isCached) {
      return path;
    } else {
      path = null;
    }
    return path;
  }
  
  @override
  Future<int> cachedFileSize({String? subDir}) async {
    final dir = await _getDir(subDir: subDir);
    if (!(await dir.exists())) {
      return 0;
    }

    int totalSize = 0;
    await for (var entity in dir.list(recursive: true)) {
      if (entity is File) {
        try {
          totalSize += await entity.length();
        } catch (e) {
          print('Get size of $entity failed with exception: $e');
        }
      }
    }

    return totalSize;
  }

  @override
  Future<void> clearCache({String? subDir}) async {
    final dir = await _getDir(subDir: subDir);
    if (!(await dir.exists())) {
      return;
    }
    dir.deleteSync(recursive: true);
  }

  Future<String> _getSavePath(String url, {String? subDir}) async {
    final saveDir = await _getDir(subDir: subDir);

    if (!saveDir.existsSync()) {
      saveDir.createSync(recursive: true);
    }

    final uri = Uri.parse(url);
    final fileName = uri.pathSegments.last;
    return saveDir.path + fileName;
  }

  Future<Directory> _getDir({String? subDir}) async {
    final cacheDir = await getTemporaryDirectory();
    late final Directory saveDir;
    if (subDir == null) {
      saveDir = cacheDir;
    } else {
      saveDir = Directory(cacheDir.path + '/$subDir/');
    }
    return saveDir;
  }
}

3.封装dio下载,实现资源断点续传。

这里的逻辑比较重点,首先未缓存100%的文件,我们以.temp后缀进行命名,在每次下载时检测下是否有.temp的文件,拿到其文件字节大小;传入在header中的range字段,服务器就会去解析需要从哪个位置继续下载;下载全部完成后,再把文件名改回正确的后缀即可。

final downloadDio = Dio();

Future<void> downloadFile({
  required String url,
  required String savePath,
  required CancelToken cancelToken,
  ProgressCallback? onReceiveProgress,
  void Function()? done,
  void Function(Exception)? failed,
}) async {
  int downloadStart = 0;
  File f = File(savePath);
  if (await f.exists()) {
    // 文件存在时拿到已下载的字节数
    downloadStart = f.lengthSync();
  }
  print("start: $downloadStart");
  try {
    var response = await downloadDio.get<ResponseBody>(
      url,
      options: Options(
        /// Receive response data as a stream
        responseType: ResponseType.stream,
        followRedirects: false,
        headers: {
          /// 加入range请求头,实现断点续传
          "range": "bytes=$downloadStart-",
        },
      ),
    );
    File file = File(savePath);
    RandomAccessFile raf = file.openSync(mode: FileMode.append);
    int received = downloadStart;
    int total = await _getContentLength(response);
    Stream<Uint8List> stream = response.data!.stream;
    StreamSubscription<Uint8List>? subscription;
    subscription = stream.listen(
      (data) {
        /// Write files must be synchronized
        raf.writeFromSync(data);
        received += data.length;
        onReceiveProgress?.call(received, total);
      },
      onDone: () async {
        file.rename(savePath.replaceAll('.temp', ''));
        await raf.close();
        done?.call();
      },
      onError: (e) async {
        await raf.close();
        failed?.call(e);
      },
      cancelOnError: true,
    );
    cancelToken.whenCancel.then((_) async {
      await subscription?.cancel();
      await raf.close();
    });
  } on DioError catch (error) {
    if (CancelToken.isCancel(error)) {
      print("Download cancelled");
    } else {
      failed?.call(error);
    }
  }
}

写在最后

这篇文章确实没有技术含量,水一篇,但其实是实用的。这个断点续传的实现有几个注意的点:

  • 使用文件操作的方式,区分后缀名来管理缓存的资源;
  • 安全性使用md5校验,这点非常重要,断点续传下载的文件,在完整性上可能会因为各种突发情况而得不到保障;
  • 在资源管理协议上,我们将下载、检测、获取大小等方法都抽象出去,在业务调用时比较灵活。

以上就是Flutter实现资源下载断点续传的示例代码的详细内容,更多关于Flutter资源下载断点续传的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Flutter实现资源下载断点续传的示例代码

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Java实现断点续传功能的示例代码

这篇文章主要为大家详细介绍了如何利用Java语言实现网络资源的断点续传功能,文中的示例代码讲解详细,具有一定的学习价值,感兴趣的可以了解一下
2022-11-13

Android多线程断点续传下载功能实现代码

原理 其实断点续传的原理很简单,从字面上理解,所谓断点续传就是从停止的地方重新下载。 断点:线程停止的位置。 续传:从停止的位置重新下载。 用代码解析就是: 断点:当前线程已经下载完成的数据长度。 续传:向服务器请求上次线程停止位置
2022-06-06

Android(Java)下载断点续传的实现

Android(Java)下载断点续传的实现一、要注意的地方1. 追加文件2. 跳过输入流3.range header坑点1.坑点2坑点3.二、代码方法1方法2 最近在做一个下载文件的功能的时候,因为要支持断点续传,虽然整体上思路很清晰,也
2022-06-06

Android实现网络多线程断点续传下载实例

我们编写的是Andorid的HTTP协议多线程断点下载应用程序。直接使用单线程下载HTTP文件对我们来说是一件非常简单的事。那么,多线程断点需要什么功能?1.多线程下载,2.支持断点。使用多线程的好处:使用多线程下载会提升文件下载的速度。那
2022-06-06

Android通过HTTP协议实现断点续传下载实例

整理文档,搜刮出一个Android通过HTTP协议实现断点续传下载的代码,稍微整理精简一下做下分享。FileDownloader.java
2022-06-06

Android编程开发实现多线程断点续传下载器实例

本文实例讲述了Android编程开发实现多线程断点续传下载器。分享给大家供大家参考,具体如下: 使用多线程断点续传下载器在下载的时候多个线程并发可以占用服务器端更多资源,从而加快下载速度,在下载过程中记录每个线程已拷贝数据的数量,如果下载中
2022-06-06

详解Android使用OKHttp3实现下载(断点续传、显示进度)

OKHttp3是如今非常流行的Android网络请求框架,那么如何利用Android实现断点续传呢,今天写了个Demo尝试了一下,感觉还是有点意思准备阶段我们会用到OKHttp3来做网络请求,使用RxJava来实现线程的切换,并且开启Jav
2022-06-06

利用断点续传实现下载的原理是什么

今天就跟大家聊聊有关利用断点续传实现下载的原理是什么,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。HTTP断点续传报文要实现HTTP断点续传必须要简单了解以下几个报文。Accept-
2023-05-31

Go Gin实现文件上传下载的示例代码

Go Gin 实现文件的上传下载流读取文件上传routerrouter.POST("/resources/common/upload", service.UploadResource)service type: POSTdata:{“sav
2022-06-07

Android原生实现多线程断点下载实例代码

各位父老乡亲,我单汉三又回来了,今天为大家带来一个用原生的安卓写的多线程断点下载Demo。通过本文你可以学习到: SQLite的基本使用,数据库的增删改查。 Handler的消息处理与更新UI。 Service(主要用于下载)的进阶与
2023-05-31

android使用OkHttp实现下载的进度监听和断点续传

1. 导入依赖包// retrofit, 基于Okhttp,考虑到项目中经常会用到retrofit,就导入这个了。compile 'com.squareup.retrofit2:retrofit:2.1.0' // ButterKnifec
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录