因产品需求,需要实现图片上传显示文件进度。我在项目中是使用的 Retrofit 和 RxJava,虽网上不乏相关文章,然而在使用的过程中还是遇到了点坑,记录为文,谨供他人参考。
实现
我在项目中使用的是 RxJava + Retrofit + OkHttp,网上不乏此类实现上传文件进度的文章,我找到的是《再谈Retrofit:文件的上传下载及进度显示》与《RxJava2+Retrofit2单文件上传监听进度封装(服务端代码+客户端代码)》。这两篇的实现方式都是一样的,即通过继承 RequestBody,对原有的 RequestBody 进行包装,通过重写写入数据的 public void writeTo(BufferedSink sink) throws IOException
方法对所传入的 BufferedSink
对象进行包装,然后通过继承 ForwardingSink
重写 public void write(Buffer source, long byteCount) throws IOException
方法,从而实现对写入数据的统计,再获取数据总长度,就可以实时获取进度了。
参考其中一篇文章,略作修改,由于这里已经使用了 rxjava,所以便使用 Emitter
来提交进度,并封装了个表示上传进度的对象,最终实现如下。
对 RequestBody 进行封装,实现上传数据统计:
1 | class ProgressRequestBody extends RequestBody { |
Retrofit 接口声明,参数为 @Body RequestBody body
:
1 | public interface UploadService { |
调用:
1 | public void uploadPhotoFile(final CertificateType type, final File file) { |
其中 private void doUpload(final CertificateType type, File file, final Emitter<UploadProgressInfo> emitter)
方法主要代码如下:
1 | final UploadParams params = new UploadParams(file); |
遇坑
然而运行之后,我有点懵了。上传进度一下子就 100%,然后继续慢慢涨,一直涨到 200%,然后提示上传失败。
反复对比文章中的代码,确定我没写错,但却得不到同样的结果。
看了一下上传失败所报的异常如下:
1 | java.net.ProtocolException: unexpected end of stream |
我又按另一篇文章的写法改了一下,把包装 BufferedSink
的成员变量 mBufferedSink
改成了局部变量:
1 |
|
这时,发现日志提示上传成功了,但是上传进度还是 200%。
原因及解决
被这个问题困扰折腾许久,最终我发现了原因。原来,我这边在 debug 版本会打印所有网络请求的日志,以便调试及查问题。打印日志的方式是通过添加一个 OkHttp 的拦截器,然后把请求及响应的内容打印处理。打印日志的拦截器,是参考 OkHttp 的 LoggingInterceptor
修改而来,其中获取请求的内容是通过创建一个 Buffer
对象,把请求体写到这个对象中,代码如下:
1 | Buffer buffer = new Buffer(); |
对于上传文件,也就是在真正的上传前,其 writeTo(BufferedSink sink)
方法会被调用一次,用于打印日志,在之后又会被调用一次,用于真正的上传。所以上传进度会是 200%。而第一次是直接写入到 buffer 对象中,所以会很快,所以一下子就先 100%。
原因是找到了,那如何解决?
首先,这个日志拦截器是不能去掉的,因为在开发中有时遇到网络请求的相关问题,就需要查看日志看是参数不对还是服务端返回有问题。
其次,这个日志拦截器在只会在 debug 版本,以及测试环境版本中加入,在正式环境的 release 版本是不会加入的,所以也不能直接写死忽略第一次写入的统计。
最终,我发现日志拦截器中的 BufferedSink
是 Buffer
类型,而实际进行网络请求的 BufferedSink
是 FixedLengthSink
。所以修改 ProgressRequestBody
里的 writeTo(BufferedSink sink)
方法,如果传入的 sink
为 Buffer
对象,则直接写入,不进行统计,代码如下:
1 |
|
运行,解决。