升级okhttp3后将PDF的ResponseBody写入外部存储报closed异常如何解决
问题修复方案
问题根因
- 调用
body.bytes()抛出closed异常:OkHttp的ResponseBody的bytes()方法读取完全部响应内容后会自动关闭底层流,后续再调用byteStream()读取时流已经被释放,因此抛出状态异常。 - 使用
body.contentLength()时写入失败的两个核心原因:- 你配置的
HttpLoggingInterceptor日志级别为BODY,高版本OkHttp中该级别会消费整个响应体内容用于打印,大文件场景下不仅会占用大量内存,还会导致后续你拿到的ResponseBody流已经为空,写入无内容。 - 原有代码逻辑存在疏漏:异常弹出的Toast未调用
.show()方法,出现异常时你无法收到错误提示;流未做自动关闭处理,也没有校验写入结果,无法定位问题。
- 你配置的
修复步骤
1. 调整OkHttp配置
下载文件专用的OkHttpClient实例建议关闭BODY级别的日志,避免流被提前消费:
// 调试阶段可以保留HEADERS级别日志,生产环境直接关闭日志即可 HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); logging.setLevel(HttpLoggingInterceptor.Level.HEADERS); OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(logging) .connectTimeout(2, TimeUnit.MINUTES) .writeTimeout(2, TimeUnit.MINUTES) .readTimeout(2, TimeUnit.MINUTES) .build();
2. 重写文件写入逻辑
不需要提前读取整个响应体获取长度,直接用缓冲区边读边写,避免大文件OOM的同时也不会触发流提前关闭的问题,同时用try-with-resources自动关闭流,补全异常提示:
private boolean writeResponseBodyToDisk(ResponseBody body) { File downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); File targetFile = new File(downloadDir, "test.pdf"); byte[] buffer = new byte[4096]; int readLen; // try-with-resources会自动关闭输入输出流,不需要手动调用close try (InputStream inputStream = body.byteStream(); OutputStream outputStream = new FileOutputStream(targetFile)) { while ((readLen = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, readLen); } outputStream.flush(); Toast.makeText(MainActivity.this, "文件写入成功", Toast.LENGTH_SHORT).show(); return true; } catch (Exception e) { e.printStackTrace(); Toast.makeText(MainActivity.this, "写入失败:" + e.getMessage(), Toast.LENGTH_LONG).show(); return false; } }
3. 补充权限配置
如果你的应用targetSdkVersion >=23且运行在Android 9及以下系统,需要在代码中动态申请WRITE_EXTERNAL_STORAGE权限,Android 10及以上系统不需要申请该权限,可直接写入公共Download目录。
内容的提问来源于stack exchange,提问作者Niklaus
相关产品推荐
相关产品推荐

