今天碰到一个上传较大的视频文件到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}");
经测试,用上面方法上传大文件,内存占用平稳,最后真机测试,也没有再闪退。