用Retrofit下載時想獲得下載的進度能夠重寫ResponseBody,和設置攔截器:java
1.設置請求方式以及Url:(註解streaming是用來下載大文件的,防止內存溢出,下載小文件就能夠不用這個註解)android
import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.http.GET; import retrofit2.http.Streaming; import retrofit2.http.Url; /** * Created by Zzm丶Fiona on 2017/7/27. */ public interface DownloadFileInterfaceForRetrofit { @Streaming @GET Call<ResponseBody> getCall(@Url String path); }
2.創建請求:(其中builder是OkHttpClient.Builder,其中子線程是將文件寫入本地)ssh
private final String mBaseUrl = "http://hc39.aipai.com/user/";
private final String videoPath = "499/56065499/1006/card/45789929/card.mp4?l=j";
Call<ResponseBody> call = getDownloadFileInterfaceForRetrofit(mBaseUrl, builder).getCall(videoPath);
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, final Response<ResponseBody> response) {
Log.i("zzmzzm", "onResponse: thread:"+Thread.currentThread().getName());
new Thread(){
@Override
public void run() {
super.run();
try {
InputStream is = response.body().byteStream();
File file = new File(Environment.getExternalStorageDirectory(), "12345.mp4");
FileOutputStream fos = new FileOutputStream(file);
BufferedInputStream bis = new BufferedInputStream(is);
byte[] buffer = new byte[1024*10];
int len;
while ((len = bis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
fos.flush();
}
fos.close();
bis.close();
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.i("zzmzzm", "鏈接失敗!!");
}
});
方法getDownloadFileInterfaceForRetrofit:(其中設置client是關鍵)ide
private DownloadFileInterfaceForRetrofit getDownloadFileInterfaceForRetrofit(String mBaseUrl, OkHttpClient.Builder builder) {
return new Retrofit
.Builder()
.baseUrl(mBaseUrl)
.addConverterFactory(GsonConverterFactory.create())
.client(builder.build())
.build()
.create(DownloadFileInterfaceForRetrofit.class);
}
3.其中設置client(builder.build())中的builder的來源:測試
OkHttpClient.Builder builder = RetrofitDownloadProgressUtil.getOkHttpClientBuilder(new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
ProgressBean progressBean = (ProgressBean) msg.obj;
long total = progressBean.getTotal();//下載的文件的總字節數 long bytesReading = progressBean.getBytesReading();//目前已經讀的字節數 boolean isDone = progressBean.getIsDone();//是否下載完成
progressBean = null;
}
}
});
上面的ProgressBean是用來儲存下載文件的相關信息,handler是用來傳遞信息給主線程的,由於下載的信息的獲取是在子線程中:ui
/** * Created by Zzm丶Fiona on 2017/7/28. */ public class ProgressBean { private long total; private long bytesReading; private boolean isDone; public long getTotal() { return total; } public void setTotal(long total) { this.total = total; } public long getBytesReading() { return bytesReading; } public void setBytesReading(long bytesReading) { this.bytesReading = bytesReading; } public boolean getIsDone() { return isDone; } public void setIsDone(boolean done) { isDone = done; } }
其中的RetrofitDownloadProgressUtil.getOkHttpClientBuilder以下:this
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import zzm.zzmotherthingsshouldknowfortheinterview.ProgressBean;
import zzm.zzmotherthingsshouldknowfortheinterview.ProgressResponseBody;
import zzm.zzmotherthingsshouldknowfortheinterview.RetrofitProgressListener;
/**
* Created by Zzm丶Fiona on 2017/7/28.
*/
public class RetrofitDownloadProgressUtil {
public static ProgressBean progressBean = new ProgressBean();//完成的時候賦值爲null,省得內存溢出
public static OkHttpClient.Builder getOkHttpClientBuilder(final Handler handler) {
OkHttpClient.Builder okHttpBuilder = new OkHttpClient.Builder();
okHttpBuilder.addNetworkInterceptor(new Interceptor() {
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
okhttp3.Response originalResponse = chain.proceed(chain.request());
return originalResponse.newBuilder().body(new ProgressResponseBody(originalResponse.body(), new RetrofitProgressListener() {
@Override
public void onProgress(long progress, long total, boolean isDone) {
//每次有進度的變化就會回調這個方法
if (null == progressBean) {
progressBean = new ProgressBean();
}
progressBean.setTotal(total);
progressBean.setBytesReading(progress);
progressBean.setDone(isDone);
Log.i("zzmzzm", "progress: " + "開始回調下載進度了!!");
handler.sendMessage(handler.obtainMessage(1, progressBean));
}
})
).build();
}
});
return okHttpBuilder;
}
}
上面的粉紅色的字就是關鍵其中的ProgressResponseBody就是繼承於ResponseBody:spa
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;
/**
* Created by Zzm丶Fiona on 2017/7/28.
*/
public class ProgressResponseBody extends ResponseBody {
private final ResponseBody responseBody;
private final RetrofitProgressListener retrofitProgressListener;
private BufferedSource bufferedSource;
public ProgressResponseBody(ResponseBody responseBody, RetrofitProgressListener retrofitProgressListener) {
this.responseBody = responseBody;
this.retrofitProgressListener = retrofitProgressListener;
}
@Override
public MediaType contentType() {
return responseBody.contentType();
}
@Override
public long contentLength() {
return responseBody.contentLength();
}
@Override
public BufferedSource source() {
if (bufferedSource == null) {
bufferedSource = Okio.buffer(getSource(responseBody.source()));
}
return bufferedSource;
}
private Source getSource(Source source) {
return new ForwardingSource(source) {
long totalBytesRead = 0L;
@Override
public long read(Buffer sink, long byteCount) throws IOException {
long bytesReading = super.read(sink, byteCount);
totalBytesRead += bytesReading != -1 ? bytesReading : 0;//讀取數據完成後bytesReading=-1;
retrofitProgressListener.onProgress(totalBytesRead, contentLength(), bytesReading == -1);
return bytesReading;
}
};
}
}
就是重寫了幾個方法,讓後做爲參數設置給了OkHttpClientBuilder的攔截器。.net
1.寫個類ProgressResponseBody繼承ResponseBody,而後重寫它的三個法,如上。線程
2.在重寫方法@Override public BufferedSource source() {}中調用本身寫的接口,方便回調後能夠獲得下載進度數據,如上面提供的代碼所示retrofitProgressListener調用onProgress方法。
3.okHttpBuilder 設置攔截器,裏面會用ProgressResponseBody做爲參數,如上面提供的代碼所示。
4.設置client:Retrofit.Builder().client(okHttpBuilder.build())。
完成以上的4點就能夠獲得下載進度的數據了,用retrofit下載東西時候,就能夠設置下載進度了。
可是根據本身的測試,在請求接口中有沒有@Streaming這個註解是有區別的,當沒有的時候,用於請求下載小文件,只須要在請求返回onResponse()方法中以下:
@Override
public void onResponse(Call<ResponseBody> call, final Response<ResponseBody> response) {
ResponseBody responseBody = response.body();
byte[] bytes = responseBody.bytes();//這個就是文件的所有內容,只是針對小文件,
//由於這個方法會把整個文件所有加載在內存中,因此避免內存溢出,
//這個方法只適用於請求小文件的狀況。
}
responseBody.bytes()方法的源碼以下:
/** * Returns the response as a byte array. * 把整個body加載在內存中 * <p>This method loads entire response body into memory. If the response body is very large this * may trigger an {@link OutOfMemoryError}. Prefer to stream the response body if this is a * possibility for your response. */ public final byte[] bytes() throws IOException{......}
可是在請求接口中有@Streaming這個註解的時候(用於請求大文件,返回的不是整個文件而是‘流’):
,必須結合上面給出的子線程中讀取文件的方法,纔可以獲得下載進的數據。發現獲取下載進度的子線程和請求返回onResponse()方法,是同時運行的。可是沒有@Streaming這個註解的時候是onResponse()方法後運行的,而且若是讀寫文件的那個線程一停,獲取下載進度數據的那個子線程也會中止(親測)。固然也能夠在加上@Streaming這個註解的時候,能夠在讀寫文件的子線程中設置進度提示,也是能夠的。但能達到有沒有@Streaming這個註解的時候都通用仍是用上面的方法比較好。
補充(解釋上面所訴狀況而且給出證據----讀寫文件的那個線程一停,獲取下載進度數據的那個子線程也會中止,能達到有沒有@Streaming這個註解的時候都通用仍是用上面的方法比較好。):
請求返回onResponse()方法(在主線程中運行)會立馬執行,由於有@Streaming這個註解 的時候請求的返回的數據是以流的形式立馬返回,就像知道了文件的具體目錄位置同樣,因此適用 於請求大文件。因此你要去讀取它纔會給你返回下載進度的數據。
請求返回onResponse()方法(在主線程中運行)不會立馬執行,由於沒有@Streaming這個注 解的時候數據是所有加載在內存中的,加載完了纔會調用onResponse()方法(親測),這個時 候你去在用流的形式去讀寫取文件是在內存中讀寫,會立馬寫到手機中,因此沒能反應出下載的進 度提示,因此綜上所訴,能夠用本文的方法來達到下載進度的提示,通用於大小文件的請求下載。