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

 

 本文由用戶 Avery1637 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!