Mobile Development 14 min read

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.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Resolving IllegalStateException: closed During Android File Upload with RxHttp and OkHttp

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.

AndroidFile UploadOkHttpProfilerNetwork InterceptorProgressRequestBodyRxHttp
Sohu Tech Products
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.