Retrofit 上传文件显示进度及踩坑记录
因产品需求,需要实现图片上传显示文件进度。我在项目中是使用的 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;}@Overridepublic long contentLength() throws IOException {return mDelegate.contentLength();}@Overridepublic MediaType contentType() {return mDelegate.contentType();}@Overridepublic 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) {@Overridepublic 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>>() {@Overridepublic void call(Emitter<UploadProgressInfo> emitter) {doUpload(type, file, emitter);}}, Emitter.BackpressureMode.LATEST).onBackpressureLatest().subscribeOn(AndroidSchedulers.mainThread()).observeOn(AndroidSchedulers.mainThread()).subscribe(watchSubscriber(new RxAction<UploadProgressInfo>() {@Overridepublic void onNext(UploadProgressInfo info) {getView().onUploading(info);}@Overridepublic 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 streamat 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
改成了局部变量:
@Overridepublic 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
对象,则直接写入,不进行统计,代码如下:
@Overridepublic void writeTo(BufferedSink sink) throws IOException {if (sink instanceof Buffer) {// Log InterceptormDelegate.writeTo(sink);return;}if (mBufferedSink == null) {mBufferedSink = Okio.buffer(wrapSink(sink));}mDelegate.writeTo(mBufferedSink);mBufferedSink.flush();}
运行,解决。
参考资料
- 《再谈Retrofit:文件的上传下载及进度显示》
- 《RxJava2+Retrofit2单文件上传监听进度封装(服务端代码+客户端代码)》
Retrofit 上传文件显示进度及踩坑记录相关推荐
- 异步上传文件显示进度条
异步上传文件显示进度条 原文地址:异步上传文件显示进度条 问题 我们在写网站时难免会遇到需要上传文件的场景,但当上传大文件时比如5个G的文件直接用表单直接提交文件会出现页面卡顿.未响应等影响用户体验的 ...
- 上传文件显示进度条_文件上传带进度条进阶-断点续传
说明 1. 把文件按大小1M分割成N份 2. 每次上传时,告诉后台大文件的md5.当前第几份(从0开始).总共几份 3. 并行上传,前端同时开启5个请求进行传输增加速度 4. 上传失败或出错后,继续上 ...
- 上传文件显示进度条_【技巧 】iOSamp;Windows互传文件?透过「文件」轻松解决~...
苹果家的隔空投送能做到手机.电脑间的无缝衔接:在iOS13的「文件」中加入了局域网的文件共享功能, 电脑不需要安装任何软件, 实现 iPhone 与 Windows 电脑间文件的共享.传输. 注意:本 ...
- react-quill 图片上传及图片粘贴功能踩坑记录
Gitlab React-quill:https://github.com/zenoamaro/react-quill 中文文档 Quill:http://doc.quilljs.cn/1409381 ...
- 批量上传文件及进度显示
不带插件 ,自己写js,实现批量上传文件及进度显示 今天接受项目中要完成文件批量上传文件而且还要显示上传进度,一开始觉得这个应该不是很麻烦,当我在做的时候遇到了很多问题,很头疼啊. 不过看了别人写的代 ...
- ServletFileUpload上传文件带进度条
ServletFileUpload上传文件带进度条 涉及了两个架包:commons-io-2.0.1.jar,commons-fileupload-1.2.2.jar,还有一个jquery-2.2.4 ...
- ASP.Net上传文件带进度条、显示剩余时间!
近段时间因为开发隐私存储网站,采用ASP.Net 2.0在处理上传文件,因为上传的文件比较大,为了改善用户体验,所以自己重写了ASP.Net 的标准上传控件,实现显示进度条,百分比,平均速度,剩余时间 ...
- java 上传 进度,关于 javaweb的文件上传实时显示进度
方法:使用单例保存实时信息.具体的实现方法就是,当用户点击了处理按钮时,在后台开启一个线程进行处理,并且每进行到一步,就向单例中写入当前状态信息.然后编写一个servlet,用于返回单例中的信息,前台 ...
- Asp.Net实现无刷新文件上传并显示进度条(非服务器控件实现)
相信通过Asp.Net的服务器控件上传文件在简单不过了,通过AjaxToolkit控件实现上传进度也不是什么难事,为什么还要自己辛辛苦苦来实现呢?我并不否认"拿来主义",只是我个人 ...
最新文章
- na na na na na ~
- Continue to debug QDD504 read - Service Order extractor debug
- 【渝粤教育】 国家开放大学2020年春季 2245社会福利与保障 参考试题
- Tiray.SMSTiray.SMSTiray.SMSTiray.SMSTiray.SMSTiray.SMS
- 揭秘淘宝286亿海量图片存储与处理架构,互联网营销
- echart雷达图数据图形的填充颜色_数据可视化,职场数据分析都需要哪些常用的图表?...
- oracle中打钩,wps文档如何在小方块里打钩?
- Windows XP 按权限设置共享
- 电脑开机后过一会就关机自动重启
- android 亮屏 激活自动亮度调节,Android 屏幕亮度调节(2.0以后出现亮度自动调节)如何开启、关闭亮度自动调节...
- 怎么录制屏幕视频?3个简单实用方法分享
- 转盘抽奖图片html,html转盘抽奖
- 在ROS中使用中文语音交互(百度语音baidu_speech)
- 微型计算机的内存乘储器,微型计算机及接口技术名词解释题及解答题
- LInux sed命令详解
- EOS的经济模型是什么?
- Facebook灰度手势识别论文笔记
- 微信公众账号第三方平台全网发布源码(java)- 实战测试通过
- 基于同态加密体制的安全多方计算
- 【其他】逻辑、逻辑推理概念