Android OkHttp文件上传与下载的进度监听扩展
http://www.loongwind.com/archives/290.html
上一篇文章介绍了用Retrofit实现文件的上传与下载,但是我们发现没办法监听上传下载的进度,毕竟我们在做开发的时候经常是要显示上传或者下载的进度了.虽然Retrofit没有给我们提供现成的api来监听进度,但是Retrofit很灵活,它底层网络访问是用的okhttp实现的,当然我们也可以设置其他第三方网络请求库,因为Retrofit可以设置client,我们可以由此来扩展下载上传的进度监听.
本文使用okhttp作为client来做,其实说白了跟用okhttp做下载上传进度监听几乎一样,参考了这篇文章:Android OkHttp文件上传与下载的进度监听扩展
1. 首先我们写两个接口用来下载和上传的进度监听回调:
/**
* 请求体进度回调接口,用于文件上传进度回调
*/
public interface ProgressRequestListener {
void onRequestProgress(long bytesWritten, long contentLength, boolean done);
}
/**
* 响应体进度回调接口,用于文件下载进度回调
*/
public interface ProgressResponseListener {
void onResponseProgress(long bytesRead, long contentLength, boolean done);
}
2. 实现 ProgressRequestBody、ProgressResponseBody
ProgressRequestBody类继承RequestBody用于请求进度监听,用于文件上传进度监听:
import java.io.IOException;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import okio.Buffer;
import okio.BufferedSink;
import okio.ForwardingSink;
import okio.Okio;
import okio.Sink;
import rx.Observable;
/**
* 包装的请求体,处理进度
*/
public class ProgressRequestBody extends RequestBody {
//实际的待包装请求体
private RequestBody requestBody;
//进度回调接口
private ProgressRequestListener progressListener;
//包装完成的BufferedSink
private BufferedSink bufferedSink;
/**
* 构造函数,赋值
* @param requestBody 待包装的请求体
* @param progressListener 回调接口
*/
public ProgressRequestBody(RequestBody requestBody, ProgressRequestListener progressListener) {
this.requestBody = requestBody;
this.progressListener = progressListener;
}
/**
* 重写调用实际的响应体的contentType
* @return MediaType
*/
@Override
public MediaType contentType() {
return requestBody.contentType();
}
/**
* 重写调用实际的响应体的contentLength
* @return contentLength
* @throws IOException 异常
*/
@Override
public long contentLength() throws IOException {
return requestBody.contentLength();
}
/**
* 重写进行写入
* @param sink BufferedSink
* @throws IOException 异常
*/
@Override
public void writeTo(BufferedSink sink) throws IOException {
if (bufferedSink == null) {
// //包装
bufferedSink = Okio.buffer(sink(sink));
}
//写入
requestBody.writeTo(bufferedSink);
//必须调用flush,否则最后一部分数据可能不会被写入
bufferedSink.flush();
}
/**
* 写入,回调进度接口
* @param sink Sink
* @return Sink
*/
private Sink sink(Sink sink) {
return new ForwardingSink(sink) {
//当前写入字节数
long bytesWritten = 0L;
//总字节长度,避免多次调用contentLength()方法
long contentLength = 0L;
@Override
public void write(Buffer source, long byteCount) throws IOException {
super.write(source, byteCount);
if (contentLength == 0) {
//获得contentLength的值,后续不再调用
contentLength = contentLength();
}
//增加当前写入的字节数
bytesWritten += byteCount;
//回调
if(progressListener != null){
progressListener.onRequestProgress(bytesWritten, contentLength, bytesWritten == contentLength);
}
}
};
}
}
ProgressResponseBody继承ResponseBody用于下载进度监听:
package com.cm.retrofit.http;
/**
* Created by Cmad on 2016/4/28.
*/
import java.io.IOException;
import okhttp3.MediaType;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSource;
import okio.ForwardingSource;
import okio.Okio;
import okio.Source;
/**
* 包装的响体,处理进度
*/
public class ProgressResponseBody extends ResponseBody {
//实际的待包装响应体
private final ResponseBody responseBody;
//进度回调接口
private final ProgressResponseListener progressListener;
//包装完成的BufferedSource
private BufferedSource bufferedSource;
/**
* 构造函数,赋值
*
* @param responseBody 待包装的响应体
* @param progressListener 回调接口
*/
public ProgressResponseBody(ResponseBody responseBody, ProgressResponseListener progressListener) {
this.responseBody = responseBody;
this.progressListener = progressListener;
}
/**
* 重写调用实际的响应体的contentType
*
* @return MediaType
*/
@Override
public MediaType contentType() {
return responseBody.contentType();
}
/**
* 重写调用实际的响应体的contentLength
*
* @return contentLength
* @throws IOException 异常
*/
@Override
public long contentLength() {
return responseBody.contentLength();
}
/**
* 重写进行包装source
*
* @return BufferedSource
*/
@Override
public BufferedSource source() {
if (bufferedSource == null) {
//包装
bufferedSource = Okio.buffer(source(responseBody.source()));
}
return bufferedSource;
}
/**
* 读取,回调进度接口
*
* @param source Source
* @return Source
*/
private Source source(Source source) {
return new ForwardingSource(source) {
//当前读取字节数
long totalBytesRead = 0L;
@Override
public long read(Buffer sink, long byteCount) throws IOException {
long bytesRead = super.read(sink, byteCount);
//增加当前读取的字节数,如果读取完成了bytesRead会返回-1
totalBytesRead += bytesRead != -1 ? bytesRead : 0;
//回调,如果contentLength()不知道长度,会返回-1
if(progressListener != null){
progressListener.onResponseProgress(totalBytesRead, responseBody.contentLength(), bytesRead == -1);
}
return bytesRead;
}
};
}
}
上面两个类里的writeTo跟source方法用到了sink跟Source,这两个类属于Okio,也是Square开源的Java io补充库,有兴趣的可以去了解一下,这里就不做详细介绍了.
3. 实现HttpClientHelper类
HttpClientHelper用于创建okhttp client,并对okhttpclient添加拦截事件,将requestBody和responseBody替换成我们自己实现的ProgressRequestBody和ProgressResponseBody
package com.cm.retrofit.http;
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
/**
* Created by Cmad on 2016/4/28.
*/
public class HttpClientHelper {
/**
* 包装OkHttpClient,用于下载文件的回调
* @param progressListener 进度回调接口
* @return 包装后的OkHttpClient
*/
public static OkHttpClient addProgressResponseListener(final ProgressResponseListener progressListener){
OkHttpClient.Builder client = new OkHttpClient.Builder();
//增加拦截器
client.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
//拦截
Response originalResponse = chain.proceed(chain.request());
//包装响应体并返回
return originalResponse.newBuilder()
.body(new ProgressResponseBody(originalResponse.body(), progressListener))
.build();
}
});
return client.build();
}
/**
* 包装OkHttpClient,用于上传文件的回调
* @param progressListener 进度回调接口
* @return 包装后的OkHttpClient
*/
public static OkHttpClient addProgressRequestListener(final ProgressRequestListener progressListener){
OkHttpClient.Builder client = new OkHttpClient.Builder();
//增加拦截器
client.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request request = original.newBuilder()
.method(original.method(), new ProgressRequestBody(original.body(),progressListener))
.build();
return chain.proceed(request);
}
});
return client.build();
}
}
4. 使用
ServiceGenerator类:
package com.cm.retrofit.service;
import com.cm.retrofit.http.HttpClientHelper;
import com.cm.retrofit.http.ProgressRequestListener;
import com.cm.retrofit.http.ProgressResponseListener;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.converter.protobuf.ProtoConverterFactory;
import retrofit2.converter.scalars.ScalarsConverterFactory;
/**
* Created by Cmad on 2016/4/28.
*/
public class ServiceGenerator {
private static final String HOST = "http://www.xxx.com/ ";
private static Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(HOST)
.addConverterFactory(ProtoConverterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create());
public static <T> T createService(Class<T> tClass){
return builder.build().create(tClass);
}
/**
* 创建带响应进度(下载进度)回调的service
*/
public static <T> T createResponseService(Class<T> tClass, ProgressResponseListener listener){
return builder
.client(HttpClientHelper.addProgressResponseListener(listener))
.build()
.create(tClass);
}
/**
* 创建带请求体进度(上传进度)回调的service
*/
public static <T> T createReqeustService(Class<T> tClass, ProgressRequestListener listener){
return builder
.client(HttpClientHelper.addProgressRequestListener(listener))
.build()
.create(tClass);
}
}
使用:
//用于上传
UpLoadService service = ServiceGenerator.createReqeustService(UpLoadService.class,this);
//下载
DownloadService downloadService = ServiceGenerator.createResponseService(DownloadService.class, this);
使用方法跟之前的一样,只是生成对应api service的时候调用带进度接口的方法就可以了,然后传入实现了回调接口的对象就可以.然后在回调里面进行界面的展示.
这样我们就实现了对上传下载的进度监听
补充:
上传进度监听我们也可以不用在okhttp的拦截里设置,可以在请求封装的requestbody的时候将requestbody封装成ProgressRequestBody,如下:
//之前的请求方法
UpLoadService service = ServiceGenerator.createService(UpLoadService.class);
File file = new File(fileUri);
RequestBody requestFile =
RequestBody.create(MediaType.parse("multipart/form-data"), file);
MultipartBody.Part body =
MultipartBody.Part.createFormData("file", file.getName(), requestFile);
Call<ResponseBody> call = service.upload(body);
//添加进度回调
UpLoadService service = ServiceGenerator.createService(UpLoadService.class);
File file = new File(fileUri);
RequestBody requestFile =
RequestBody.create(MediaType.parse("multipart/form-data"), file);
//将requestFile封装成ProgressRequestBody传入
MultipartBody.Part body =
MultipartBody.Part.createFormData("file", file.getName(), new ProgressRequestBody(requestFile,this));//this是在当前类实现了ProgressRequestListener接口
Call<ResponseBody> call = service.upload(body);
这样一样也可以实现对上传的进度监听.
Android OkHttp文件上传与下载的进度监听扩展的更多相关文章
- Android文件上传与下载
文件上传与下载 文件上传 -- 服务端 以Tomcat为服务器,Android客服端访问Servlet,经Servlet处理逻辑,最终将文件上传,这里就是简单模拟该功能,就将文件上传到本机的D:\\u ...
- 七牛云存储 qiniu 域名 回收 文件上传 备份 下载 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- java web学习总结(二十四) -------------------Servlet文件上传和下载的实现
在Web应用系统开发中,文件上传和下载功能是非常常用的功能,今天来讲一下JavaWeb中的文件上传和下载功能的实现. 对于文件上传,浏览器在上传的过程中是将文件以流的形式提交到服务器端的,如果直接使用 ...
- (转载)JavaWeb学习总结(五十)——文件上传和下载
源地址:http://www.cnblogs.com/xdp-gacl/p/4200090.html 在Web应用系统开发中,文件上传和下载功能是非常常用的功能,今天来讲一下JavaWeb中的文件上传 ...
- JavaWeb学习总结,文件上传和下载
在Web应用系统开发中,文件上传和下载功能是非常常用的功能,今天来讲一下JavaWeb中的文件上传和下载功能的实现. 对于文件上传,浏览器在上传的过程中是将文件以流的形式提交到服务器端的,如果直接使用 ...
- java文件上传和下载
简介 文件上传和下载是java web中常见的操作,文件上传主要是将文件通过IO流传放到服务器的某一个特定的文件夹下,而文件下载则是与文件上传相反,将文件从服务器的特定的文件夹下的文件通过IO流下载到 ...
- 使用jsp/servlet简单实现文件上传与下载
使用JSP/Servlet简单实现文件上传与下载 通过学习黑马jsp教学视频,我学会了使用jsp与servlet简单地实现web的文件的上传与下载,首先感谢黑马.好了,下面来简单了解如何通过使用 ...
- JavaWeb学习总结(五十)——文件上传和下载
在Web应用系统开发中,文件上传和下载功能是非常常用的功能,今天来讲一下JavaWeb中的文件上传和下载功能的实现. 对于文件上传,浏览器在上传的过程中是将文件以流的形式提交到服务器端的,如果直接使用 ...
- 文件上传和下载(可批量上传)——Spring(三)
在文件上传和下载(可批量上传)——Spring(二)的基础上,发现了文件下载时,只有在Chrome浏览器下文件名正常显示,还有发布到服务器后,不能上传到指定的文件夹目录,如上传20160310.txt ...
随机推荐
- >/dev/null 2>&1
>/dev/null 2>&1 大部分在 crontab 计划任务中都会年到未尾带 >/dev/null 2>&1,是什么意思呢? > 是重定向 /dev ...
- 转换xml格式的短信记录
<?xml version="1.0" encoding="UTF-8"?> <SMSRecord> <SMS> ...
- 调用sed命令的三种方式
调用sed命令的三种方式 调用sed有三种方式,一种为Shell命令行方式,另外两种是将sed命令写入脚本文件,然后执行该脚本文件. 三种方式的命令格式归纳如下: 一.在Shell命令行输入命令调用s ...
- Linux Shell 只列出目录的方法
在实际应用中,我们有时需要仅列出目录,下面是 4 种不同的方法. 1. 利用 ls 命令的 -d 选项: $ ls -d */ Desktop/ pic/ shell/ src/ 2. 利用 ls 命 ...
- opencv读写视频,对感兴趣区域进行裁剪
作为小码农,本人最近想对一段视频的某个区域进行处理,因此要将该段视频区域裁剪出来,搜搜网上,发现没有痕迹,是故自己琢磨一下,左右借鉴,编了如下代码,目标得以实现,希望对你有用. #include &q ...
- 解决ADT大量出现"Unexpected value from nativeGetEnabledTags: 0"的问题
在过滤器中输入下面的代码即可:(此方法搜索于网略) ^(?!.*(nativeGetEnabledTags)).*$
- Cocos2D的OALSimpleAudio预加载音频
在OALSimpleAudio接口中,没有委托机制和通知机制告诉你什么时候预加载完成.也不需要这样的机制. 如下代码: //Listing 11-1. Preloading a background ...
- Linux多线程编程初探
Linux线程介绍 进程与线程 典型的UNIX/Linux进程可以看成只有一个控制线程:一个进程在同一时刻只做一件事情.有了多个控制线程后,在程序设计时可以把进程设计成在同一时刻做不止一件事,每个线程 ...
- 苹果新的编程语言 Swift 语言进阶(十五)--协议
协议定义了适合某个特定任务或功能需要的方法.属性和其它需求的一个蓝图.协议本身不提供这些需求的实现,它只是描述了一个任务或功能实现的蓝图. 协议与java 语言中的接口定义类似,都是描述了一个实现可以 ...
- android 热修补之andfix实践
首先有这方面需要的请到https://github.com/xiangzhihong/andfixDemo/tree/master下载例子 对于网上提供的热补丁修复就不多说了,主要有这3种方式可以实现 ...