【问题标题】:Tracking progress of multipart file upload using OKHTTP使用 OKHTTP 跟踪分段文件上传的进度
【发布时间】:2014-11-15 17:47:22
【问题描述】:

我正在尝试实现一个进度条来指示分段文件上传的进度。

我从对此答案的评论中读到 - https://stackoverflow.com/a/24285633/1022454 我必须包装传递给 RequestBody 的接收器并提供一个回调来跟踪移动的字节。

我创建了一个自定义 RequestBody 并用 CustomSink 类包装了接收器,但是通过调试我可以看到字节正在由RealBufferedSink ln 44 写入,并且自定义接收器写入方法只运行一次,不允许我跟踪移动的字节。

    private class CustomRequestBody extends RequestBody {

    MediaType contentType;
    byte[] content;

    private CustomRequestBody(final MediaType contentType, final byte[] content) {
        this.contentType = contentType;
        this.content = content;
    }

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

    @Override
    public long contentLength() {
        return content.length;
    }

    @Override
    public void writeTo(BufferedSink sink) throws IOException {
        CustomSink customSink = new CustomSink(sink);
        customSink.write(content);

    }
}


private class CustomSink implements BufferedSink {

    private static final String TAG = "CUSTOM_SINK";

    BufferedSink bufferedSink;

    private CustomSink(BufferedSink bufferedSink) {
        this.bufferedSink = bufferedSink;
    }

    @Override
    public void write(Buffer source, long byteCount) throws IOException {
        Log.d(TAG, "source size: " + source.size() + " bytecount" + byteCount);
        bufferedSink.write(source, byteCount);
    }

    @Override
    public void flush() throws IOException {
        bufferedSink.flush();
    }

    @Override
    public Timeout timeout() {
        return bufferedSink.timeout();
    }

    @Override
    public void close() throws IOException {
        bufferedSink.close();
    }

    @Override
    public Buffer buffer() {
        return bufferedSink.buffer();
    }

    @Override
    public BufferedSink write(ByteString byteString) throws IOException {
        return bufferedSink.write(byteString);
    }

    @Override
    public BufferedSink write(byte[] source) throws IOException {
        return bufferedSink.write(source);
    }

    @Override
    public BufferedSink write(byte[] source, int offset, int byteCount) throws IOException {
        return bufferedSink.write(source, offset, byteCount);
    }

    @Override
    public long writeAll(Source source) throws IOException {
        return bufferedSink.writeAll(source);
    }

    @Override
    public BufferedSink writeUtf8(String string) throws IOException {
        return bufferedSink.writeUtf8(string);
    }

    @Override
    public BufferedSink writeString(String string, Charset charset) throws IOException {
        return bufferedSink.writeString(string, charset);
    }

    @Override
    public BufferedSink writeByte(int b) throws IOException {
        return bufferedSink.writeByte(b);
    }

    @Override
    public BufferedSink writeShort(int s) throws IOException {
        return bufferedSink.writeShort(s);
    }

    @Override
    public BufferedSink writeShortLe(int s) throws IOException {
        return bufferedSink.writeShortLe(s);
    }

    @Override
    public BufferedSink writeInt(int i) throws IOException {
        return bufferedSink.writeInt(i);
    }

    @Override
    public BufferedSink writeIntLe(int i) throws IOException {
        return bufferedSink.writeIntLe(i);
    }

    @Override
    public BufferedSink writeLong(long v) throws IOException {
        return bufferedSink.writeLong(v);
    }

    @Override
    public BufferedSink writeLongLe(long v) throws IOException {
        return bufferedSink.writeLongLe(v);
    }

    @Override
    public BufferedSink emitCompleteSegments() throws IOException {
        return bufferedSink.emitCompleteSegments();
    }

    @Override
    public OutputStream outputStream() {
        return bufferedSink.outputStream();
    }
}

有没有人举个例子说明我会怎么做?

【问题讨论】:

    标签: android okhttp okio


    【解决方案1】:

    您必须创建一个自定义 RequestBody 并覆盖 writeTo 方法,并且您必须将文件分段发送到接收器。在每个段之后刷新接收器非常重要,否则您的进度条将很快填满,而文件实际上不会通过网络发送,因为内容将保留在接收器中(其作用类似于缓冲区)。

    public class CountingFileRequestBody extends RequestBody {
    
        private static final int SEGMENT_SIZE = 2048; // okio.Segment.SIZE
    
        private final File file;
        private final ProgressListener listener;
        private final String contentType;
    
        public CountingFileRequestBody(File file, String contentType, ProgressListener listener) {
            this.file = file;
            this.contentType = contentType;
            this.listener = listener;
        }
    
        @Override
        public long contentLength() {
            return file.length();
        }
    
        @Override
        public MediaType contentType() {
            return MediaType.parse(contentType);
        }
    
        @Override
        public void writeTo(BufferedSink sink) throws IOException {
            Source source = null;
            try {
                source = Okio.source(file);
                long total = 0;
                long read;
    
                while ((read = source.read(sink.buffer(), SEGMENT_SIZE)) != -1) {
                    total += read;
                    sink.flush();
                    this.listener.transferred(total);
    
                }
            } finally {
                Util.closeQuietly(source);
            }
        }
    
        public interface ProgressListener {
            void transferred(long num);
        }
    
    }
    

    您可以在我的 gist 中找到支持在 AdapterView 中显示进度并取消上传的完整实现:​​https://gist.github.com/eduardb/dd2dc530afd37108e1ac

    【讨论】:

    • 如果通过慢速网络连接上传小文件,这似乎不起作用。见github.com/square/okhttp/issues/1078。这种情况有解决办法吗?
    • @Edy Bolos 有没有办法将它与 RxJava & Observable 结合使用?
    • 回答你的问题:一切都可以被包裹在一个 Observable 中:) 但我必须让别人去做。我唯一的建议是也许使用BehaviorSubjectUploadsHandler 中发出进度值
    • 正如之前的评论者所说。即使连接不好,小文件也没有进展。这是因为网络接口发送缓冲区太高以至于吞下整个文件,okHttp 会报告它 100% 上传。使用它可以解决此问题。 gist.github.com/slightfoot/00a26683ea68856ceb50e26c7d8a47d0
    • 你们能定义你所说的小是10MB还是2MB还是什么......??
    【解决方案2】:
    • 我们只需要创建一个自定义的RequestBody,不需要实现自定义的BufferedSink。我们可以分配 Okio 缓冲区来读取图像文件,并将这个缓冲区连接到 sink。

    例如,请看下面的createCustomRequestBody函数

    public static RequestBody createCustomRequestBody(final MediaType contentType, final File file) {
        return new RequestBody() {
            @Override public MediaType contentType() {
                return contentType;
            }
            @Override public long contentLength() {
                return file.length();
            }
            @Override public void writeTo(BufferedSink sink) throws IOException {
                Source source = null;
                try {
                    source = Okio.source(file);
                    //sink.writeAll(source);
                    Buffer buf = new Buffer();
                    Long remaining = contentLength();
                    for (long readCount; (readCount = source.read(buf, 2048)) != -1; ) {
                        sink.write(buf, readCount);
                        Log.d(TAG, "source size: " + contentLength() + " remaining bytes: " + (remaining -= readCount));
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
    }
    
    • 使用 -

      .addPart(
          Headers.of("Content-Disposition", "form-data; name=\"image\""),
          createCustomRequestBody(MediaType.parse("image/png"), new File("test.jpg")))
      .build()
      

    【讨论】:

    • 由于accepted answer 中解释的原因,这将无法按预期工作。 It is very important that you flush the sink after each segment, otherwise your progress bar will fill up quickly without the file being actually sent over the network
    【解决方案3】:

    这东西很好用!

    分级

    dependencies {
      compile 'io.github.lizhangqu:coreprogress:1.0.2'
    }
    
    //wrap your original request body with progress
    RequestBody requestBody = ProgressHelper.withProgress(body, new ProgressUIListener()....} 
    

    完整的示例代码在这里 https://github.com/lizhangqu/CoreProgress

    【讨论】:

      【解决方案4】:

      我编写了一个简单的模块来使用 Kotlin Flow 实现这一点。你可以参考那个https://github.com/colin-yeoh/FileUploader

      【讨论】:

      • 虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接答案可能会失效。 - From Review
      【解决方案5】:

      这可能对 Kotlin 用户有帮助,我在 java.io.File 上写了一个扩展函数。

      import okhttp3.MediaType
      import okhttp3.RequestBody
      import okio.BufferedSink
      import okio.source
      import java.io.File
      
      fun File.asProgressRequestBody(
          contentType: MediaType? = null,
          onProgress: (percent: Float) -> Unit
      ): RequestBody {
          return object : RequestBody() {
              override fun contentType() = contentType
      
              override fun contentLength() = length()
      
              override fun writeTo(sink: BufferedSink) {
                  source().use { source ->
                      var total: Long = 0
                      var read: Long = 0
                      while (source.read(sink.buffer, 2048).apply {
                              read = this
                          } != -1L) {
                          total += read
                          sink.flush()
                          onProgress.invoke(
                              (total.toFloat() / length())*100
                          )
                      }
                  }
      
      
              }
          }
      }
      

      你可以在 Retrofit 和 OkHttp 中使用它来创建一个 Multipart 请求体,如下所示,

      fun file2MultiPartBody(myFile: File,key:String,onProgress:(percent:Float)->Unit): MultipartBody.Part {
          val requestBody = myFile.asProgressRequestBody("application/octet-stream".toMediaType(),onProgress)
          return MultipartBody.Part.createFormData(key, myFile.name, requestBody)
      }
      

      【讨论】:

        猜你喜欢
        • 2016-06-02
        • 2018-10-16
        • 1970-01-01
        • 2016-04-13
        • 2016-06-27
        • 2023-03-31
        • 2014-08-08
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多