[Flutter] 大文件上传之随传随处理(避免占用大量内存)

今天碰到一个上传较大的视频文件到S3引发闪退的问题。经查此问题产生的原因是内存溢出,连个闪退日志都没有。

这个上传使用的是第三方的插件,我是用 uploadFileStream 来上传文件的,查看其实现代码,它使用的是http插件的 http.StreamedRequest, 它会把文件分块读出来,添加分块签名,再使用 request.sink.add(xxx) 加入缓冲区, 最后调用 request.send() 来完成发送。

这样问题就来了,它会把整个文件外加签名信息都放到缓冲区,意味着文件越大,也就占用更多的内存,最终导致崩溃的发生。

由于需要对文件进行签名处理,不能直接使用 dio 插件文件上传方式(说不定dio也会有同样的问题,还没来得及细品)。http 插件也没有提供边读边处理边发送的方法,问题限入卡顿状态,在网上搜索半天也没有找到一个解决方案,最后想想,能不能直接用最基础的 HttpClient 来解决呢?

因为平常主要用dio和http这两个插件,没有用过HttpClient,没有认真研究过它。这个时候想起来它,就马上细品起来,最终真的找到的解决方案。还真是越低级的封装,关键时候越能解决问题。

 

下面给出使用 HttpClient 解决上面问题的关键代码:

    // 初始化一个Http客户端,并加入自定义Header
    var req = await HttpClient().putUrl(uri);
    headers.forEach((key, value) {
      req.headers.add(key, value);
    });

    // 读文件
    var s = await file.open();
    var x = 0;
    var size = file.lengthSync();
    var chunkSize = 65536;
    while (x < size) {
          var _len = size - x >= chunkSize  ? chunkSize : size - x;
          val = s.readSync(_len).toList();
          x = x + _len;
          // 处理数据块
          val = proc(val);
          // 加入http发送缓冲区
          req.add(val);
          // 立即发送并清空缓冲区
          await req.flush();
    }
    await s.close();
    
    // 文件发送完成
    await req.close();
    // 获取返回数据
    final response = await req.done;
    // 其它处理逻辑
    print("response statusCode: ${resp.statusCode}");

 

经测试,用上面方法上传大文件,内存占用平稳,最后真机测试,也没有再闪退。

 

posted @ 2020-06-11 18:02  我爱我家喵喵  阅读(3839)  评论(2编辑  收藏  举报