因产品需求,需要实现图片上传显示文件进度。我在项目中是使用的 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 版本是不会加入的,所以也不能直接写死忽略第一次写入的统计。
最终,我发现日志拦截器中的 BufferedSinkBuffer 类型,而实际进行网络请求的 BufferedSinkFixedLengthSink。所以修改 ProgressRequestBody 里的 writeTo(BufferedSink sink) 方法,如果传入的 sinkBuffer 对象,则直接写入,不进行统计,代码如下:

    @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 上传文件显示进度及踩坑记录相关推荐

  1. 异步上传文件显示进度条

    异步上传文件显示进度条 原文地址:异步上传文件显示进度条 问题 我们在写网站时难免会遇到需要上传文件的场景,但当上传大文件时比如5个G的文件直接用表单直接提交文件会出现页面卡顿.未响应等影响用户体验的 ...

  2. 上传文件显示进度条_文件上传带进度条进阶-断点续传

    说明 1. 把文件按大小1M分割成N份 2. 每次上传时,告诉后台大文件的md5.当前第几份(从0开始).总共几份 3. 并行上传,前端同时开启5个请求进行传输增加速度 4. 上传失败或出错后,继续上 ...

  3. 上传文件显示进度条_【技巧 】iOSamp;Windows互传文件?透过「文件」轻松解决~...

    苹果家的隔空投送能做到手机.电脑间的无缝衔接:在iOS13的「文件」中加入了局域网的文件共享功能, 电脑不需要安装任何软件, 实现 iPhone 与 Windows 电脑间文件的共享.传输. 注意:本 ...

  4. react-quill 图片上传及图片粘贴功能踩坑记录

    Gitlab React-quill:https://github.com/zenoamaro/react-quill 中文文档 Quill:http://doc.quilljs.cn/1409381 ...

  5. 批量上传文件及进度显示

    不带插件 ,自己写js,实现批量上传文件及进度显示 今天接受项目中要完成文件批量上传文件而且还要显示上传进度,一开始觉得这个应该不是很麻烦,当我在做的时候遇到了很多问题,很头疼啊. 不过看了别人写的代 ...

  6. ServletFileUpload上传文件带进度条

    ServletFileUpload上传文件带进度条 涉及了两个架包:commons-io-2.0.1.jar,commons-fileupload-1.2.2.jar,还有一个jquery-2.2.4 ...

  7. ASP.Net上传文件带进度条、显示剩余时间!

    近段时间因为开发隐私存储网站,采用ASP.Net 2.0在处理上传文件,因为上传的文件比较大,为了改善用户体验,所以自己重写了ASP.Net 的标准上传控件,实现显示进度条,百分比,平均速度,剩余时间 ...

  8. java 上传 进度,关于 javaweb的文件上传实时显示进度

    方法:使用单例保存实时信息.具体的实现方法就是,当用户点击了处理按钮时,在后台开启一个线程进行处理,并且每进行到一步,就向单例中写入当前状态信息.然后编写一个servlet,用于返回单例中的信息,前台 ...

  9. Asp.Net实现无刷新文件上传并显示进度条(非服务器控件实现)

    相信通过Asp.Net的服务器控件上传文件在简单不过了,通过AjaxToolkit控件实现上传进度也不是什么难事,为什么还要自己辛辛苦苦来实现呢?我并不否认"拿来主义",只是我个人 ...

最新文章

  1. na na na na na ~
  2. Continue to debug QDD504 read - Service Order extractor debug
  3. 【渝粤教育】 国家开放大学2020年春季 2245社会福利与保障 参考试题
  4. Tiray.SMSTiray.SMSTiray.SMSTiray.SMSTiray.SMSTiray.SMS
  5. 揭秘淘宝286亿海量图片存储与处理架构,互联网营销
  6. echart雷达图数据图形的填充颜色_数据可视化,职场数据分析都需要哪些常用的图表?...
  7. oracle中打钩,wps文档如何在小方块里打钩?
  8. Windows XP 按权限设置共享
  9. 电脑开机后过一会就关机自动重启
  10. android 亮屏 激活自动亮度调节,Android 屏幕亮度调节(2.0以后出现亮度自动调节)如何开启、关闭亮度自动调节...
  11. 怎么录制屏幕视频?3个简单实用方法分享
  12. 转盘抽奖图片html,html转盘抽奖
  13. 在ROS中使用中文语音交互(百度语音baidu_speech)
  14. 微型计算机的内存乘储器,微型计算机及接口技术名词解释题及解答题
  15. LInux sed命令详解
  16. EOS的经济模型是什么?
  17. Facebook灰度手势识别论文笔记
  18. 微信公众账号第三方平台全网发布源码(java)- 实战测试通过
  19. 基于同态加密体制的安全多方计算
  20. 【其他】逻辑、逻辑推理概念

热门文章

  1. 财务大数据比赛有python吗-Python 适合大数据量的处理吗?
  2. python和c++哪个好用-python和C++语言哪个好?老男孩教育
  3. python语言入门u-[学习总结] python语言学习总结 (一)
  4. matlab求kcf算法响应图_剖析KCF
  5. 不使用vue-cli来创建项目
  6. 添加lombok插件
  7. 题目1191:矩阵最大值
  8. unity3d游戏数据加密
  9. ClassNotFoundException和 NoClassDefFoundError区别验证
  10. 刷题总结——魔法森林(bzoj3669)