Android中Retrofit下载大文件为字节数组时遇OutOfMemoryError求解
解决Retrofit下载大文件时的OutOfMemoryError问题
嗨,我来帮你搞定这个问题!你现在遇到的OOM,核心原因是虽然加了@Streaming注解,但返回类型用了byte[]——这等于让Retrofit还是把整个文件的字节全部加载到内存里,大文件肯定会爆内存啊!@Streaming的作用是让Retrofit以流的方式处理响应,但必须配合正确的返回类型才能生效。
下面给你一步步的解决方案:
1. 修改Retrofit接口的返回类型
把原来返回byte[]改成ResponseBody,这样才能拿到原始的输入流,避免一次性加载整个文件:
@Streaming @GET(POOL + "DmListarDocsProjZip/{IdProjetct}") Call<ResponseBody> dmListarDocsProjZip(@Path("IdProjetct") int idProject);
2. 以流的方式逐步写入本地文件
在IntentService里调用接口时,不要直接把响应转成byte[],而是通过输入流分块读取,再写入本地文件。这样内存里只会保留一小段缓冲区的数据,不会爆内存:
private void downloadProjectDocs(int projectId) { // 这里替换成你的Retrofit实例创建逻辑 ApiService apiService = RetrofitClient.getInstance().create(ApiService.class); Call<ResponseBody> call = apiService.dmListarDocsProjZip(projectId); call.enqueue(new Callback<ResponseBody>() { @Override public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) { if (response.isSuccessful() && response.body() != null) { boolean isWriteSuccess = writeResponseToLocalFile(response.body()); if (isWriteSuccess) { // 这里处理文件下载成功的逻辑,比如发送广播通知UI sendBroadcast(new Intent("DOWNLOAD_SUCCESS_ACTION")); } else { // 文件写入失败的处理 } } else { // 请求失败的处理,比如打印错误信息 Log.e("DownloadError", "Request failed: " + response.message()); } } @Override public void onFailure(Call<ResponseBody> call, Throwable t) { // 网络请求失败的处理 Log.e("DownloadError", "Network failed: " + t.getMessage()); } }); } private boolean writeResponseToLocalFile(ResponseBody body) { File outputFile = new File(getExternalFilesDir(null), "project_" + System.currentTimeMillis() + ".zip"); InputStream inputStream = null; OutputStream outputStream = null; try { byte[] buffer = new byte[4096]; // 用4KB的缓冲区,平衡内存占用和读写效率 long totalSize = body.contentLength(); long downloadedSize = 0; inputStream = body.byteStream(); outputStream = new FileOutputStream(outputFile); int readBytes; while ((readBytes = inputStream.read(buffer)) != -1) { downloadedSize += readBytes; outputStream.write(buffer, 0, readBytes); // 如果需要显示下载进度,这里可以计算进度比例发送到UI // float progress = (float) downloadedSize / totalSize * 100; } outputStream.flush(); return true; } catch (IOException e) { e.printStackTrace(); return false; } finally { // 一定要关闭流资源,避免内存泄漏 try { if (inputStream != null) inputStream.close(); if (outputStream != null) outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } }
3. 额外注意事项
- 权限问题:如果你的应用运行在Android 6.0及以上,记得动态申请
WRITE_EXTERNAL_STORAGE(Android 10及以上建议用Scoped Storage,避免权限申请); - 线程问题:你用IntentService是正确的,它本身就在后台线程执行文件写入;如果用其他组件,一定要确保文件操作在后台线程,别阻塞主线程;
- 大文件进度:如果需要显示下载进度,可以在while循环里计算已下载比例,通过广播或者LiveData通知UI更新。
内容的提问来源于stack exchange,提问作者Levi Mendes
相关产品推荐
相关产品推荐

