OkHttp上传进度与实际上传不同步

5

我正在尝试使用OkHttp跟踪上传的进度。我创建了一个自定义的RequestBody,其主体如下(感谢此答案),它将写入sink并发布进度。

public class CountingFileRequestBody extends RequestBody {
    private static final String TAG = "CountingFileRequestBody";

    private final ProgressListener listener;
    private final String key;
    private final MultipartBody multipartBody;
    protected CountingSink mCountingSink;

    public CountingFileRequestBody(MultipartBody multipartBody,
                                   String key,
                                   ProgressListener listener) {
        this.multipartBody = multipartBody;
        this.listener = listener;
        this.key = key;
    }

    @Override
    public long contentLength() throws IOException {
        return multipartBody.contentLength();
    }

    @Override
    public MediaType contentType() {
        return multipartBody.contentType();
    }

    @Override
    public void writeTo(BufferedSink sink) throws IOException {
        mCountingSink = new CountingSink(sink);
        BufferedSink bufferedSink = Okio.buffer(mCountingSink);
        multipartBody.writeTo(bufferedSink);
        bufferedSink.flush();
    }

    public interface ProgressListener {
        void transferred(String key, int num);
    }

    protected final class CountingSink extends ForwardingSink {
        private long bytesWritten = 0;

        public CountingSink(Sink delegate) {
            super(delegate);
        }

        @Override
        public void write(Buffer source, long byteCount) throws IOException {
            bytesWritten += byteCount;
            listener.transferred(key, (int) (100F * bytesWritten / contentLength()));
            super.write(source, byteCount);
            delegate().flush(); // I have added this line to manually flush the sink
        }
    }

}

这里的问题是,数据被写入到接收端之后立即就会被发送,但实际上缓冲区的字节并没有真正传输到服务器。这意味着我的进度条很早就到达了终点,而实际上传却还未结束。
注意:有些人认为在每次迭代中都需要刷新接收端才能真正上传字节,但对我来说并没有起作用。

看看这个问题(https://dev59.com/FF8e5IYBdhLWcg3wNYUn),它似乎与你的类似。 - Piyush Khera
2个回答

1
我知道这是一篇旧文章,但对于有同样问题的人,我已经从这个库中适应了一个辅助类(ProgressOutputStream):https://github.com/lizhangqu/CoreProgress,我的工作代码如下:(适用于上传文件和上传json)
import java.io.IOException;

import okhttp3.MediaType;
import okhttp3.RequestBody;
import okio.BufferedSink;
import okio.Okio;

public class UploadProgressRequestBody extends RequestBody {
    private final RequestBody requestBody;
    private final ProgressListener progressListener;

    public UploadProgressRequestBody(RequestBody requestBody) {
        this.requestBody = requestBody;
        this.progressListener = getDefaultProgressListener();
    }

    @Override public MediaType contentType() {
        return requestBody.contentType();
    }

    @Override public long contentLength() {
        try {
            return requestBody.contentLength();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return -1;
    }

    @Override
    public void writeTo(BufferedSink sink) throws IOException {
        if (progressListener == null) {
            requestBody.writeTo(sink);
            return;
        }
        ProgressOutputStream progressOutputStream = new ProgressOutputStream(sink.outputStream(), progressListener, contentLength());
        BufferedSink progressSink = Okio.buffer(Okio.sink(progressOutputStream));
        requestBody.writeTo(progressSink);
        progressSink.flush();
    }

    interface ProgressListener {
        void update(long bytesWritten, long contentLength);
    }

    private ProgressListener getDefaultProgressListener(){
        ProgressListener progressListener = new UploadProgressRequestBody.ProgressListener() {
            @Override public void update(long bytesRead, long contentLength) {
                System.out.println("bytesRead: "+bytesRead);
                System.out.println("contentLength: "+contentLength);
                System.out.format("%d%% done\n", (100 * bytesRead) / contentLength);
            }
        };

        return progressListener;
    }

}

===========

import java.io.IOException;
import java.io.OutputStream;

class ProgressOutputStream extends OutputStream {
    private final OutputStream stream;
    private final UploadProgressRequestBody.ProgressListener listener;

    private long total;
    private long totalWritten;

    ProgressOutputStream(OutputStream stream, UploadProgressRequestBody.ProgressListener listener, long total) {
        this.stream = stream;
        this.listener = listener;
        this.total = total;
    }

    @Override
    public void write(byte[] b, int off, int len) throws IOException {
        this.stream.write(b, off, len);
        if (this.total < 0) {
            this.listener.update(-1, -1);
            return;
        }
        if (len < b.length) {
            this.totalWritten += len;
        } else {
            this.totalWritten += b.length;
        }
        this.listener.update(this.totalWritten, this.total);
    }

    @Override
    public void write(int b) throws IOException {
        this.stream.write(b);
        if (this.total < 0) {
            this.listener.update(-1, -1);
            return;
        }
        this.totalWritten++;
        this.listener.update(this.totalWritten, this.total);
    }

    @Override
    public void close() throws IOException {
        if (this.stream != null) {
            this.stream.close();
        }
    }

    @Override
    public void flush() throws IOException {
        if (this.stream != null) {
            this.stream.flush();
        }
    }
}

==========

    OkHttpClient client = new OkHttpClient.Builder()
    .addNetworkInterceptor(new Interceptor() {
        @Override public Response intercept(Chain chain) throws IOException {
            Request originalRequest = chain.request();

            if (originalRequest.body() == null) {
                return chain.proceed(originalRequest);
            }

            Request progressRequest = originalRequest.newBuilder()
                    .method(originalRequest.method(),
                            new UploadProgressRequestBody(originalRequest.body()))
                    .build();

            return chain.proceed(progressRequest);
        }
    }).build();

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(baseUrl)
        .client(client)
        .addConverterFactory(GsonConverterFactory.create())
        .build();

这段代码不起作用。progressListener始终返回100%。bytesRead和contentLength始终相同。 - Deepak kaku
@Deepakkaku 我正在使用完全相同的代码在Android和PHP服务器之间进行通信,它正常工作。在服务器上,我在请求响应中添加了头部 'Content-Encoding: gzip' 和 "Content-Length:" . strlen($replyBody)。 - Johny

1
在疲惫的挖掘工作之后,我发现这位天才提出了在网络接口层设置缓冲区大小的解决方案。我的问题是进度条会立即跳到超过一半的位置。原来是我的输入流被读取,缓冲区(输出流)被写入,直到系统认为足够为止,然后消耗它并继续进行...这使得无法准确地跟踪上传进度。看一下这个链接:Fix for upload progress monitoring in Android. 尝试使用该类并将其sendBufferSize设置为较小但合理的值或任何适当的值。
int sendBufferSize = 128 * 1028;
new OkHttpClient.Builder().socketFactory(new RestrictedSocketFactory(sendBufferSize));

关于进度条跳到100的问题,有一点需要注意:我看到很多答案都建议关闭OkHttp日志记录,可以尝试关闭看看是否有帮助。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接