Retrofit2的再封裝實(shí)戰(zhàn)—多線程下載與斷點(diǎn)續(xù)傳(三)

終結(jié)篇

前面兩篇文章我們講了項(xiàng)目整體的設(shè)計(jì)結(jié)構(gòu)、入口類DownloadManager、下載類DownloadTask适荣,這篇文章我們講最重要的類DownLoadRequest。
由于離前兩篇文章時間比較長了院领,感覺陌生的同學(xué)可以先回顧一下:
Retrofit2的再封裝實(shí)戰(zhàn)—多線程下載與斷點(diǎn)續(xù)傳(一)
Retrofit2的再封裝實(shí)戰(zhàn)—多線程下載與斷點(diǎn)續(xù)傳(二)

流程圖


回憶之前文章提到的弛矛,我們將需要下載的任務(wù)構(gòu)造成一個List傳入DownLoadManager中,DownLoadManager調(diào)用方法downLoad生成DownLoadRequest對象比然,同時將List參數(shù)代入丈氓,最后調(diào)用downLoadRequest.start()方法。

一强法、Start

start

我們將下載的部分操作封裝成DownLoadHandle對象万俗,59行我們調(diào)用queryDownLoadData方法,對應(yīng)上面結(jié)構(gòu)圖的查詢下載總長度步驟饮怯,這是一個耗時操作闰歪,不用擔(dān)心,我們在之前的DownLoadManager中已經(jīng)創(chuàng)建線程了蓖墅,這里面的所有操作都是在子線程中進(jìn)行的库倘,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,先在數(shù)據(jù)庫中查詢當(dāng)前任務(wù)的url教翩,如果查詢結(jié)果大于0杆勇,說明我們曾經(jīng)下載過此url,將dataList賦值給multList饱亿,下面介紹一個概念蚜退。如果我們下載過一個文件,但是服務(wù)器將這個文件的內(nèi)容置換掉了路捧,客戶端如何判斷下載文件的時效性关霸?


request

http請求頭中有個If-Range屬性,下面摘自網(wǎng)絡(luò)上解釋:

If-Range是另一個起條件判斷的請求頭(我們之前講過If-Match/If-None-Match,If-Modified-Since/If-Unmodified-Since).If-Range頭用來避免客戶端在下載了某資源(比如圖片)的一部分后杰扫,下次重新下載又從頭開始下載队寇。使用If-Range之后,客戶端每次可以從上次下載的部分之后繼續(xù)開始下載章姓。
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的工作原理一樣,他們的值正是服務(wù)器返回的Etag和Last-Modified值凡伊。

初次接觸你可能是蒙圈的零渐,沒關(guān)系,這里舉例來說明一下系忙,我下載過一個文件A诵盼,這是http的response頭信息:


response

Last-Modified,直觀上很清晰他是一個關(guān)于時間戳的屬性银还。他代表著文件最后修改時間风宁,我們需要做的就是保持這個字段到本地,下次請求時候賦值給If-Range頭信息蛹疯,服務(wù)器會告訴你這文件是否更新過戒财。怎么判斷?

如果請求報文中的Last-Modified與服務(wù)器目標(biāo)內(nèi)容的Last-Modified相等捺弦,即沒有發(fā)生變化饮寞,那么應(yīng)答報文的狀態(tài)碼為206。如果服務(wù)器目標(biāo)內(nèi)容發(fā)生了變化列吼,那么應(yīng)答報文的狀態(tài)碼為200幽崩。

好了,理論具備寞钥,只欠代碼了慌申。繼續(xù)看queryDownLoadData方法,如果我們下載過此url凑耻,并且Modified不為空太示,調(diào)用接口來看看他是否更新過
@Streaming @GET Call<ResponseBody> getHttpHeaderWithIfRange(@Url String fileUrl, @Header("If-Range") String lastModify, @Header("Range") String range);
和我們之前的downloadFile方法差不多柠贤,這里不多解釋。繼續(xù)看类缤,如果沒下載過臼勉,直接調(diào)用getHttpHeader方法,不需要If-Range頭餐弱。
executeGetFileWork方法很簡單只有兩行代碼:

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

GetFileCountTask宴霸,看名字就知道了,創(chuàng)建獲取文件長度的任務(wù)膏蚓,然后加入線程池瓢谢。
GetFileCountListener查詢結(jié)果回調(diào):

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

很簡單兩個方法,成功和失敗驮瞧。GetFileCountTask中通過response的返回報文氓扛,判斷是否支持多線程下載,是否更新過论笔,modified值采郎,下載長度,代碼很簡單這里就不貼了狂魔,感興趣的同學(xué)自己擼代碼看吧蒜埋。下面看GetFileCountListener回調(diào):


GetFileCountListener回調(diào)

先看失敗 如果重試次數(shù)小于0,停止所有任務(wù)最楷,如果未到0整份,則重新嘗試獲取長度,重復(fù)次數(shù)默認(rèn)為3次籽孙。


成功后賦值mDownLoadEntity相關(guān)屬性烈评,93-108行,如果未更換文件蚯撩,判斷下載文件還是否存在础倍,存在說明只要下載剩余任務(wù)就可以了烛占,不存在胎挎,當(dāng)新任務(wù)對待。
setCount方法結(jié)合queryDownLoadData最后的while循環(huán)看忆家,有個全局變量記錄任務(wù)的完成數(shù)犹菇,每個url任務(wù)完成或者失敗后count +1,如果未完成任務(wù)芽卿,或者線程池未被關(guān)閉則一直循環(huán)等待揭芍。
這里提醒下:尤其每個task都是一個線程,所以這里的計(jì)數(shù)卸例,必須要考慮線程同步問題称杨!
整個queryDownLoadData就結(jié)束了肌毅,再回到start方法繼續(xù)看,60-86行遍歷所有下載任務(wù)姑原,獲得總下載值悬而,如果總下載值=已經(jīng)下載值,直接回調(diào)UI線程锭汛,已經(jīng)下載結(jié)束了笨奠。87生成下載總回調(diào),我們知道一個url是一個線程唤殴,一個線程對應(yīng)一個自己的回調(diào)般婆,那么每個線程的回調(diào),統(tǒng)一匯聚到下載總回調(diào)朵逝,只有這個回調(diào)負(fù)責(zé)和UI接口通信蔚袍。
一張圖可能更能說明:

回調(diào)結(jié)構(gòu)圖

從下向上看,UI回調(diào)和總回調(diào)1對1關(guān)系配名,總回調(diào)里有UI回調(diào)引用页响,總回調(diào)和每個Task的回調(diào),1對多關(guān)系段誊,每個Listener中有總回調(diào)引用闰蚕。
現(xiàn)在從上向下看,Listener下載了1MB连舍,告訴總回調(diào):“你可以給UI回調(diào)了”没陡,UI回調(diào)就老老實(shí)實(shí)告訴UI我下載了1MB了。簡單的說索赏,總回調(diào)就是一個代理類盼玄。

二、AddDownLoadTask

我們還差什么潜腻?入口類完成了埃儿,真正的下載類完成了,下載之前的巴拉巴拉已經(jīng)完成了融涣,那就只差下載任務(wù)了對不對童番?下面就真的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);            
            //當(dāng)前分支是否下載完成
            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是內(nèi)存緩存威鹿,之前就提過了剃斧,我們用
//URL下載Taskprivate Map<String, Map<Integer, Future>> mUrlTaskMap = new ConcurrentHashMap<>();
保存緩存信息,String是url忽你,Map<Integer, Future>是當(dāng)前url下的任務(wù)幼东,為啥又用個Map?因?yàn)榭赡苁嵌嗑€程啊根蟹!Integer脓杉,下載任務(wù)的唯一ID,這里是數(shù)據(jù)庫主鍵简逮,F(xiàn)uture不了解的同學(xué)請自行百度丽已,這就是下載任務(wù)。
如果有下載記錄买决,就找未完成的生成DownLoadTask, executeNetWork就是加入線程池沛婴。如果沒有下載記錄,就是新文件督赤,createDownLoadTask創(chuàng)建下載任務(wù)嘁灯。

createDownLoadTask

127-141 如果下載任務(wù)大于多線程下載的分割值,切成多段進(jìn)行下載躲舌。else 單線程下載丑婿。
好了 大概的流程到這里就結(jié)束了,還差什么没卸?Task任務(wù)回調(diào)羹奉,主線程回調(diào),這些代碼沒有貼出來约计,大家自己去發(fā)現(xiàn)吧诀拭。這里用了代理模式,還有很多的多線程數(shù)據(jù)安全方面的代碼煤蚌。下載Error重置下載機(jī)制耕挨,判斷下載是否真正結(jié)束機(jī)制。對緩存的操作尉桩,map套map的增刪改查筒占。

總結(jié)

到這所有的多線程下載和斷點(diǎn)續(xù)傳就結(jié)束了,其實(shí)寫作過程是痛苦的蜘犁,但是到結(jié)束還是很欣慰的翰苫,相信您從開始看到這篇結(jié)束,整個項(xiàng)目的流程您是了解的这橙,怎么定制奏窑,怎么修改bug應(yīng)該也沒有問題了,畢竟思路有了析恋,就差不停的實(shí)踐了良哲,對嗎盛卡?
我希望這篇文章再思路上可以幫助到您助隧,那也是我的初衷啊!
下篇文章我會整理封裝的支持上拉并村,下拉巍实,可以添加Head的RecycleView。
最后哩牍,感謝私信過我棚潦,鼓勵過我,打賞過我的朋友膝昆,謝謝你們的支持丸边。
GitHub地址
我希望大家可以積極fork,一起修改荚孵,如發(fā)現(xiàn)問題妹窖,歡迎反饋。
微信:hly1501

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末收叶,一起剝皮案震驚了整個濱河市骄呼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌判没,老刑警劉巖蜓萄,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異澄峰,居然都是意外死亡嫉沽,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進(jìn)店門俏竞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來耻蛇,“玉大人,你說我怎么就攤上這事胞此〕伎В” “怎么了?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵漱牵,是天一觀的道長夺蛇。 經(jīng)常有香客問我,道長酣胀,這世上最難降的妖魔是什么刁赦? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮闻镶,結(jié)果婚禮上甚脉,老公的妹妹穿的比我還像新娘。我一直安慰自己铆农,他們只是感情好牺氨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般猴凹。 火紅的嫁衣襯著肌膚如雪夷狰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天郊霎,我揣著相機(jī)與錄音沼头,去河邊找鬼。 笑死书劝,一個胖子當(dāng)著我的面吹牛进倍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播购对,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼背捌,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了洞斯?” 一聲冷哼從身側(cè)響起毡庆,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎烙如,沒想到半個月后么抗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡亚铁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年蝇刀,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片徘溢。...
    茶點(diǎn)故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡吞琐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出然爆,到底是詐尸還是另有隱情站粟,我是刑警寧澤,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布曾雕,位于F島的核電站奴烙,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏剖张。R本人自食惡果不足惜切诀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望搔弄。 院中可真熱鬧幅虑,春花似錦、人聲如沸顾犹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至哄芜,卻和暖如春貌亭,著一層夾襖步出監(jiān)牢的瞬間柬唯,已是汗流浹背认臊。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锄奢,地道東北人失晴。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像拘央,于是被迫代替她去往敵國和親涂屁。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評論 2 354

推薦閱讀更多精彩內(nèi)容