Android 断点续传实现原理
轉(zhuǎn)載請(qǐng)標(biāo)明出處:http://blog.csdn.net/zhaoyanjun6/article/details/120956134
本文出自【趙彥軍的博客】
文章目錄
- 下載原理
- 斷點(diǎn)續(xù)傳原理
- 1、java.io.RandomAccessFile
- 2、請(qǐng)求響應(yīng)碼及Header
- 3、處理請(qǐng)求資源發(fā)生改變的問(wèn)題
- 4、文件可復(fù)用判斷
下載原理
在介紹斷點(diǎn)續(xù)傳之前,我們先說(shuō)說(shuō)下載的原理。代碼示例用 OkHttp 作為示例。
下載核心思路是把 responseBody 寫(xiě)入文件,核心代碼如下:
但是這種做法有個(gè)明顯的問(wèn)題,假如手機(jī)在下載文件的時(shí)候下載了80%,某些原因斷網(wǎng)了,如果不支持?jǐn)帱c(diǎn)續(xù)傳,那就只有被迫重頭開(kāi)始下載。但是如果有斷點(diǎn)續(xù)傳的加持,就只需要下載最后 20% 的資源,避免重新下載。
斷點(diǎn)續(xù)傳原理
1、java.io.RandomAccessFile
斷點(diǎn)續(xù)傳/下載需要使用到 java.io.RandomAccessFile 類(lèi),RandomAccessFile 的實(shí)例支持讀取和寫(xiě)入隨機(jī)訪問(wèn)文件,它也可以 seek(long pos) 設(shè)置從此文件的開(kāi)頭開(kāi)始測(cè)量的文件指針偏移量,在該位置進(jìn)行下一次讀取或?qū)懭氩僮鳌:?jiǎn)單點(diǎn)說(shuō)就是可以通過(guò) seek(long pos)方法跳過(guò)pos字節(jié)開(kāi)始寫(xiě)入字節(jié)。
如何創(chuàng)建一個(gè) RandomAccessFile ?
//rw:支持可讀可寫(xiě) val file = RandomAccessFile(file, "rw")假如我有一段文本要寫(xiě)入文件,但是需要從文件的第100個(gè)字節(jié)開(kāi)始寫(xiě),那么需要調(diào)用 seek(long pos ) 方法跳過(guò)前 100 個(gè)字節(jié), 具體實(shí)現(xiàn)如下:
val file = RandomAccessFile(file, "rw") file.seek(100) file.writeBytes(.....)所以,我們可以把 responseBody 寫(xiě)入文件的實(shí)現(xiàn)改成如下實(shí)現(xiàn):
2、請(qǐng)求響應(yīng)碼及Header
正常情況下,我們下載一個(gè)文件,響應(yīng)碼是 200
responseBody 返回的是整個(gè)文件的內(nèi)容。如何才能返回部分內(nèi)容?
在實(shí)現(xiàn)分塊請(qǐng)求之前,首先要判斷服務(wù)器是否支持分塊返回,標(biāo)志是 Access-Ranges ,值為 bytes 就代表服務(wù)器支持分塊返回,也就是支持?jǐn)帱c(diǎn)續(xù)傳。
所以,我們可以封裝一個(gè)方法,來(lái)判斷服務(wù)是否支持?jǐn)帱c(diǎn)續(xù)傳
在確定了服務(wù)器支持分塊傳輸后,我們就可以在請(qǐng)求資源的時(shí)候,添加 header 請(qǐng)求 Range 字段,來(lái)指定請(qǐng)求實(shí)體的范圍。它的范圍取值是在 0-Content-Length 之間,使用 - 分割。
例如已經(jīng)下載了 1000 bytes 的資源內(nèi)容,想接著繼續(xù)下載之后的資源內(nèi)容,只要在 HTTP 請(qǐng)求頭部,增加 Range:bytes=1000- 就可以了。
Range 還有幾種不同的方式來(lái)限定范圍,可以根據(jù)需要靈活定制:
- 500-1000:指定開(kāi)始和結(jié)束的范圍。
- 500- :指定開(kāi)始區(qū)間,一直傳遞到結(jié)束。這個(gè)就比較適用于斷點(diǎn)續(xù)傳、或者在線播放等等。
- -500:無(wú)開(kāi)始區(qū)間,只意思是需要最后 500 bytes 的內(nèi)容實(shí)體。
- 100-300,1000-3000:指定多個(gè)范圍,這種方式使用的場(chǎng)景很少,了解一下就好了。
具體舉例如下:
通過(guò)抓包我們發(fā)現(xiàn),添加了 Range 后,請(qǐng)求響應(yīng)碼變成了 206
并且添加 Range 后,響應(yīng)的 responseBody 就不再是整個(gè)文件的內(nèi)容了,而是一個(gè)片段,我們只需要把這個(gè)片段的數(shù)據(jù)直接寫(xiě)入文件就可以。
注意事項(xiàng),如何計(jì)算 Range 請(qǐng)求頭的開(kāi)始值,一般取用緩存文件的長(zhǎng)度值,如下
val start = targetFile.length()3、處理請(qǐng)求資源發(fā)生改變的問(wèn)題
在現(xiàn)實(shí)的場(chǎng)景中,服務(wù)器中的文件是會(huì)有發(fā)生變化的情況的,那么我們發(fā)起續(xù)傳的請(qǐng)求肯定是失敗的
那么為了處理這種服務(wù)器文件資源發(fā)生改變的問(wèn)題,在 RFC2616 中定義了 Last-Modified和 Etag 來(lái)判斷續(xù)傳文件資源是否發(fā)生改變。
- Last-Modified:記錄 Http 頁(yè)面最后修改時(shí)間的 Http 頭部參數(shù),Last-Modified 是由服務(wù)端發(fā)送給客戶(hù)端的
- Etag:作為文件的唯一標(biāo)志,這個(gè)標(biāo)志可以是文件的 hash 值或者是一個(gè)版本
我們封裝一個(gè)方法用來(lái)獲取這兩個(gè)值
if-Range:用于判斷實(shí)體是否發(fā)生改變,如果實(shí)體未改變,服務(wù)器發(fā)送客戶(hù)端丟失的部分,否則發(fā)送整個(gè)實(shí)體。一般格式:
If-Range: Etag | HTTP-DateIf-Range 可以使用 Etag 或者 Last-Modified 返回的值。當(dāng)沒(méi)有 ETage 卻有 Last-modified 時(shí),可以把 Last-modified 作為 If-Range 字段的值。實(shí)現(xiàn)舉例如下:
關(guān)于 Etag 、Last-Modified 注意事項(xiàng):
- 當(dāng)?shù)谝淮握?qǐng)求資源的時(shí)候,獲取 Etag/Last-Modified 值,并且保存到本地
- 當(dāng)下一次需要斷點(diǎn)續(xù)傳的時(shí)候,把 Etag/Last-Modified 值取出,添加到請(qǐng)求頭 If-Range 字段
4、文件可復(fù)用判斷
在文件下載完成后,如果又發(fā)起了一次下載請(qǐng)求,那么此時(shí)不應(yīng)該重復(fù)下載的。需要做可復(fù)用判斷,思路如下
- 1、獲取目前已下載文件的大小
- 2、獲取文件url 獲取服務(wù)器文件大小
- 3、如果已下載大小等于服務(wù)器文件大小,則復(fù)用已下載的文件
舉例如下:
//獲取已下載文件長(zhǎng)度 val downloadLength = targetFile.length() //獲取服務(wù)器文件長(zhǎng)度 val contentLength = responseBody.contentLength() //兩者長(zhǎng)度對(duì)比 if(downloadLength == contentLength){//復(fù)用已下載文件,不再重復(fù)下載 }總結(jié)
以上是生活随笔為你收集整理的Android 断点续传实现原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Java字符串、文件MD5工具类
- 下一篇: Android Flow遇见Retrof