用Retrofit下载时想得到下载的进度可以重写ResponseBody,和设置拦截器:
1.设置请求方式以及Url:(注解streaming是用来下载大文件的,防止内存溢出,下载小文件就可以不用这个注解)
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,其中子线程是将文件写入本地)
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是关键)
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是用来传递信息给主线程的,因为下载的信息的获取是在子线程中:
/** * 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如下:
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:
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的拦截器。
最后总结一下,可能有点凌乱,最主要就是三点:
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这个注解的时候都通用还是用上面的方法比较好。):
- 其实就是有@Streaming这个注解的时候:
请求返回onResponse()方法(在主线程中运行)会立马执行,因为有@Streaming这个注解 的时候请求的返回的数据是以流的形式立马返回,就像知道了文件的具体目录位置一样,所以适用 于请求大文件。所以你要去读取它才会给你返回下载进度的数据。
- 没有@Streaming这个注解的时候:
请求返回onResponse()方法(在主线程中运行)不会立马执行,因为没有@Streaming这个注 解的时候数据是全部加载在内存中的,加载完了才会调用onResponse()方法(亲测),这个时 候你去在用流的形式去读写取文件是在内存中读写,会立马写到手机中,所以没能反应出下载的进 度提示,所以综上所诉,可以用本文的方法来达到下载进度的提示,通用于大小文件的请求下载。