亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Flutter實(shí)現(xiàn)資源下載斷點(diǎn)續(xù)傳的示例代碼

 更新時(shí)間:2022年07月28日 14:50:08   作者:Karl_wei  
在項(xiàng)目開(kāi)發(fā)中,特別是C端的產(chǎn)品,資源下載實(shí)現(xiàn)斷點(diǎn)續(xù)傳是非常有必要的。今天我們不講過(guò)多原理的知識(shí),分享下簡(jiǎn)單實(shí)用的資源斷點(diǎn)續(xù)傳

協(xié)議梳理

一般情況下,下載的功能模塊,至少需要提供如下基礎(chǔ)功能:資源下載、取消當(dāng)前下載、資源是否下載成功、資源文件的大小、清除緩存文件。而斷點(diǎn)續(xù)傳主要體現(xiàn)在取消當(dāng)前下載后,再次下載時(shí)能在之前已下載的基礎(chǔ)上繼續(xù)下載。這個(gè)能極大程度的減少我們服務(wù)器的帶寬損耗,而且還能為用戶減少流量,避免重復(fù)下載,提高用戶體驗(yàn)。

前置條件:資源必須支持?jǐn)帱c(diǎn)續(xù)傳。如何確定可否支持?看看你的服務(wù)器是否支持Range請(qǐng)求即可

實(shí)現(xiàn)步驟

1.定好協(xié)議。我們用的http庫(kù)是dio;通過(guò)校驗(yàn)md5檢測(cè)文件緩存完整性;關(guān)于代碼中的subDir,設(shè)計(jì)上認(rèn)為資源會(huì)有多種:音頻、視頻、安裝包等,每種資源分開(kāi)目錄進(jìn)行存儲(chǔ)。

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中通過(guò)CancelToken可控制
  void cancelDownload(CancelToken cancelToken);

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

  /// 檢查文件是否緩存成功,簡(jiǎn)單對(duì)比md5
  Future<String?> checkCachedSuccess(String url, {String? md5Str});
  
  /// 查看緩存文件的大小
  Future<int> cachedFileSize({String? subDir});

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

2.實(shí)現(xiàn)抽象協(xié)議,其中HttpManagerProtocol內(nèi)部封裝了dio的相關(guān)請(qǐng)求。

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驗(yàn)證不通過(guò)
      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下載,實(shí)現(xiàn)資源斷點(diǎn)續(xù)傳。

這里的邏輯比較重點(diǎn),首先未緩存100%的文件,我們以.temp后綴進(jìn)行命名,在每次下載時(shí)檢測(cè)下是否有.temp的文件,拿到其文件字節(jié)大??;傳入在header中的range字段,服務(wù)器就會(huì)去解析需要從哪個(gè)位置繼續(xù)下載;下載全部完成后,再把文件名改回正確的后綴即可。

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()) {
    // 文件存在時(shí)拿到已下載的字節(jié)數(shù)
    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請(qǐng)求頭,實(shí)現(xiàn)斷點(diǎn)續(xù)傳
          "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);
    }
  }
}

寫(xiě)在最后

這篇文章確實(shí)沒(méi)有技術(shù)含量,水一篇,但其實(shí)是實(shí)用的。這個(gè)斷點(diǎn)續(xù)傳的實(shí)現(xiàn)有幾個(gè)注意的點(diǎn):

  • 使用文件操作的方式,區(qū)分后綴名來(lái)管理緩存的資源;
  • 安全性使用md5校驗(yàn),這點(diǎn)非常重要,斷點(diǎn)續(xù)傳下載的文件,在完整性上可能會(huì)因?yàn)楦鞣N突發(fā)情況而得不到保障;
  • 在資源管理協(xié)議上,我們將下載、檢測(cè)、獲取大小等方法都抽象出去,在業(yè)務(wù)調(diào)用時(shí)比較靈活。

以上就是Flutter實(shí)現(xiàn)資源下載斷點(diǎn)續(xù)傳的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于Flutter資源下載斷點(diǎn)續(xù)傳的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 非常實(shí)用的Android圖片工具類(lèi)

    非常實(shí)用的Android圖片工具類(lèi)

    這篇文章主要為大家詳細(xì)介紹了非常實(shí)用的Android圖片工具類(lèi),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-06-06
  • Android實(shí)現(xiàn)QQ手機(jī)管家懸浮小火箭效果

    Android實(shí)現(xiàn)QQ手機(jī)管家懸浮小火箭效果

    這篇文章主要介紹了Android實(shí)現(xiàn)QQ手機(jī)管家懸浮小火箭效果,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-05-05
  • Android自定義View模仿虎撲直播界面的打賞按鈕功能

    Android自定義View模仿虎撲直播界面的打賞按鈕功能

    這篇文章主要介紹了Android自定義View模仿虎撲直播界面的打賞按鈕功能,文中介紹的非常詳細(xì),對(duì)各位Android開(kāi)發(fā)者們具有一定的參考價(jià)值,需要的朋友們下面來(lái)一起看看吧。
    2017-04-04
  • Android從觸碰屏幕開(kāi)始的事件采集解析及分發(fā)

    Android從觸碰屏幕開(kāi)始的事件采集解析及分發(fā)

    這篇文章主要為大家介紹了Android從觸碰屏幕開(kāi)始的事件采集解析及分發(fā),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-06-06
  • Android adb安裝apk時(shí)提示Invalid APK file的問(wèn)題

    Android adb安裝apk時(shí)提示Invalid APK file的問(wèn)題

    這篇文章主要介紹了Android adb安裝apk時(shí)提示Invalid APK file的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-08-08
  • Flutter?點(diǎn)擊兩次退出app的實(shí)現(xiàn)示例

    Flutter?點(diǎn)擊兩次退出app的實(shí)現(xiàn)示例

    本文主要介紹了Flutter?點(diǎn)擊兩次退出app的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-05-05
  • Android中 webView調(diào)用JS出錯(cuò)的解決辦法

    Android中 webView調(diào)用JS出錯(cuò)的解決辦法

    這篇文章主要介紹了Android中 webView調(diào)用JS出錯(cuò)的解決辦法,需要的朋友可以參考下
    2015-01-01
  • RecyclerView焦點(diǎn)跳轉(zhuǎn)BUG優(yōu)化的方法

    RecyclerView焦點(diǎn)跳轉(zhuǎn)BUG優(yōu)化的方法

    這篇文章主要介紹了RecyclerView焦點(diǎn)跳轉(zhuǎn)BUG優(yōu)化的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-04-04
  • Android自定義控制條效果

    Android自定義控制條效果

    這篇文章主要為大家詳細(xì)介紹了Android自定義控制條效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-07-07
  • Android RecyclerView的焦點(diǎn)記憶封裝

    Android RecyclerView的焦點(diǎn)記憶封裝

    這篇文章主要介紹了Android RecyclerView的焦點(diǎn)記憶封裝,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-04-04

最新評(píng)論