Android實現(xiàn)多線程斷點續(xù)傳

文件在下載的過程中,手動暫停或異常時,下載被中斷,此時如果需要恢復(fù)下載,又不想重新下載的話,那么就需要實現(xiàn)斷點續(xù)傳了,斷點續(xù)傳的意思就是,恢復(fù)下載時,文件從被中斷的位置繼續(xù)下載,而無需重新將文件重新下載,最大的好處便是節(jié)省時間以及網(wǎng)絡(luò)產(chǎn)生的流量了。要實現(xiàn)文件下載的斷點續(xù)傳,就必須首先需要明白Http斷點續(xù)傳的原理。

Http請求頭Range是斷點續(xù)傳的核心。

什么是Range

當(dāng)用戶在聽一首歌的時候,如果聽到一半(網(wǎng)絡(luò)下載了一半),網(wǎng)絡(luò)斷掉了,用戶需要繼續(xù)聽的時候,文件服務(wù)器不支持?jǐn)帱c的話,則用戶需要重新下載這個文件。而Range支持的話,客戶端應(yīng)該記錄了之前已經(jīng)讀取的文件范圍,網(wǎng)絡(luò)恢復(fù)之后,則向服務(wù)器發(fā)送讀取剩余Range的請求,服務(wù)端只需要發(fā)送客戶端請求的那部分內(nèi)容,而不用整個文件發(fā)送回客戶端,以此節(jié)省網(wǎng)絡(luò)帶寬。

HTTP1.1規(guī)范的Range的約定

服務(wù)端通過請求頭Range:bytes=start-end來判斷是否做Range請求,如果這個值存在并且有效,將返回206的響應(yīng)碼給客戶端,告知這個請求支持?jǐn)帱c續(xù)傳。Range指定的是一個閉合區(qū)間范圍(也可以是多個區(qū)間范圍,不需要連續(xù)),Range請求頭的格式規(guī)范如下:

Range: bytes = start-end,start2-end2,start3-end3,...,startN-endN

start-end 是一個閉合區(qū)間,即包含start到end的部分,所以下一個請求應(yīng)該以end+1開頭

例如:

Range: bytes=0-100 (0到100字節(jié)的數(shù)據(jù))
Range: bytes=40- (40字節(jié)以后的數(shù)據(jù))
Range: bytes=-500(最后一個500字節(jié)的數(shù)據(jù))
Range: bytes=0-0,-1 (第一個和最后一個字節(jié))
Range: bytes=500-600,800-999 (同時指定兩個范圍)

響應(yīng)頭

Content-Range:bytes 0-100/3103

服務(wù)器響應(yīng)了前(0-100)個字節(jié)的數(shù)據(jù),該資源一共有(3103)個字節(jié)大小。

Content-Length:101

表示這次請求,服務(wù)器響應(yīng)了101個字節(jié)數(shù)據(jù),我們通常也通過Content-Length去獲取整個資源文件的大小,作為分段下載的基礎(chǔ)

以HttpUrlConnection為例


URL url = new URL(fileUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(10 * 1000);
connection.setRequestProperty("Accept-Ranges", "bytes");
connection.setRequestProperty("Connection", "Keep-Alive");

//加上這個頭部,則可以防止出現(xiàn)getContentLength()為-1的問題
connection.setRequestProperty("Accept-Encoding", "identity");
//必須加這個頭部,否則無法返回正常支持?jǐn)帱c續(xù)傳的響應(yīng)碼206,這里取值范圍是從第一個字節(jié)到結(jié)束,所以是請求的是整個資源文件
connection.setRequestProperty("Range", "bytes=0-");
connection.connect();

//假如此請求支持?jǐn)帱c續(xù)傳的方式將返回206響應(yīng)碼,否則資源請求成功的時候返回正常的200
int code = connection.getResponseCode() ;
//獲取本次服務(wù)器響應(yīng)的字節(jié)長度
long fileLength = connection.getContentLength();

實現(xiàn)多線程斷點續(xù)傳的基本思路

RandomAccessFile

多線程如何操作同一個文件,分段寫入呢?了解一下RandomAccessFile

RandomAccessFile類的主要功能是完成隨機(jī)讀取功能,可以讀取指定位置的內(nèi)容。
之前的File類只是針對文件本身進(jìn)行操作的,而如果要想對文件內(nèi)容進(jìn)行操作,則可以使用RandomAccessFile類,此類屬于隨機(jī)讀取類,可以隨機(jī)讀取一個文件中指定位置的數(shù)據(jù)

構(gòu)造方法

public RandomAccessFile(File file, String mode)throws FileNotFoundException 
public RandomAccessFile(String name, String mode) throws FileNotFoundException

mode 文件的打開模式
r:讀模式
w:只寫
rw:讀寫,如果使用此模式,如果此文件不存在,則會自動創(chuàng)建。

指定讀寫的位置

randomAccessFile.seek(startIndex);

HTTP請求頭Range

首先,訪問服務(wù)器資源,獲取資源文件的長度,即ContentLength,然后將ContentLength進(jìn)行劃分,假設(shè)ContentLength=1000,開辟4個線程去分段下載,每段下載250個字節(jié),那么每個線程的Range請求頭部應(yīng)該為

Thread1 Range:bytes=0-249
Thread2 Range:bytes=250-499
Thread3 Range:bytes=500-749
Thread4 Range:bytes=750-999

區(qū)間范圍由自己定義即可,不一定非要按等分

當(dāng)每個線程開始分段下載,訪問RandomAccessFile的開始位置就應(yīng)該是首字節(jié)的位置,如Thread2應(yīng)該如下設(shè)置

//Thread2
randomAccessFile.seek(250);

手動停止或出現(xiàn)中斷時,應(yīng)該保存上次寫入的最后一個字節(jié)位置,以作為下一次請求的開始。以上面作為例子,假設(shè)線程Thread2中斷時,寫入的最后一個字節(jié)的位置為350,那么恢復(fù)下載的時候,Thread2請求頭就應(yīng)該為

Range:bytes=351-499

此時Thread2訪問RandomAccessFile的開始位置應(yīng)該是351,即

//Thread2
randomAccessFile.seek(351);

最佳實踐

Android斷點續(xù)傳下載器JarvisDownloader

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容