Retrofit 上传文件显示进度及踩坑记录
来源:互联网 发布:淘宝楠楠家是洋垃圾 编辑:程序博客网 时间:2024/06/04 20:21
因产品需求,需要实现图片上传显示文件进度。我在项目中是使用的 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 进行封装,实现上传数据统计:
class ProgressRequestBody extends RequestBody { private RequestBody mDelegate; private Emitter<UploadProgressInfo> mEmitter; private UploadProgressInfo mProgressInfo; private BufferedSink mBufferedSink; ProgressRequestBody(RequestBody delegate, Emitter<UploadProgressInfo> emitter, UploadProgressInfo info) { mDelegate = delegate; mEmitter = emitter; mProgressInfo = info; } @Override public long contentLength() throws IOException { return mDelegate.contentLength(); } @Override public MediaType contentType() { return mDelegate.contentType(); } @Override public void writeTo(BufferedSink sink) throws IOException { if (mBufferedSink == null) { mBufferedSink = Okio.buffer(wrapSink(sink)); } mDelegate.writeTo(mBufferedSink); mBufferedSink.flush(); } private Sink wrapSink(Sink sink) { return new ForwardingSink(sink) { @Override public void write(Buffer source, long byteCount) throws IOException { super.write(source, byteCount); if (mProgressInfo.total == 0) { mProgressInfo.total = contentLength(); } mProgressInfo.current += byteCount; mEmitter.onNext(mProgressInfo); } }; }}
Retrofit 接口声明,参数为 @Body RequestBody body
:
public interface UploadService { /** * 上传图片 * * @param body 请求体 * @return Observable */ @POST("/upload") Observable<UploadResponse> upload(@Body RequestBody body);}
调用:
public void uploadPhotoFile(final CertificateType type, final File file) { Observable.create(new Action1<Emitter<UploadProgressInfo>>() { @Override public void call(Emitter<UploadProgressInfo> emitter) { doUpload(type, file, emitter); } }, Emitter.BackpressureMode.LATEST) .onBackpressureLatest() .subscribeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(watchSubscriber(new RxAction<UploadProgressInfo>() { @Override public void onNext(UploadProgressInfo info) { getView().onUploading(info); } @Override public void onError(Throwable e) { super.onError(e); getView().onUploadFailure(type); } })); }
其中 private void doUpload(final CertificateType type, File file, final Emitter<UploadProgressInfo> emitter)
方法主要代码如下:
final UploadParams params = new UploadParams(file); final RequestBody fileOriginalBody = BodyUtil.createMultipartBody(params); UPLOAD_SERVICE.upload(new ProgressRequestBody(fileOriginalBody, emitter, info)) .compose(this.<UploadResponse>applySchedulers()) //代码略
遇坑
然而运行之后,我有点懵了。上传进度一下子就 100%,然后继续慢慢涨,一直涨到 200%,然后提示上传失败。
反复对比文章中的代码,确定我没写错,但却得不到同样的结果。
看了一下上传失败所报的异常如下:
java.net.ProtocolException: unexpected end of stream at okhttp3.internal.http1.Http1Codec$FixedLengthSink.close(Http1Codec.java:298) at okio.RealBufferedSink.close(RealBufferedSink.java:236) at okhttp3.internal.http.CallServerInterceptor.intercept(CallServerInterceptor.java:63) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92) at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:45) ...
我又按另一篇文章的写法改了一下,把包装 BufferedSink
的成员变量 mBufferedSink
改成了局部变量:
@Override public void writeTo(BufferedSink sink) throws IOException { BufferedSink bufferedSink = Okio.buffer(wrapSink(sink)); mDelegate.writeTo(bufferedSink); bufferedSink.flush(); }
这时,发现日志提示上传成功了,但是上传进度还是 200%。
原因及解决
被这个问题困扰折腾许久,最终我发现了原因。原来,我这边在 debug 版本会打印所有网络请求的日志,以便调试及查问题。打印日志的方式是通过添加一个 OkHttp 的拦截器,然后把请求及响应的内容打印处理。打印日志的拦截器,是参考 OkHttp 的 LoggingInterceptor
修改而来,其中获取请求的内容是通过创建一个 Buffer
对象,把请求体写到这个对象中,代码如下:
Buffer buffer = new Buffer();requestBody.writeTo(buffer);
对于上传文件,也就是在真正的上传前,其 writeTo(BufferedSink sink)
方法会被调用一次,用于打印日志,在之后又会被调用一次,用于真正的上传。所以上传进度会是 200%。而第一次是直接写入到 buffer 对象中,所以会很快,所以一下子就先 100%。
原因是找到了,那如何解决?
首先,这个日志拦截器是不能去掉的,因为在开发中有时遇到网络请求的相关问题,就需要查看日志看是参数不对还是服务端返回有问题。
其次,这个日志拦截器在只会在 debug 版本,以及测试环境版本中加入,在正式环境的 release 版本是不会加入的,所以也不能直接写死忽略第一次写入的统计。
最终,我发现日志拦截器中的 BufferedSink
是 Buffer
类型,而实际进行网络请求的 BufferedSink
是 FixedLengthSink
。所以修改 ProgressRequestBody
里的 writeTo(BufferedSink sink)
方法,如果传入的 sink
为 Buffer
对象,则直接写入,不进行统计,代码如下:
@Override public void writeTo(BufferedSink sink) throws IOException { if (sink instanceof Buffer) { // Log Interceptor mDelegate.writeTo(sink); return; } if (mBufferedSink == null) { mBufferedSink = Okio.buffer(wrapSink(sink)); } mDelegate.writeTo(mBufferedSink); mBufferedSink.flush(); }
运行,解决。
参考资料
- 《再谈Retrofit:文件的上传下载及进度显示》
- 《RxJava2+Retrofit2单文件上传监听进度封装(服务端代码+客户端代码)》
- Retrofit 上传文件显示进度及踩坑记录
- retrofit显示上传文件进度
- Retrofit图片上传及进度的显示
- 文件上传及进度显示
- Retrofit文件下载进度显示
- retrofit上传获取文件上传进度
- 解决Retrofit文件下载进度显示问题
- Retrofit+Rxjava 下载文件(带进度显示)
- 解决Retrofit文件下载进度显示问题
- 解决Retrofit文件下载进度显示问题
- Retrofit(三)、使用Retrofit上传文件并带进度
- 带进度显示的单个和多个 Retrofit+Rxjava2文件上传
- Android网络开源库-Retrofit(二) 文件上传、下载及进度监听
- 用JAVA实现大文件上传及显示进度信息
- JAVA实现大文件上传及显示进度信息
- 用JAVA实现大文件上传及显示进度信息
- HTML5上传文件显示进度
- SpringMVC上传文件进度显示
- 11月 第4周 GitChat 话题排行榜
- 存储性能瓶颈的背后,这篇文章带来的参考价值
- Material Design 中的应用栏该怎么设计?
- 5475. 【NOIP2017提高组正式赛】逛公园
- dsoframer控件学习小结(打开WORD,EXCEL等文件)
- Retrofit 上传文件显示进度及踩坑记录
- 分阶段剖析新站seo教程优化三部曲
- codeforce 893B (模拟)
- JS选项卡切换
- 80C51单片机模仿实例100—1 LED闪烁
- 【一周头条盘点】中国软件网(2017.11.20~2017.11.24)
- 5476. 【NOIP2017提高组正式赛】奶酪
- 模型优化:BatchNorm合并到卷积中
- iframe与父页面、子页面的交互