Resolving IllegalStateException: closed During Android File Upload with RxHttp and OkHttp
This article details a real‑world Android file‑upload failure caused by the Android Studio Profiler’s OkHttp3Interceptor closing the request’s BufferedSink, explains the debugging steps taken, and provides code modifications to the ProgressRequestBody to prevent the IllegalStateException and ensure correct upload progress tracking.
This case study describes a puzzling file‑upload error that occurred intermittently in an Android app using RxHttp 2.6.4 and OkHttp 4.9.1 . The error manifested as java.lang.IllegalStateException: closed when the request body was written.
Problem description
The upload code looks like this:
fun uploadFiles(fileList: List<File>) {
RxHttp.postForm("/server/...")
.add("key", "value")
.addFiles("files", fileList)
.upload { /* progress callback */ }
.asString()
.subscribe({ /* success */ }, { /* failure */ })
}After working for a while, the code suddenly started throwing the exception, and the log showed that the data stream had been closed while still being written.
Investigation
Opening ProgressRequestBody (line 76) revealed that it kept a BufferedSink as a member variable and only created it when null. The interceptor chain showed that CallServerInterceptor (the last OkHttp interceptor) called ProgressRequestBody.writeTo after an OkHttp3Interceptor injected by Android Studio Profiler had already closed the sink.
class ProgressRequestBody extends RequestBody {
private BufferedSink bufferedSink;
@Override
public void writeTo(BufferedSink sink) throws IOException {
if (bufferedSink == null) {
bufferedSink = Okio.buffer(sink(sink));
}
requestBody.writeTo(bufferedSink);
bufferedSink.flush();
}
}The OkHttp3Interceptor lives in the package com.android.tools.profiler.agent.okhttp and is added to the network interceptor list via byte‑code instrumentation when the Profiler or Database Inspector is enabled. Its trackRequest method writes the request body to a temporary BufferedSink and closes it, leaving the original sink closed for the subsequent CallServerInterceptor call.
public final class OkHttp3Interceptor implements Interceptor {
private HttpConnectionTracker trackRequest(Request request) throws IOException {
// ...
if (request.body() != null) {
OutputStream outputStream = tracker.trackRequestBody(OkHttpUtils.createNullOutputStream());
BufferedSink bufferedSink = Okio.buffer(Okio.sink(outputStream));
request.body().writeTo(bufferedSink);
bufferedSink.close();
}
return tracker;
}
}Thus the same ProgressRequestBody.writeTo method was invoked twice: first by the Profiler interceptor (which closed the sink) and then by the normal OkHttp flow, causing the IllegalStateException: closed .
Solution
Make the BufferedSink a local variable so it is not reused after being closed, and add a guard to skip the Profiler’s call:
public class ProgressRequestBody extends RequestBody {
@Override
public void writeTo(BufferedSink sink) throws IOException {
// If the caller is OkHttp3Interceptor or a Buffer, write directly without wrapping
if (sink instanceof okio.Buffer || sink.toString().contains("com.android.tools.profiler.support.network.HttpTracker$OutputStreamTracker")) {
requestBody.writeTo(sink);
return;
}
BufferedSink bufferedSink = Okio.buffer(sink(sink));
requestBody.writeTo(bufferedSink);
bufferedSink.close();
}
}After applying this change, enabling the Profiler’s network monitor no longer crashes the upload, and progress callbacks fire only once. If additional interceptors (e.g., HttpLoggingInterceptor ) also call writeTo , similar guard logic can be added based on the sink type.
Conclusion
The root cause was the Android Studio Profiler injecting OkHttp3Interceptor via byte‑code instrumentation, which closed the request’s BufferedSink before the real network interceptor wrote the body. Converting the sink to a local variable and detecting Profiler‑specific sinks resolves the issue and restores reliable file uploads.
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.