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 ...
随机推荐
- iOS中 MPMoviePlayer 实现视频音频播放 作者:韩俊强
ios播放视频文件一般使用 MPMoviePlayerViewController 和 MPMoviePlayerController.前者是一个view,后者是个Controller.区别就是 MP ...
- Java将网络地址对应的图片转成本地的图片
只知道浏览器使用的是HTTP协议,那么如何将网络资源使用JavaHTTP下载下来呢! 这只是一个非常简单的小示例,只是不想每次碰到关于此方面的内容忘了就无从下手! 示例创建HttpURLConn ...
- TCP状态转换
最近笔试遇到一个题目:如果tcp建立连接时第三次握手失败,tcp会做何操作?该问题的本质是判断我们对tcp的状态转换是否能有比较深刻的理解.只要理解了下面的状态转换图,很容易回答上述问题. 在此,将& ...
- Linux Shell脚本攻略学习总结:二
比较与测试 程序中的流程控制是由比较和测试语句来处理的. 我们可以用if,if else 以及逻辑运算符来执行测试,而用一些比较运算符来比较数据项.另外,有一个test 命令也可以用来进行测试.让我们 ...
- (十二)UITableView的基本使用
UITableView之所以支持滚动,是因为继承自UIScrollView.默认是垂直滚动,性能极佳. UITableView的两种样式: 1.UITableViewStylePlain ...
- Dynamics CRM 2011 仪表盘(dashbord)中加入公告(announcement)模块
具体步骤如下: 1.将一下代码黏贴入一个取名叫"announcementsondashboard.htm"的html文件中,当然文件名你随便起无所谓. <span style ...
- 一个App与另一个App之间的交互,添加了自己的一些理解
URL Scheme 是什么? iOS有个特性就是应用将其自身"绑定"到一个自定义 URL scheme 上,该 scheme用于从浏览器或其他应用中启动本应用.常见的分享到第三方 ...
- 【Coding算法导论】第4章:最大子数组问题
Coding算法导论 本系列文章主要针对算法导论一书上的算法,将书中的伪代码用C++实现 代码未经过大量数据测试,如有问题,希望能在回复中指出! (一)问题描述 给定一个数组,求数组中连续的子数组的和 ...
- Leetcode_144_Binary Tree Preorder Traversal
本文是在学习中的总结,欢迎转载但请注明出处:http://blog.csdn.net/pistolove/article/details/42876699 Given a binary tree, r ...
- NDK 与 JNI 的关系
简介 JNI是java语言提供的Java和C/C++相互沟通的机制,Java可以通过JNI调用本地的C/C++代码,本地的C/C++的代码也可以调用java代码.JNI 是本地编程接口,Java和C/ ...