You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 09:43:28