前面兩篇文章咱們講了項目總體的設計結構、入口類DownloadManager、下載類DownloadTask,這篇文章咱們講最重要的類DownLoadRequest。
因爲離前兩篇文章時間比較長了,感受陌生的同窗能夠先回顧一下:
Retrofit2的再封裝實戰—多線程下載與斷點續傳(一)
Retrofit2的再封裝實戰—多線程下載與斷點續傳(二)javascript
//彙總全部下載信息
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,下面介紹一個概念。若是咱們下載過一個文件,可是服務器將這個文件的內容置換掉了,客戶端如何判斷下載文件的時效性?
java
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值。git
初次接觸你多是蒙圈的,不要緊,這裏舉例來講明一下,我下載過一個文件A,這是http的response頭信息:
github
若是請求報文中的Last-Modified與服務器目標內容的Last-Modified相等,即沒有發生變化,那麼應答報文的狀態碼爲206。若是服務器目標內容發生了變化,那麼應答報文的狀態碼爲200。數據庫
這裏須要注意的是:If-Range首部行必須與Range首部行配套使用。若是請求報文中沒有Range首部行,那麼If-Range首部行就會被忽略。若是服務器不支持If-Range,那麼Range首部行也會被忽略。
好了,理論具有,只欠代碼了。繼續看queryDownLoadData方法,若是咱們下載過此url,而且Modified不爲空,調用接口來看看他是否更新過api
@GET CallgetHttpHeaderWithIfRange(@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回調:
服務器
整個queryDownLoadData就結束了,再回到start方法繼續看,60-86行遍歷全部下載任務,若是其中有total未獲取到的任務(對應前面獲取長度失敗),那麼直接返回錯誤,終止下載任務。若是都正常,疊加得到總下載值,若是總下載值=已經下載值,直接回調UI線程,已經下載結束了。88行,onStart()這時就已經回調給主線程下載百分比了,細心的朋友可能發現了,這是使用mMainThread回調UI線程,mMainThread是什麼?看過Retrofit源碼的朋友確定不陌生,他的實現原理其實就是運用了擁有MainLooper的hander,由於咱們的操做都是在異步線程中進行的,因此須要mMainThread是什麼回調主線程(這個在以前已經講過了),87行生成下載總回調,一個url是一個下載線程,一個下載線程對應一個本身的回調,那麼每一個線程的回調,統一匯聚到下載總回調,只有這個回調負責和UI接口通訊。
一張圖可能更能說明:微信
咱們還差什麼?入口類完成了,真正的下載類完成了,下載以前的巴拉巴拉已經完成了,那就只差下載任務了對不對?下面就真的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是內存緩存,以前就提過了,咱們用Taskprivate Map<String, Map<Integer, Future>> mUrlTaskMap = new ConcurrentHashMap<>();
保存緩存信息,String是url,Map
若是有下載記錄,就找未完成的生成DownLoadTask, executeNetWork就是加入線程池。若是沒有下載記錄,就是新文件,createDownLoadTask建立下載任務。
到這全部的多線程下載和斷點續傳就結束了,其實寫做過程是痛苦的,可是到結束仍是很欣慰的,相信您從開始看到這篇結束,整個項目的流程您是瞭解的,怎麼定製,怎麼修改bug應該也沒有問題了,畢竟思路有了,就差不停的實踐了,對嗎?
我但願這篇文章再思路上能夠幫助到您,那也是個人初衷啊!
下篇文章我會整理封裝的支持上拉,下拉,能夠添加Head的RecycleView。
最後,感謝私信過我,鼓勵過我,打賞過個人朋友,謝謝大家的支持。
GitHub地址我但願你們能夠積極fork,一塊兒修改,如發現問題,歡迎反饋。微信:hly1501