okhttp、retrofit 2未提供上传、下载的进度回调,但是很多应用在UI显示方面需要加入进度显示。实现方式如下:
========
下载进度及拦截器:
public class DownloadProgressInterceptor implements Interceptor {
private DownloadProgressListener progressListener;
public DownloadProgressInterceptor(DownloadProgressListener progressListener) {
this.progressListener = progressListener;
}
@Override public Response intercept(Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
return originalResponse.newBuilder()
.body(new DownloadProgressResponseBody(originalResponse.body(), progressListener))
.build();
}
private static class DownloadProgressResponseBody extends ResponseBody {
private final ResponseBody responseBody;
private final DownloadProgressListener progressListener;
private BufferedSource bufferedSource;
public DownloadProgressResponseBody(ResponseBody responseBody,
DownloadProgressListener progressListener) {
this.responseBody = responseBody;
this.progressListener = progressListener;
}
@Override public MediaType contentType() {
return responseBody.contentType();
}
@Override public long contentLength() throws IOException {
return responseBody.contentLength();
}
@Override public BufferedSource source() throws IOException {
if (bufferedSource == null) {
bufferedSource = Okio.buffer(source(responseBody.source()));
}
return bufferedSource;
}
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);
// read() returns the number of bytes read, or -1 if this source is exhausted.
totalBytesRead += bytesRead != -1 ? bytesRead : 0;
if (null != progressListener) {
progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1);
}
return bytesRead;
}
};
}
}
public interface DownloadProgressListener {
void update(long bytesRead, long contentLength, boolean done);
}
}
上传进度
/**
* Decorates an OkHttp request body to count the number of bytes written when writing it. Can
* decorate any request body, but is most useful for tracking the upload progress of large
* multipart requests.
*
* @author Leo Nikkilä
*/
public class CountingRequestBody extends RequestBody {
protected RequestBody delegate;
protected Listener listener;
protected CountingSink countingSink;
public CountingRequestBody(RequestBody delegate, Listener listener) {
this.delegate = delegate;
this.listener = listener;
}
@Override public MediaType contentType() {
return delegate.contentType();
}
@Override public long contentLength() {
try {
return delegate.contentLength();
} catch (IOException e) {
e.printStackTrace();
}
return -1;
}
@Override public void writeTo(BufferedSink sink) throws IOException {
BufferedSink bufferedSink;
countingSink = new CountingSink(sink);
bufferedSink = Okio.buffer(countingSink);
delegate.writeTo(bufferedSink);
bufferedSink.flush();
}
protected final class CountingSink extends ForwardingSink {
private long bytesWritten = 0;
public CountingSink(Sink delegate) {
super(delegate);
}
@Override public void write(Buffer source, long byteCount) throws IOException {
super.write(source, byteCount);
bytesWritten += byteCount;
listener.onRequestProgress(bytesWritten, contentLength());
}
}
public static interface Listener {
public void onRequestProgress(long bytesWritten, long contentLength);
}
}
上传进度拦截器
public class UpLoadProgressInterceptor implements Interceptor {
private CountingRequestBody.Listener progressListener;
public UpLoadProgressInterceptor(CountingRequestBody.Listener progressListener) {
this.progressListener = progressListener;
}
@Override public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
if (originalRequest.body() == null) {
return chain.proceed(originalRequest);
}
Request progressRequest = originalRequest.newBuilder()
.method(originalRequest.method(),
new CountingRequestBody(originalRequest.body(), progressListener))
.build();
return chain.proceed(progressRequest);
}
}
====
使用方式:
如果只是在okhttp中使用,上传可以使用CountingRequestBody类,下载可以把DownloadProgressResponseBody单独抽出来使用。使用时new出来,传入相应参数即可。
如果是在retrofit 2中使用,可以使用拦截器的方式。类似DownloadProgressInterceptor,UpLoadProgressInterceptor,关键代码如下:
OkHttpClientManager.getInstance()
.getOkHttpClient()
.networkInterceptors()
.add(upLoadProgressInterceptor);
/*上传图片请求*/
@Multipart @POST(ConstantsNetInterface.COURSE_UPLOAD_PIC) Call<UploadPicBean> uploadPic(
@PartMap Map<String, ?> params);
RequestBody fileBody = RequestBody.create(MediaType.parse("image/*"), imgFile);
mParams = new HashMap<>();//请求参数
mParams.put("file\"; filename=\"" + imgFile.getName() + "", fileBody);
===
关于Batow提的问题(为方便例子使用了Rxjava,不使用Rxjava则是用下面的Call来处理):
@GET("{path}") @Streaming Call<ResponseBody> download(@Path("path") String path);
@GET("{path}") @Streaming Observable<ResponseBody> downloadFile(@Path("path") String path);
public static void downloadFile(final String path, final Subscriber<Boolean> subscriber) {
RetrofitManager.getInstance()
.createDownloadApiService(Api.class)
.downloadFile(path)
.map(new Func1<ResponseBody, Boolean>() {
@Override public Boolean call(ResponseBody responseBody) {
return writeFileToSD(path, responseBody);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
}
private static boolean writeFileToSD(String pathName, ResponseBody responseBody) {
String sdStatus = Environment.getExternalStorageState();
if (!sdStatus.equals(Environment.MEDIA_MOUNTED)) {
Logger.d("SD card is not avaiable/writeable right now.");
return false;
}
try {
String fileName = pathName.substring(pathName.lastIndexOf("/"));
Logger.d("fileName-->" + fileName);
File path = Environment.getExternalStorageDirectory();
File file = new File(path.getPath() + fileName);
if (!file.exists()) {
Logger.d("Create the file:" + file.getPath());
file.createNewFile();
}
FileOutputStream stream = new FileOutputStream(file);
byte[] buf = responseBody.bytes();
stream.write(buf);
stream.close();
return true;
} catch (Exception e) {
Logger.e("Error on writeFilToSD.");
e.printStackTrace();
}
return false;
}
NetworkWrapper.downloadFile(path, new Subscriber<Boolean>() {
@Override public void onCompleted() {
Logger.d("onCompleted");
}
@Override public void onError(Throwable e) {
Logger.d("onError:" + e);
}
@Override public void onNext(Boolean aBoolean) {
Logger.d("onNext" + aBoolean);
if (aBoolean) {
Toast.makeText(DownloadActivity.this, "pic download finish", Toast.LENGTH_LONG)
.show();
}
}
});
===