你必須學會的okhttp——進階篇
今天上一篇部落格剛好在郭神公眾號出現了。也有一個多月沒寫點什麼了。今天就繼上一次的okhttp繼續深入瞭解把。在你必須學會的okhttp——入門篇中我簡單介紹了okhttp的使用方法。不瞭解可以看完在回來看這篇文章。
好了。話不多說。這次我主要介紹下okhttp如何實現多檔案斷點下載。
參考部落格:
http://blog.csdn.net/KevinsCSDN/article/details/51934274
之前對如何使用okhttp上傳與下載我們已經知道該怎麼做了。但是如何實現多檔案的操作呢?首先,在這邊闡述下我做的過程中所遇到的問題。
- 如何儲存url對應的當前長度以及總長度
- 如何實現暫停以及續傳操作
- 如何用一個info物件實現多檔案的下載
- response.body.contentlength與實際長度不一樣。(例如我獲取的長度是5.5M但他的實際長度是6.7M)
如何儲存當前長度以及總長度
我在網上看到很多demo對於這塊是用SQLite實現,我覺得完全可以Shareperference來儲存,通過他的url來儲存對應的當前長度和總長度,有人回說Shareperference不是隻能一個建對應一個值麼,兩個怎麼解決。我們可以通過MD5加密的url來儲存當前進度。通過MD2加密來儲存總進度。
如何實現暫停以及續傳操作
我們可以通過okhttp自帶的攔截器來實現其效果,具體程式碼如下:
private Call newCall(long current_length ) {
Request request = new Request.Builder()
.url(url)
.header("RANGE", "bytes=" + current_length + "-")
.build();
return client.newCall(request);
}
public OkHttpClient getProgressClient () {
Interceptor interceptor = new Interceptor() {
public Response intercept(Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
return originalResponse.newBuilder()
.body(originalResponse.body())
.build();
}
};
return new OkHttpClient.Builder()
.addNetworkInterceptor(interceptor)
.build();
}
如何用一個info物件實現多檔案的下載
其實在前面我也說了。用Shareperference來實現,具體怎麼說的。你的url,儲存路徑每次都是需要傳的,但是為了防止進度衝突(例:明明的A的進度,下載B的時候卻用的A的進度)。所以通過傳入的url來用Shareperference得到他儲存的當前長度與總長度來解決。
response.body.contentlength與實際長度不一樣。
其實。。。我也不知道。。百度了好久。得到的答案是在HTTP協議中,訊息實體長度和訊息實體的傳輸長度是有區別,比如說gzip壓縮下,訊息實體長度是壓縮前的長度,訊息實體的傳輸長度是gzip壓縮後的長度。還有種說法還有種說法是伺服器限制問題。不解。總之會導致獲取進度的時候,進度值是大於100的。。。
大致的問題和解決方法已經說明了。首先,我們先來看下效果圖。
最後上原始碼,相信你看懂了上面的思路。對於原始碼的理解就不是很難了。
public class DownLoadSupport {
private OkHttpClient okHttpClient;
private Call call;
@Bean
FileSupport fileSupport;
@Bean
ByteUtils byteUtils;
@Bean
SharePreferencesUtils sharePreferencesUtils;
private MD5Utils md5Utils;
public DownLoadSupport() {
md5Utils = new MD5Utils();
okHttpClient = getProgressClient();
}
public OkHttpClient getProgressClient() {
Interceptor interceptor = new Interceptor() {
public Response intercept(Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
return originalResponse.newBuilder()
.body(originalResponse.body())
.build();
}
};
return new OkHttpClient.Builder().addNetworkInterceptor(interceptor).build();
}
private Call newCall(HttpDownloadBean httpDownloadBean) {
Request request = new Request.Builder().tag(md5Utils.md5(httpDownloadBean.getUrl()))
.url(httpDownloadBean.getUrl()).addHeader("Accept-Encoding", "identity")
.header("RANGE", "bytes=" + sharePreferencesUtils.get(md5Utils.md5(httpDownloadBean.getUrl()), (long) 0) + "-")
.build();
return okHttpClient.newCall(request);
}
public void download(final HttpDownloadBean httpDownloadBean, final DownloadCallBack callBack) {
if (!sharePreferencesUtils.contains(md5Utils.md5(httpDownloadBean.getUrl()))) {
sharePreferencesUtils.put(md5Utils.md5(httpDownloadBean.getUrl()), (long) 0);
}
if (!sharePreferencesUtils.contains(md5Utils.md2(httpDownloadBean.getUrl()))) {
sharePreferencesUtils.put(md5Utils.md2(httpDownloadBean.getUrl()), (long) 0);
}
call = newCall(httpDownloadBean);
call.enqueue(new Callback() {
public void onFailure(Call call, IOException e) {
}
public void onResponse(Call call, Response response) throws IOException {
writeToSDCard(response, httpDownloadBean, callBack);
}
});
}
public void pause(HttpDownloadBean httpDownloadBean) {
for (Call call : okHttpClient.dispatcher().queuedCalls()) {
if (call.request().tag().equals(md5Utils.md5(httpDownloadBean.getUrl())))
call.cancel();
}
for (Call call : okHttpClient.dispatcher().runningCalls()) {
if (call.request().tag().equals(md5Utils.md5(httpDownloadBean.getUrl())))
call.cancel();
}
}
private void writeToSDCard(Response response, HttpDownloadBean httpDownloadBean, DownloadCallBack callBack) {
ResponseBody body = response.body();
InputStream input = body.byteStream();
FileChannel channelOut = null;
// 隨機訪問檔案,可以指定斷點續傳的起始位置
RandomAccessFile randomAccessFile = null;
long current = 0;
long total = 0;
current = (long) sharePreferencesUtils.get(md5Utils.md5(httpDownloadBean.getUrl()), (long) 0);
total = (long) sharePreferencesUtils.get(md5Utils.md2(httpDownloadBean.getUrl()), (long) 0);
if (total == 0) {
total = body.contentLength();
httpDownloadBean.setTotal_length(body.contentLength());
sharePreferencesUtils.put(md5Utils.md2(httpDownloadBean.getUrl()), httpDownloadBean.getTotal_length());
}
try {
randomAccessFile = new RandomAccessFile(fileSupport.createStorgeFile(httpDownloadBean.getStoragepath(), httpDownloadBean.getFilepath()), "rwd");
//Chanel NIO中的用法,由於RandomAccessFile沒有使用快取策略,直接使用會使得下載速度變慢,親測快取下載3.3秒的檔案,用普通的RandomAccessFile需要20多秒。
channelOut = randomAccessFile.getChannel();
// 記憶體對映,直接使用RandomAccessFile,是用其seek方法指定下載的起始位置,使用快取下載,在這裡指定下載位置。
MappedByteBuffer mappedBuffer = channelOut.map(FileChannel.MapMode.READ_WRITE, current, total);
byte[] buffer = new byte[1024];
int len;
while ((len = input.read(buffer)) != -1) {
current += len;
if (callBack != null) {
callBack.download(byteUtils.getSize(current) + byteUtils.getByte(current), byteUtils.getSize(total) + byteUtils.getByte(total));
callBack.downloadprogress((int) (current * 1.0f / total * 100));
}
httpDownloadBean.setCurrent_length(current);
if (current >= total) {
sharePreferencesUtils.remove(md5Utils.md5(httpDownloadBean.getUrl()));
sharePreferencesUtils.remove(md5Utils.md2(httpDownloadBean.getUrl()));
} else {
sharePreferencesUtils.put(md5Utils.md5(httpDownloadBean.getUrl()), httpDownloadBean.getCurrent_length());
}
mappedBuffer.put(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
input.close();
if (channelOut != null) {
channelOut.close();
}
if (randomAccessFile != null) {
randomAccessFile.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public interface DownloadCallBack {
void download(String current_progress, String total_progress);
void downloadprogress(int progress);
}
}
關於httpdownloadbean:
`
public class HttpDownloadBean {
private String url = null;
private String storagepath = null;
private String filepath = null;
private long current_length = 0L;
private long total_length = 0L;
public long getTotal_length() {
return total_length;
}
public void setTotal_length(long total_length) {
this.total_length = total_length;
}
public long getCurrent_length() {
return current_length;
}
public void setCurrent_length(long current_length) {
this.current_length = current_length;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getStoragepath() {
return storagepath;
}
public void setStoragepath(String storagepath) {
this.storagepath = storagepath;
}
public String getFilepath() {
return filepath;
}
public void setFilepath(String path) {
this.filepath = path;
}
}
就是這幾個資料,通過set和get來設定和獲取。
ShareperferenceUtils是關於Shareperference的工具類。filesupport是用來建立檔案的。byteutils可以不必理會。
有什麼問題可以提出來一起討論。這應該是年前最後一篇技術博文了。我也該為年後找工作的事件而忙碌了。