如何从Retrofit API请求中获取中间响应 解决大响应OOM及无分段回调问题
问题根因定位
- HttpLoggingInterceptor日志级别设置为
BODY会提前读取完整响应体存入内存,直接导致@Streaming注解失效,分片读取的能力被拦截器提前消耗 - 即使
@Streaming生效,如果消费响应时直接调用ResponseBody.string()/bytes()方法,依然会一次性加载全量响应到内存触发OOM - 当前接口返回类型为
ResponseBody未走Gson全量序列化逻辑,这部分配置是正确的
具体修复步骤
1. 调整日志拦截器配置
BODY级别的日志会强制读取全量响应,是导致@Streaming失效的核心原因,可以根据环境调整日志级别,或者精准判断请求路径,仅对大响应接口关闭BODY级日志:
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(); // 测试环境用HEADERS级别,生产环境关闭日志;也可以在拦截器内判断路径为SEARCH_DOCUMENTS时跳过响应日志打印 interceptor.setLevel(BuildConfig.DEBUG ? HttpLoggingInterceptor.Level.HEADERS : HttpLoggingInterceptor.Level.NONE);
2. 调整响应消费逻辑,逐段读取分片
拿到ResponseBody后禁止直接调用string()/bytes()方法,通过流逐段读取,搭配Gson流式解析器JsonReader逐个解析数据,避免全量加载到内存,同时可以实现进度回调:
searchDocuments(documentRequest) .subscribeOn(Schedulers.io()) .observeOn(Schedulers.io()) // 流式解析全程在子线程执行,不要提前切主线程 .subscribe(responseBody -> { JsonReader jsonReader = new JsonReader(new InputStreamReader(responseBody.byteStream())); jsonReader.beginArray(); // 按实际响应结构调整,假设外层为JSON数组 Gson gson = new Gson(); int parsedCount = 0; while (jsonReader.hasNext()) { // 逐个解析数组内的单条数据 DocumentItem item = gson.fromJson(jsonReader, DocumentItem.class); // 此处可做增量存储到数据库、回调进度等操作 parsedCount++; // 每解析100条更新一次进度到UI if (parsedCount % 100 == 0) { int finalParsedCount = parsedCount; runOnUiThread(() -> updateProgressBar(finalParsedCount)); } } jsonReader.endArray(); jsonReader.close(); responseBody.close(); // 全部解析完成回调 runOnUiThread(() -> loadFinish()); }, throwable -> { // 异常处理逻辑 runOnUiThread(() -> showLoadError(throwable.getMessage())); });
3. 可选优化配置
可以调整OkHttpClient的缓冲区大小适配流式读取场景,提升读取效率:
OkHttpClient client = new OkHttpClient.Builder() // 原有配置保持不变 .readBufferSize(16 * 1024) // 设置16KB读缓冲区 .writeBufferSize(16 * 1024) // 设置16KB写缓冲区 .build();
注意事项
- 流式读取、解析的全流程必须在子线程执行,避免阻塞主线程触发ANR
- 操作完成后必须主动关闭
JsonReader和ResponseBody,避免资源泄漏 - 解析到的单条数据建议直接存入本地数据库,不要全部缓存在内存中,解析完成后再从数据库读取展示
内容的提问来源于stack exchange,提问作者Rohan Patel
相关产品推荐
相关产品推荐

