Android 之基于Retrofit的網絡框架Leopard,下載與斷點續傳深入分析
前言
Leopard 意為獵豹,在所有貓科動物中。獵豹體型最小,速度快、最穩定。這也是筆者想用這個名字命名這個Kit的原因。希望這個Kit能對部分開發者對于網絡框架封裝的一些思路有所幫助,筆者也在奮斗路上堅持總結進步中,共勉!最后,有任何問題可以提issuse給我,或者直接聯系本人(QQ:708854877 傻小孩b),喜歡可以為我點個star,你們的支持是我最大的動力~謝謝!
Leopard 下載深入解讀
一、基本原理
首先看下整個下載原理圖,Leopard下載管理原理圖如下:
LeoPard Download.png
對于Leopard的下載管理,由于底層由RxJava本地線程安全管理。所有的網絡操作都存在于IO線程,訂閱方則依賴于主線程,用于Layout處理。在下載管理中,所有的下載的操作都依賴于每一個獨立的DownLoadTask(這里指下載任務), 并且每個下載Task都依賴被DownLoadManager 所管理控制。其中每個下載的任務實時狀態的存在一個model (DownloadInfo)中,依賴圖如下:
Leopard DwonLoad_2.png
二、下載進度監聽解讀分析
一般情況,比較常見的下載進度監聽方法:在每次請求下載的時候,將流寫入文件的時候,實時將寫入的有效長度回調到UI界面,達到監聽實時的下載變化。Retrofit底層由OKhttp3支持,比較坑爹沒有想外拋出流寫入的回調。這時候,我們應該巧妙去重寫Okhttp3已經封裝好的RsonpseBody,在這底層回調監聽,看看Leopard怎么做的。關鍵代碼如下:
**
- Created by Yuan on 2016/8/25.
- Detail 下載響應監聽進度
*/
public class DownLoadResponseBody extends ResponseBody {
....
public BufferedSource source() {
if (bufferedSource == null) {
bufferedSource = Okio.buffer(source(mResponseBody.source()));
}
return bufferedSource;
}
public Source source(Source source) {
return new ForwardingSource(source) {
....
@Override
public long read(Buffer sink, long byteCount) throws IOException {
....
try {
bytesRead = super.read(sink, byteCount);
} catch (Exception e){
e.printStackTrace();
}
if (bytesRead != -1) {
} else {
bytesRead = 0;
}
totalLength += bytesRead;
downloadInfo.setProgress(downloadInfo.getProgress() + bytesRead);//實時更新downloadinfo的進度
long progress = downloadInfo.getProgress();
long total = downloadInfo.getFileLength();
postMainThread(progress,total);//這里處理回調
return bytesRead;
}
};
}
}</code></pre>
Okhttp3底層流的讀寫依賴于Okio,沒有接觸過的程序猿可以自行谷歌了解~在初始化構建RsponseBody的緩存源的時候調用了okio的buffer方法,這里構造的時候需要傳入Source接口,這里由請求響應的ReponseBody提供。真正需要進行流的監聽,其實就是對Source接口的read監聽。這里ForwardingSource操作類,讀者可以自行讀下ForwardingSource源碼,其實只是個委托操作類而已,真正的執行方,是實現了Source接口的ReponseBody。
三、斷點續傳解讀分析
說到斷點續傳,我們必須先了解下HTTP協議怎么對斷點續傳進行處理。首先,不是所有的服務器都支持斷點續傳,這里可以在客戶端通過服務器響應的頭信息,例如"Accept-Ranges: none"表示不支持。當然,不是所有服務器在進行斷點續傳的時候都返回狀態碼200,有時候可以是206,這個讀者自己注意下。
那么怎么指定斷點續傳位置?
斷點續傳需要做到三點:1、記錄斷點位置 2、緩存保存 3、服務器支持。
這里得繼續說下HTTP協議,首先我們必須理解Ranges這個屬性:表示在客戶端請求的時候,指定下載范圍,例如
Range: bytes=0-499 下載第0-499字節范圍的內容
Range: bytes=500-999 下載第500-999字節范圍的內容
Range: bytes=500- 下載第500-總大小字節范圍的內容
看看Leopard怎么做的,關鍵代碼:
public class DownLoadFileFactory implements Interceptor {
....
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request().newBuilder().addHeader("RANGE", "bytes=" + downloadInfo.getBreakProgress() + "-").build();
Response originalResponse = chain.proceed(request);
DownLoadResponseBody body = new DownLoadResponseBody(this.downloadInfo ,originalResponse.body(), fileRespondResult);
Response response = originalResponse.newBuilder().body(body).build();
return response;
}
}</code></pre>
Observable.create(new Observable.OnSubscribe<ResponseBody>() {
@Override
public void call(Subscriber<? super ResponseBody> subscriber) {
....
Request request = new Request.Builder().url(downloadInfo.getUrl()).build();
try {
Response response = okHttpClient.newCall(request).execute();
....
downloadInfo.getDownLoadTask().writeCache(response.body().byteStream());
// TODO: 2016/8/31 更新數據庫 這里記得做下數據庫延時更新
HttpDbUtil.instance.updateState(downloadInfo);
....
} catch (IOException e) {
....
}
}
})
從代碼中我們可以觀察到,Leopard對下載請求的時候,會通過DownloadInfo實時記錄的進度,在向服務器請求的時候加入RANGE的頭信息屬性,讓服務器在指定的范圍返回對于的流。并且,當在Rx控制的IO線程中,Leopard在call進行緩存存儲,再下載完成的時候在onNext對文件的最終追加合并,達到斷點續傳的作用。當然在Leopard中,斷點續傳也需要結合數據庫操作,包括下載狀態、進度、斷點等信息,這個由讀者去源碼閱讀,歡迎交流~
四、DownLoadManager 與DownLoadTask 解讀分析
DownLoadManager 顧名思義,Leopard中對所有的下載任務起一個總控制的管理器。DownLoadTask 顧名思義,Leopard中指的是單一下載任務,可提供下載管理(開始、暫停、停止等功能)。
首先我們看下DownLoadTask 關鍵代碼:
private LeopardClient getClient() {
return new LeopardClient.Builder()
.addRxJavaCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addDownLoadFileFactory(DownLoadFileFactory.create(this.fileRespondResult, this.downloadInfo))
.build();
}
以上代碼很明顯每個Task都有一個獨立的LeopardClient,在初始化的時候必須依賴DownLoadFileFactory,同時依然由Rx進行本地線程安全控制。
再其次看看其中一個Task的控制,下載控制
public void download( boolean isRestart) {
....
downloadInfo.setState(DownLoadManager.STATE_DOWNLOADING);//改變狀態為下載狀態
...
if (isRestart) {//如果是重新開始,則一切從0開始
stop();
resetProgress();
startPoints = 0L;
}
getClient().downLoadFile(this.downloadInfo, this.fileRespondResult, this);
}
Observable.create(new Observable.OnSubscribe<ResponseBody>() {
@Override
public void call(Subscriber<? super ResponseBody> subscriber) {
....
Request request = new Request.Builder().url(downloadInfo.getUrl()).build();
try {
Response response = okHttpClient.newCall(request).execute();
....
downloadInfo.getDownLoadTask().writeCache(response.body().byteStream());
// TODO: 2016/8/31 更新數據庫 這里記得做下數據庫延時更新
HttpDbUtil.instance.updateState(downloadInfo);
....
} catch (IOException e) {
....
}
}
})
以上代碼,剛剛文章前面已經貼過,現在重新貼下。從關鍵代碼,我們可以讀出:在Leopard下載任務中,其實也是走了okhttp3底層的異步請求中,關鍵代碼:new Request.Builder().url(downloadInfo.getUrl()).build();在Rx控制的IO線程中進行緩存處理、以及下載任務狀態變化。最后則是在主線程更新追加完整的文件。
最后看下DownLoadManager ,其中一個下載任務管理-暫停功能
首先貼下源代碼:
public void pauseAllTask(){
for (DownloadInfo downloadInfo : downloadInfosList){
downloadInfo.getDownLoadTask().pause();
}
}
public void pause() {
....
if (this.downloadInfo!=null &&this.downloadInfo.getSubscriber() != null)
this.downloadInfo.getSubscriber().unsubscribe();
....
HttpDbUtil.instance.updateState(downloadInfo);
}
讀者在這里應該可以很直接知道Leopard究竟怎么暫停,最新得Retrofit提供了能夠直接取消請求的方法 call.cancel();從暫停的角度,某種意義下,是我們本地以及存儲了緩存文件后,再下一次繼續下載的時候告訴服務器指定那個范圍開始下載的意思。因此,在暫停的時候,Leopard是通過取消下載的訂閱事件,達到下載暫停的效果。
五、總結
Leopard ,當然也存在許多問題,首先感謝讀者在github給的star。其實寫Leopard的原因是希望能夠幫助程序猿們,一起提高編程抽象思維能力。我們當然也不能只是搬運代碼的“程序猿”,更重要我們是需要寫出更高質量的代碼,創造的“程序猿”。
來自:http://www.jianshu.com/p/3fb0a77d2b60