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;
}
};
}
}

上面兩個類里的writeTosource方法用到了sinkSource,這兩個類屬於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);

這樣一樣也可以實現對上傳的進度監聽.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM