今天碰到一個上傳較大的視頻文件到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}");
經測試,用上面方法上傳大文件,內存占用平穩,最后真機測試,也沒有再閃退。