android开发分享Flutter实现资源下载断点续传的示例代码

协议梳理一般情况下,下载的功能模块,至少需要提供如下基础功能:资源下载、取消当前下载、资源是否下载成功、资源文件的大小、清除缓存文件。而断点续传主要体现在取消当前下载后,再次下载时能在之前已下载的基础

上述就是android开发分享Flutter实现资源下载断点续传的示例代码的全部内容,如果对大家有所用处且需要了解更多关于Android学习教程,希望大家多多关注—计算机技术网(www.ctvol.com)!

协议梳理

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

前置条件:资源必须支持断点续传。如何确定可否支持?看看你的服务器是否支持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资源下载断点续传的资料请关注<计算机技术网(www.ctvol.com)!!>其它相关文章!

本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。

ctvol管理联系方式QQ:251552304

本文章地址:https://www.ctvol.com/addevelopment/1174647.html

(0)
上一篇 2022年8月3日
下一篇 2022年8月3日

精彩推荐