1. 程式人生 > >Retrofit2的再封裝實戰—多執行緒下載與斷點續傳(三)

Retrofit2的再封裝實戰—多執行緒下載與斷點續傳(三)

流程圖



回憶之前文章提到的,我們將需要下載的任務構造成一個List傳入DownLoadManager中,DownLoadManager呼叫方法downLoad生成DownLoadRequest物件,同時將List引數代入,最後呼叫downLoadRequest.start()方法。

一、Start


start


我們將下載的部分操作封裝成DownLoadHandle物件,59行我們呼叫queryDownLoadData方法,對應上面結構圖的查詢下載總長度步驟,這是一個耗時操作,不用擔心,我們在之前的DownLoadManager中已經建立執行緒了,這裡面的所有操作都是在子執行緒中進行的,UI執行緒是不會被阻塞的。
queryDownLoadData:

//彙總所有下載資訊
List<DownLoadEntity> queryDownLoadData(List<DownLoadEntity> list) {
    final Iterator iterator = list.iterator();
    while (iterator.hasNext()) {
        DownLoadEntity downLoadEntity = (DownLoadEntity) iterator.next();
        downLoadEntity.downed = 0;
        Call<ResponseBody> mResponseCall = null
; List<DownLoadEntity> dataList = mDownLoadDatabase.query(downLoadEntity.url); if (dataList.size() > 0) { downLoadEntity.multiList = dataList; if (!TextUtils.isEmpty(dataList.get(0).lastModify)) { mResponseCall = NetWorkRequest.getInstance().getDownLoadService().getHttpHeaderWithIfRange(downLoadEntity.url, dataList.get(0
).lastModify, "bytes=" + 0 + "-" + 0); } } else { mResponseCall = NetWorkRequest.getInstance().getDownLoadService().getHttpHeader(downLoadEntity.url, "bytes=" + 0 + "-" + 0); } executeGetFileWork(mResponseCall, new GetFileCount(downLoadEntity, mResponseCall)); } while (!mGetFileService.isShutdown() && getCount() != list.size()) { } return list; }

迭代List,先在資料庫中查詢當前任務的url,如果查詢結果大於0,說明我們曾經下載過此url,將dataList賦值給multList,下面介紹一個概念。如果我們下載過一個檔案,但是伺服器將這個檔案的內容置換掉了,客戶端如何判斷下載檔案的時效性?


request


http請求頭中有個If-Range屬性,下面摘自網路上解釋:

If-Range是另一個起條件判斷的請求頭(我們之前講過If-Match/If-None-Match,If-Modified-Since/If-Unmodified-Since).If-Range頭用來避免客戶端在下載了某資源(比如圖片)的一部分後,下次重新下載又從頭開始下載。使用If-Range之後,客戶端每次可以從上次下載的部分之後繼續開始下載。
If-Range的使用格式為:If-Range: Etag|Http-Date也就是說If-Range後面可以使用Etag或者Last-Modified返回的值:
If-Range: "df6b0-b4a-3be1b5e1"
If-Range: Tue, 8 Jul 2008 05:05:56 GMT
邏輯上來講,上面2種方式分別和If-Match,If-Unmodified-Since的工作原理一樣,他們的值正是伺服器返回的Etag和Last-Modified值。

初次接觸你可能是蒙圈的,沒關係,這裡舉例來說明一下,我下載過一個檔案A,這是http的response頭資訊:


response


Last-Modified,直觀上很清晰他是一個關於時間戳的屬性。他代表著檔案最後修改時間,我們需要做的就是保持這個欄位到本地,下次請求時候賦值給If-Range頭資訊,伺服器會告訴你這檔案是否更新過。怎麼判斷?

如果請求報文中的Last-Modified與伺服器目標內容的Last-Modified相等,即沒有發生變化,那麼應答報文的狀態碼為206。如果伺服器目標內容發生了變化,那麼應答報文的狀態碼為200。

好了,理論具備,只欠程式碼了。繼續看queryDownLoadData方法,如果我們下載過此url,並且Modified不為空,呼叫介面來看看他是否更新過
@[email protected]<ResponseBody> getHttpHeaderWithIfRange(@Url String fileUrl, @Header("If-Range") String lastModify, @Header("Range") String range);
和我們之前的downloadFile方法差不多,這裡不多解釋。繼續看,如果沒下載過,直接呼叫getHttpHeader方法,不需要If-Range頭。
executeGetFileWork方法很簡單隻有兩行程式碼:

private void executeGetFileWork(Call<ResponseBody> call, GetFileCountListener listener) {
    GetFileCountTask getFileCountTask = new GetFileCountTask(call, listener);    
    mGetFileService.submit(getFileCountTask);
}

GetFileCountTask,看名字就知道了,建立獲取檔案長度的任務,然後加入執行緒池。
GetFileCountListener查詢結果回撥:

public interface GetFileCountListener {
    void success(boolean isSupportMulti, boolean isNew, String modified, Long fileSize);
    void failed()
}

很簡單兩個方法,成功和失敗。GetFileCountTask中通過response的返回報文,判斷是否支援多執行緒下載,是否更新過,modified值,下載長度,程式碼很簡單這裡就不貼了,感興趣的同學自己擼程式碼看吧。下面看GetFileCountListener回撥:


GetFileCountListener回撥


先看失敗 如果重試次數小於0,停止所有任務,如果未到0,則重新嘗試獲取長度,重複次數預設為3次。



成功後賦值mDownLoadEntity相關屬性,93-108行,如果未更換檔案,判斷下載檔案還是否存在,存在說明只要下載剩餘任務就可以了,不存在,當新任務對待。
setCount方法結合queryDownLoadData最後的while迴圈看,有個全域性變數記錄任務的完成數,每個url任務完成或者失敗後count +1,如果未完成任務,或者執行緒池未被關閉則一直迴圈等待。
這裡提醒下:尤其每個task都是一個執行緒,所以這裡的計數,必須要考慮執行緒同步問題!
整個queryDownLoadData就結束了,再回到start方法繼續看,60-86行遍歷所有下載任務,獲得總下載值,如果總下載值=已經下載值,直接回調UI執行緒,已經下載結束了。87生成下載總回撥,我們知道一個url是一個執行緒,一個執行緒對應一個自己的回撥,那麼每個執行緒的回撥,統一匯聚到下載總回撥,只有這個回撥負責和UI介面通訊。
一張圖可能更能說明:


回撥結構圖


從下向上看,UI回撥和總回撥1對1關係,總回撥裡有UI回撥引用,總回撥和每個Task的回撥,1對多關係,每個Listener中有總回撥引用。
現在從上向下看,Listener下載了1MB,告訴總回撥:“你可以給UI回調了”,UI回撥就老老實實告訴UI我下載了1MB了。簡單的說,總回撥就是一個代理類。

二、AddDownLoadTask

我們還差什麼?入口類完成了,真正的下載類完成了,下載之前的巴拉巴拉已經完成了,那就只差下載任務了對不對?下面就真的easy了。

private void addDownLoadTask(DownLoadEntity downLoadEntity) {
    Map<Integer, Future> downLoadTaskMap = new ConcurrentHashMap<>();
    MultiDownLoaderListener multiDownLoaderListener = new MultiDownLoaderListener(mDownCallBackListener);
    if (downLoadEntity.multiList != null && downLoadEntity.multiList.size() != 0) {
        for (int i = 0; i < downLoadEntity.multiList.size(); i++) {
            DownLoadEntity entity = downLoadEntity.multiList.get(i);            
            //當前分支是否下載完成
            if (entity.downed + entity.start > entity.end) {                continue;
            }
            DownLoadTask downLoadTask = new DownLoadTask.Builder().downLoadModel(entity).downLoadTaskListener(multiDownLoaderListener).build();
            executeNetWork(entity, downLoadTask, downLoadTaskMap);
        }
    } else {
        //檔案不存在 直接下載        
        createDownLoadTask(downLoadEntity, NEW_DOWN_BEGIN, downLoadTaskMap, multiDownLoaderListener);
    }
}

map是記憶體快取,之前就提過了,我們用
//URL下載Taskprivate Map<String, Map<Integer, Future>> mUrlTaskMap = new ConcurrentHashMap<>();
儲存快取資訊,String是url,Map<Integer, Future>是當前url下的任務,為啥又用個Map?因為可能是多執行緒啊!Integer,下載任務的唯一ID,這裡是資料庫主鍵,Future不瞭解的同學請自行百度,這就是下載任務。
如果有下載記錄,就找未完成的生成DownLoadTask, executeNetWork就是加入執行緒池。如果沒有下載記錄,就是新檔案,createDownLoadTask建立下載任務。


createDownLoadTask


127-141 如果下載任務大於多執行緒下載的分割值,切成多段進行下載。else 單執行緒下載。
好了 大概的流程到這裡就結束了,還差什麼?Task任務回撥,主執行緒回撥,這些程式碼沒有貼出來,大家自己去發現吧。這裡用了代理模式,還有很多的多執行緒資料安全方面的程式碼。下載Error重置下載機制,判斷下載是否真正結束機制。對快取的操作,map套map的增刪改查。

總結

到這所有的多執行緒下載和斷點續傳就結束了,其實寫作過程是痛苦的,但是到結束還是很欣慰的,相信您從開始看到這篇結束,整個專案的流程您是瞭解的,怎麼定製,怎麼修改bug應該也沒有問題了,畢竟思路有了,就差不停的實踐了,對嗎?
我希望這篇文章再思路上可以幫助到您,那也是我的初衷啊!
下篇文章我會整理封裝的支援上拉,下拉,可以新增Head的RecycleView。
最後,感謝私信過我,鼓勵過我,打賞過我的朋友,謝謝你們的支援。
GitHub地址
我希望大家可以積極fork,一起修改,如發現問題,歡迎反饋。
微信:hly1501

相關推薦

Retrofit2封裝實戰執行下載斷點()

流程圖 回憶之前文章提到的,我們將需要下載的任務構造成一個List傳入DownLoadManager中,DownLoadManager呼叫方法downLoad生成DownLoadRequest物件,同時將List引數代入,最後呼叫downLoadRequest.s

Android 實現執行下載檔案+斷點

                            Android 多執行緒下載檔案+斷點續傳       在專案快要結束的時候,發現了app沒有版本更新的功能,於是找到一些過去的資料,在app上應用完成了版本更新,現在記錄一下apk的下載,也就是如何通過多執行緒將ap

C# Http執行下載斷點

using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using Sy

Java實現線程下載斷點

get import 服務 結束 parseint RR range turn con 開三個線程下載,代碼: package demo; import java.io.InputStream; import java.io.RandomAccessFile; impo

基於OkHttp Retrofit RxJava 執行下載。請求、快取、自動更新.限制佇列數.封裝

XDownload介紹 本庫封裝基於Okhttp3,Retrofit2,RxJava2.0,Greendao3.2 ps : 當然當然,都封裝好了,你也可以無視 GitHub地址 如果你覺得好用,對你有幫助,請給個star 介面

libcurl實現執行下載

       libcurl官網(http://curl.haxx.se/)是一個很強大網路功能的庫,支援當前DICT, FILE, FTP, FTPS, Gopher, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS,

Android核心技術-day05-05-安卓下的執行下載(帶ProgressBar)有bug版

package com.gaozewen.multidownloader; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; impor

Android核心技術-day05-04-JavaSE執行下載

package com.gaozewen.lib; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import ja

斷點執行下載

多執行緒下載 原理:伺服器CPU分配給每條執行緒的時間片相同,伺服器頻寬平均分配給每條執行緒,所以客戶端開啟的執行緒越多,就能搶佔到更多的伺服器資源,所以使用多執行緒下載的話,速度會更快。 JavaSE實現帶斷點續傳的多執行緒下載步驟: 1、傳送http請求至下載地址,獲取要下載

c++封裝win32執行簡介

  /* 執行緒函式如果宣告在類中, 則需要靜態函式. 否則普通函式中將壓入this引數 . 所以在類中先宣告一個靜態函式,用於執行緒的啟動. 再寫一個虛擬函式 run , 線上程中呼叫, 也可以用於子類的重寫 */ class ThreadObj

Android原生下載(下篇)檔案下載+執行下載

零、前言 1.上篇實現了單執行緒的單檔案下載,本篇將講述多個檔案的多執行緒下載,在此之前希望你先弄懂上篇 2.本篇將用到上篇之外的技術: 多執行緒、執行緒池(簡)、RecyclerView、資料庫多執行緒訪問下的注意點、volatile AtomicLong(簡) 最終靜態的效果

android執行下載-HttpURLConnection

android多執行緒下載-HttpURLConnection private static String PATH = "http://192.168.37.2:8080/http/02.jpg"; public static void main(String[] args)

Java編寫的一個執行下載

這裡只是演示這個下載器如何使用以及介面 1.可以百度TIM:找到下載介面,複製下載地址: 2.貼上到下載器介面如下圖:(注意儲存地址一定是存在的) 3.點選開始下載:(如果想要暫停或者繼續可以先選中下載行,點選暫停或者繼續)

Java執行下載原理實現

多執行緒下載原理 客戶端要下載一個檔案, 首先請求伺服器,伺服器將這個檔案傳送給客戶端,客戶端儲存到本地, 完成了一個下載的過程. 多執行緒下載的思想是客戶端開啟多個執行緒同時下載,每個執行緒只負責下載檔案的一部分, 當所有執行緒下載完成的時候,檔案下載完畢.

風火程式設計--python執行下載檔案

多執行緒下載檔案 出現異常的執行緒會自動重新下載, 所有的進度會在同一位置輪換顯示 import os import datetime from urllib import request url_list = ["url1","url2","url3"

axel: 執行下載工具,下載檔案時可以替代curl、wget

 Axel 是 Linux 下一個不錯的HTTP/FTP高速下載工具。支援多執行緒下載、斷點續傳,且可以從多個地址或者從一個地址的多個連線來下載同一個檔案。適合網速不給力時多執行緒下載提高下載速度。比如在國內VPS或伺服器上下載lnmp一鍵安裝包用Axel就比wget快。opti

java使用 網路連線+RandomAccessFile + io 執行實現執行下載檔案並顯示實時網速

下載檔案的時候,一個大檔案切成很多片,用多執行緒下載,速度會快很多 閱讀程式碼的時候注意檢視程式碼裡面的註釋 想用多執行緒下載檔案,則, 第一:得了解  RandomAccessFile  類,這是個隨機訪問檔案類,裡面可以設定 訪問的 開始地址和結束地址,且該類可讀可

執行下載一個檔案

瞭解了這麼多與執行緒相關的知識,那麼我們也要實戰一下了(在學習本篇知識之前,如果對java中的網路基礎連結不太熟悉的,建議先去學一下java網路程式設計,再來看本文章。) 因為本篇是多執行緒下載的demo,所以就直接附上程式碼,裡面都寫好了註釋,不影響對本篇的學習。

Deepin學習筆記---如何執行下載網路檔案

**** 命令列下載網路檔案* 當時看到師兄用多執行緒下載一個檔案,簡直帥呆了,然後自己偷摸學習了一下,很爽,這邊推薦兩個命令列下載網路檔案的方法,前提是要有下載連結哦! 一、 1.wget下載方式 wget http://www.linuxsense.or

Android實現執行下載檔案,支援斷點

本篇部落格主要介紹多執行緒去下載檔案,以下載多個app為例。不管去下在app,音視訊等檔案,實現起來都一樣。篇幅有點長,先貼張美女圖看看 正在下載的效果圖 下載完成效果圖 小編的下載路徑是放在sd卡的絕對路徑中,方便驗證! 工程目錄圖 介紹下每