vivo 應用商店中的斷點續傳技術剖析

1、業務背景

目前,vivo 平臺有不少的業務都涉及到文件的下載:譬如說應用商店、遊戲中心的C端用戶下載更新應用或遊戲;開放平臺B端用戶經過接口傳包能力更新應用或遊戲,須要從用戶服務器上下載apk、圖片等文件,來完成用戶的一次版本更新。java

2、面臨的挑戰

針對上述C端用戶,平臺須要提供良好的下載環境,而且客戶端須要兼容手機上用戶的異常操做。編程

針對上述B端用戶,平臺亟需解決的問題就是從用戶服務器上,拉取各類資源文件。服務器

下載自己也是一個很複雜的問題,會涉及到網絡問題、URL重定向、超大文件、遠程服務器文件變動、本地文件被刪除等各類問題。這就須要咱們保證平臺具有快速下載文件的能力,同時兼具備有對異常場景的快速預警、容錯處理的機制。網絡

3、業務實現方案

基於前面提到的挑戰,咱們設計實現方案的時候,引用了行業經常使用的解決方法:斷點下載。多線程

針對B端用戶場景,咱們的處理方案入下圖:併發

1、極速下載:經過分析文件大小,智能選擇是否採用直接下載、單線程斷點下載、多線程斷點下載的方案;在使用多線程下載方案時,對"多線程"的使用,有兩種方式:app

  • 分組模式:單個文件採用固定最大N個線程來進行下載,分組的好處是能保證服務節點線程數量可控,劣勢就是遇到大文件的時候,下載耗時相對會比較長;
  • 分片模式:採用單個線程,固定下載N個字節大小空間,分片的好處是遇到大文件的時候,下載耗時仍然會相對短,劣勢是會致使服務器節點線程數量突增,對服務節點穩定性有干擾;

在兩者之間,咱們選擇了分組模式。dom

2、容錯處理:在咱們處理下載過程當中,會遇到下載過程當中網絡不穩定、本地文件刪除,遠程文件變動等各類場景,這就須要咱們可以兼容處理這些場景,失敗後的任務,會有定時任務自動從新調起執行,也有後臺管理系統界面,進行人工調起;curl

3、完整性校驗:文件下載完成以後,須要對文件的最終一致性作校驗,來確保文件的正確性;ide

4、異常預警:對於單次任務在嘗試屢次下載操做後仍然失敗的狀況,及時發起預警警告。

對於C端用戶,業務方案相對更簡單,由於文件服務器有vivo平臺提供,網絡環境相對可控,這裏就再也不贅述。接下來,咱們將對文件下載裏面的各類技術細節,進行詳盡的剖析。

4、斷點下載原理剖析

在進行原理分析前,先給你們普及一下,什麼叫斷點下載?相信你們都有過使用迅雷下載網絡文件的經歷吧,有沒有注意到迅雷的下載任務欄裏面,有一個「暫停」和「開始下載」按鈕,會隨着任務的當前狀態顯示不一樣的按鈕。當你在下載一個100M的文件,下載到50M的時候,你點擊了「暫停」,而後點擊了「開始下載」,你會發現文件的下載居然是從已經下載好的50M之後接着下載的。沒錯,這就是斷點下載的真實應用。

4.1 HTTP 斷點下載之祕密:Range

在講解這個知識點前,你們有必要了解一下http的發展歷史,HTTP(HyperText Transfer Protocol),超文本傳輸協議,是目前萬維網(World Wide Web)的基礎協議,已經經歷四次的版本迭代:HTTP/0.9,HTTP/1.0,HTTP/1.1,HTTP/2.0。在HTTP/1.1(RFC2616)協議中,定義了HTTP1.1標準所包含的全部頭字段的相關語法和含義,其中就包括我們要講到的Accept-Ranges,服務端支持範圍請求(range requests)。有了這個重要的屬性,才使得咱們的斷點下載成爲可能。

基於HTTP不一樣版本之間的適配性,因此當咱們在決定是否須要使用斷點下載能力的時候,須要提早識別文件地址是否支持斷點下載,怎麼識別呢?方法不少,若是採用curl命令,命令爲:curl -I url

CURL驗證是否支持範圍請求:

若是服務端的響應信息裏面包含了上圖中Accept-Ranges: bytes,這個屬性,那麼說該URL是支持範圍請求的。若是URL返回消息體裏面,Accept-Ranges: none 或者壓根就沒有 Accept-Ranges這個屬性,那麼這個URL就是不支持範圍請求,也就是不支持斷點下載。

前面咱們有看到,當使用curl命令獲取URL的響應時,服務端返回了一大段文本信息,咱們要實現文件的斷點下載,就要從這些文本信息裏面獲取我們斷點下載須要的重要參數,有了這些參數後才能實現咱們想要達到的效果。

4.2 HTTP 斷點下載之Range語法說明

HTTP/1.1 中定義了一個 Range 的請求頭,來指定請求實體的範圍。它的範圍取值是在 0 - Content-Length 之間,使用 - 分割。

4.2.1 單區間段範圍請求

curl https://swsdl.vivo.com.cn/appstore/test-file-range-download.txt -i -H "Range: bytes=0-100"
HTTP/1.1 206 Partial Content
Date: Sun, 20 Dec 2020 03:06:43 GMT
Content-Type: text/plain
Content-Length: 101
Connection: keep-alive
Server: AliyunOSS
x-oss-request-id: 5FDEBFC33243A938379F9410
Accept-Ranges: bytes
ETag: "1FFD36BD1B06EB6C287AF8D788458808"
Last-Modified: Sun, 20 Dec 2020 03:04:33 GMT
x-oss-object-type: Normal
x-oss-hash-crc64ecma: 5148872045942545519
x-oss-storage-class: Standard
Content-MD5: H/02vRsG62woevjXiEWICA==
x-oss-server-time: 2
Content-Range: bytes 0-100/740
X-Via: 1.1 PShnzssxek171:14 (Cdn Cache Server V2.0), 1.1 x71:12 (Cdn Cache Server V2.0), 1.1 PS-FOC-01z6n168:27 (Cdn Cache Server V2.0)
X-Ws-Request-Id: 5fdebfc3_PS-FOC-01z6n168_36519-1719
Access-Control-Allow-Origin: *

4.2.2 多區間段範圍請求

curl https://swsdl.vivo.com.cn/appstore/test-file-range-download.txt -i -H "Range: bytes=0-100,200-300"
HTTP/1.1 206 Partial Content
Date: Sun, 20 Dec 2020 03:10:27 GMT
Content-Type: multipart/byteranges; boundary="Cdn Cache Server V2.0:37E1D9B3B2B94DF2F1D84393694C7E8A"
Content-Length: 506
Connection: keep-alive
Server: AliyunOSS
x-oss-request-id: 5FDEC030BDB66C33302A497E
Accept-Ranges: bytes
ETag: "1FFD36BD1B06EB6C287AF8D788458808"
Last-Modified: Sun, 20 Dec 2020 03:04:33 GMT
x-oss-object-type: Normal
x-oss-hash-crc64ecma: 5148872045942545519
x-oss-storage-class: Standard
Content-MD5: H/02vRsG62woevjXiEWICA==
x-oss-server-time: 2
Age: 1
X-Via: 1.1 xian23:7 (Cdn Cache Server V2.0), 1.1 PS-NTG-01KKN43:8 (Cdn Cache Server V2.0), 1.1 PS-FOC-01z6n168:27 (Cdn Cache Server V2.0)
X-Ws-Request-Id: 5fdec0a3_PS-FOC-01z6n168_36013-8986
Access-Control-Allow-Origin: *


--Cdn Cache Server V2.0:37E1D9B3B2B94DF2F1D84393694C7E8A
Content-Type: text/plain
Content-Range: bytes 0-100/740

--Cdn Cache Server V2.0:37E1D9B3B2B94DF2F1D84393694C7E8A
Content-Type: text/plain
Content-Range: bytes 200-300/740

看完上述請求的響應結果信息,咱們發現使用單範圍區間請求時:Content-Type: text/plain,使用多範圍區間請求時:Content-Type: multipart/byteranges; boundary="Cdn Cache Server V2.0:37E1D9B3B2B94DF2F1D84393694C7E8A",而且在尾部信息裏面,攜帶了單個區間片斷的Content-Type和Content-Range。另外,不知道你們有沒有發現一個很重要的信息,我們的HTTP響應的狀態並不是咱們預想中的200,而是HTTP/1.1 206 Partial Content,這個狀態碼很是重要,由於它標識着當次下載是否支持範圍請求。

4.3 異常場景之資源變動

有一種場景,不知道你們有沒有思考過,就是咱們在下載一個大文件的時候,在未下載完成的時候,遠程文件已經發生了變動,若是咱們繼續使用斷點下載,會出現什麼樣的問題?結果固然是文件與遠程文件不一致,會致使文件不可用。那麼咱們有什麼辦法可以在下載以前及時發現遠程文件已經變動,並及時進行調整下載方案呢?解決方法其實上面有給你們提到,遠程文件有沒有發生變化,有兩個標識:Etag和Last-Modified。兩者任意一個屬性都可反應出來,相比而言,Etag會更精準些,緣由以下:

  1. Last-Modified只能精確到秒級別,若是一秒內文件進行了屢次修改,時間不會發生更新,可是文件的內容卻已經發生了變動,此時Etag會及時更新識別到變動;
  2. 在不一樣的時間節點(超過1秒),若是文件從A狀態改爲B狀態,而後又重B狀態改回了A狀態,時間會發生更新,可是相較於A狀態文件內容,兩次變動後並沒又發生變化,此時Etag會變回最開始A狀態值,有點相似我們併發編程裏面常說的ABA問題。

若是咱們在進行範圍請求下載的時候,帶上了這兩個屬性中的一個或兩個,就能監控遠程文件發生了變化。若是發生了變化,那麼區間範圍請求的響應狀態就不是206而是200,說明它已經不支持該次請求的斷點下載了。接下來咱們驗證一下Etag的驗證信息,咱們的測試文件:ETag: "1FFD36BD1B06EB6C287AF8D788458808",而後咱們將最後一個數值8改爲9進行驗證,驗證以下:

文件未變動:

curl -I --header 'If-None-Match: "1FFD36BD1B06EB6C287AF8D788458808"' https://swsdl.vivo.com.cn/appstore/test-file-range-download.txt
HTTP/1.1 304 Not Modified
Date: Sun, 20 Dec 2020 03:53:03 GMT
Content-Type: text/plain
Connection: keep-alive
Last-Modified: Sun, 20 Dec 2020 03:04:33 GMT
ETag: "1FFD36BD1B06EB6C287AF8D788458808"
Age: 1
X-Via: 1.1 PS-FOC-01vM6221:15 (Cdn Cache Server V2.0)
X-Ws-Request-Id: 5fdeca9f_PS-FOC-01FMC220_2660-18267
Access-Control-Allow-Origin: *

文件已變動:

curl -I --header 'If-None-Match: "1FFD36BD1B06EB6C287AF8D788458809"' https://swsdl.vivo.com.cn/appstore/test-file-range-download.txt
HTTP/1.1 200 OK
Date: Sun, 20 Dec 2020 03:53:14 GMT
Content-Type: text/plain
Content-Length: 740
Connection: keep-alive
Server: AliyunOSS
x-oss-request-id: 5FDEC837E677A23037926897
Accept-Ranges: bytes
ETag: "1FFD36BD1B06EB6C287AF8D788458808"
Last-Modified: Sun, 20 Dec 2020 03:04:33 GMT
x-oss-object-type: Normal
x-oss-hash-crc64ecma: 5148872045942545519
x-oss-storage-class: Standard
Content-MD5: H/02vRsG62woevjXiEWICA==
x-oss-server-time: 17
X-Cache-Spec: Yes
Age: 1
X-Via: 1.1 xian23:7 (Cdn Cache Server V2.0), 1.1 PS-NTG-01KKN43:8 (Cdn Cache Server V2.0), 1.1 PS-FOC-01vM6221:15 (Cdn Cache Server V2.0)
X-Ws-Request-Id: 5fdecaaa_PS-FOC-01FMC220_4661-42392
Access-Control-Allow-Origin: *

結果顯示:當咱們使用跟遠程文件一致的Etag時,狀態碼返回:HTTP/1.1 304 Not Modified,而使用篡改後的Etag後,返回狀態200,而且也攜帶了正確的Etag返回。因此咱們在使用斷點下載過程當中,對於這種資源變動的場景也是須要兼顧考慮的,否則就會出現下載後文件沒法使用狀況。

4.4 完整性驗證

文件在下載完成後,咱們是否是就能直接使用呢?答案:NO。由於咱們沒法確認文件是否跟遠程文件徹底一致,因此在使用前,必定要作一次文件的完整性驗證。驗證方法很簡單,就是我們前面提到過的屬性:Etag,資源版本的標識符,一般是消息摘要。帶雙引號的32位字符串,筆者驗證過,該屬性移除雙引號後,就是文件的MD5值,你們知道,文件MD5是能夠用來驗證文件惟一性的標識。經過這個校驗,就能很好的識別解決本地文件被刪除、遠程資源文件變動的各種很是規的業務場景。

5、實踐部分

5.1 單線程斷點下載

假如咱們須要下載1000個字節大小的文件,那麼咱們在開始下載的時候,首先會獲取到文件的Content-Length,而後在第一次開始下載時,會使用參數:httpURLConnection.setRequestProperty("Range", "bytes=0-1000");

當下載到到150個字節大小的時候,由於網絡問題或者客戶端服務重啓等狀況,致使下載終止,那麼本地就存在一個大小爲150byte的不完整文件,當咱們服務重啓後從新下載該文件時,咱們不只須要從新獲取遠程文件的大小,還須要獲取本地已經下載的文件大小,此時使用參數:httpURLConnection.setRequestProperty("Range", "bytes=150-1000");

來保證咱們的下載是基於前一次的下載基礎之上的。圖示:

5.2 多線程斷點下載

多線程斷點下載的原理,與上面提到的單線程相似,惟一的區別在於:多個線程並行下載,單線程是串行下載。

5.3 代碼示例

5.3.1  獲取鏈接

在下載前,咱們須要獲取遠程文件的HttpURLConnection 鏈接,以下:

/**
 * 獲取鏈接
 */
private static HttpURLConnection getHttpUrlConnection(String netUrl) throws Exception {
    URL url = new URL(netUrl);
    HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
    // 設置超時間爲3秒
    httpURLConnection.setConnectTimeout(3 * 1000);
    // 防止屏蔽程序抓取而返回403錯誤
    httpURLConnection.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
    return httpURLConnection;
}

5.3.2 是否支持範圍請求

在進行斷點下載開始前,咱們須要判斷該文件,是否支持範圍請求,支持的範圍請求,咱們才能實現斷點下載,以下:

/**
 * 判斷鏈接是否支持斷點下載
 */
private static boolean isSupportRange(String netUrl) throws Exception {
    HttpURLConnection httpURLConnection = getHttpUrlConnection(netUrl);
    String acceptRanges = httpURLConnection.getHeaderField("Accept-Ranges");
    if (StringUtils.isEmpty(acceptRanges)) {
        return false;
    }
    if ("bytes".equalsIgnoreCase(acceptRanges)) {
        return true;
    }
    return false;
}

5.3.3 獲取遠程文件大小

當文件支持斷點下載,咱們須要獲取遠程文件的大小,來設置Range參數的範圍區間,固然,若是是單線程斷線下載,不獲取遠程文件大小,使用 Range: start- 也是能完成斷點下載的,以下:

/**
 * 獲取遠程文件大小
 */
private static int getFileContentLength(String netUrl) throws Exception {
    HttpURLConnection httpUrlConnection = getHttpUrlConnection(netUrl);
    int contentLength = httpUrlConnection.getContentLength();
    closeHttpUrlConnection(httpUrlConnection);
    return contentLength;
}

5.3.4 單線程斷點下載

無論是單線程斷點下載仍是多線程斷點下載,片斷文件下載完成後,都沒法繞開的一個問題,那就是文件合併。咱們使用範圍請求,拿到了文件中的某個區間片斷,最終仍是要將各個片斷合併成一個完整的文件,才能實現咱們最初的下載目的。

相較而言,單線程的合併會比較簡單,由於單線程斷點下載使用串行下載,在文件斷點寫入過程當中,都是基於已有片斷進行尾部追加,咱們使用commons-io-2.4.jar裏面的一個工具方法,來實現文件的尾部追加:

5.3.4.1 文件分段

單線程-範圍分段

/**
 * 單線程串行下載
 *
 * @param totalFileSize 文件總大小
 * @param netUrl        文件地址
 * @param N             串行下載分段次數
 */
private static void segmentDownload(int totalFileSize, String netUrl, int N) throws Exception {
    // 本地文件目錄
    String localFilePath = "F:\\test_single_thread.txt";
    // 文件咱們分N次來下載
    int eachFileSize = totalFileSize / N;
    for (int i = 1; i <= N; i++) {
        // 寫入本地文件
        File localFile = new File(localFilePath);
        // 獲取本地文件,若是爲空,則start=0,不爲空則爲該本地文件的大小做爲斷點下載開始位置
        long start = localFile.length();
        long end = 0;
        if (i == 1) {
            end = eachFileSize;
        } else if (i == N) {
            end = totalFileSize;
        } else {
            end = eachFileSize * i;
        }
        appendFile(netUrl, localFile, start, end);
        System.out.println(String.format("我是第%s次下載,下載片斷範圍start=%s,end=%s", i, start, end));
    }
    File localFile = new File(localFilePath);
    System.out.println("本地文件大小:" + localFile.length());
}

5.3.4.2 文件追加

單線程-文件尾部追加

/**
 * 文件尾部追加
 * @param netUrl 地址
 * @param localFile 本地文件
 * @param start 分段開始位置
 * @param end 分段結束位置
 */
private static void appendFile(String netUrl, File localFile, long start, long end) throws Exception {
    HttpURLConnection httpURLConnection = getHttpUrlConnection(netUrl);
    httpURLConnection.setRequestProperty("Range", "bytes=" + start + "-" + end);
    // 獲取遠程文件流信息
    InputStream inputStream = httpURLConnection.getInputStream();
    // 本地文件寫入流,支持文件追加
    FileOutputStream fos = FileUtils.openOutputStream(localFile, true);
    IOUtils.copy(inputStream, fos);
    closeHttpUrlConnection(httpURLConnection);
}

單線程下載結果

遠程文件支持斷點下載
遠程文件大小:740
我是第1次下載,下載片斷範圍start=0,end=246
我是第2次下載,下載片斷範圍start=247,end=492
我是第3次下載,下載片斷範圍start=493,end=740
本地文件和遠程文件一致,md5 = 1FFD36BD1B06EB6C287AF8D788458808, Etag = "1FFD36BD1B06EB6C287AF8D788458808"

5.3.5 多線程斷點下載

多線程的文件合併方式與單線程不同,由於多線程是並行下載,每一個子線程下載完成的時間是不肯定的。這個時候,咱們須要使用到java一個核心類:RandomAccessFile。這個類能夠支持隨機的文件讀寫,其中有一個seek函數,能夠將指針指向文件任意位置,而後進行讀寫。什麼意思呢,舉個栗子:假如咱們開了10個線程,首先第一個下載完成的是線程X,它下載的數據範圍是300-400,那麼這時咱們調用seek函數將指針動到300,而後調用它的write函數將byte寫出,這時候300以前都是NULL,300-400以後就是咱們插入的數據。這樣就能夠實現多線程下載和本地寫入了。話很少說,咱們仍是以代碼的方式來呈現:

5.3.5.1 資源分組

多線程-資源分組

/**
 * 多線程分組策略
 * @param netUrl 網絡地址
 * @param totalFileSize 文件總大小
 * @param N 線程池數量
 */
private static void groupDownload(String netUrl, int totalFileSize, int N) throws Exception {
    // 採用閉鎖特性來實現最後的文件校驗事件
    CountDownLatch countDownLatch = new CountDownLatch(N);
    // 本地文件目錄
    String localFilePath = "F:\\test_multiple_thread.txt";
    int groupSize = totalFileSize / N;
    int start = 0;
    int end = 0;
    for (int i = 1; i <= N; i++) {
        if (i <= 1) {
            start = groupSize * (i - 1);
            end = groupSize * i;
        } else if (i > 1 && i < N) {
            start = groupSize * (i - 1) + 1;
            end = groupSize * i;
        } else {
            start = groupSize * (i - 1) + 1;
            end = totalFileSize;
        }
        System.out.println(String.format("線程%s分配區間範圍start=%s, end=%s", i, start, end));
        downloadAndMerge(i, netUrl, localFilePath, start, end, countDownLatch);
    }
    // 校驗文件一致性
    countDownLatch.await();
    validateCompleteness(localFilePath, netUrl);
}

5.3.5.2 文件合併

多線程-文件合併

/**
 * 文件下載、合併
 *  @param threadNum     線程標識
 * @param netUrl        網絡文件地址
 * @param localFilePath 本地文件路徑
 * @param start         範圍請求開始位置
 * @param end           範圍請求結束位置
 * @param countDownLatch 閉鎖對象
 */
private static void downloadAndMerge(int threadNum, String netUrl, String localFilePath, int start, int end, CountDownLatch countDownLatch) {
    threadPoolExecutor.execute(() -> {
        try {
            HttpURLConnection httpURLConnection = getHttpUrlConnection(netUrl);
            httpURLConnection.setRequestProperty("Range", "bytes=" + start + "-" + end);
            // 獲取遠程文件流信息
            InputStream inputStream = httpURLConnection.getInputStream();
            RandomAccessFile randomAccessFile = new RandomAccessFile(localFilePath, "rw");
            // 文件寫入開始位置指針移動到已經下載位置
            randomAccessFile.seek(start);
            byte[] buffer = new byte[1024 * 10];
            int len = -1;
            while ((len = inputStream.read(buffer)) != -1) {
                randomAccessFile.write(buffer, 0, len);
            }
            closeHttpUrlConnection(httpURLConnection);
            System.out.println(String.format("下載完成時間%s, 線程:%s, 下載完成: start=%s, end = %s", System.currentTimeMillis(), threadNum, start, end));
        } catch (Exception e) {
            System.out.println(String.format("片斷下載異常:線程:%s, start=%s, end = %s", threadNum, start, end));
            e.printStackTrace();
        }
        countDownLatch.countDown();
    });
}

多線程下載運行結果

遠程文件支持斷點下載
遠程文件大小:740
線程1分配區間範圍start=0, end=74
線程2分配區間範圍start=75, end=148
線程3分配區間範圍start=149, end=222
線程4分配區間範圍start=223, end=296
線程5分配區間範圍start=297, end=370
線程6分配區間範圍start=371, end=444
線程7分配區間範圍start=445, end=518
線程8分配區間範圍start=519, end=592
線程9分配區間範圍start=593, end=666
線程10分配區間範圍start=667, end=740
下載完成時間1608443874752, 線程:7, 下載完成: start=445, end = 518
下載完成時間1608443874757, 線程:2, 下載完成: start=75, end = 148
下載完成時間1608443874758, 線程:3, 下載完成: start=149, end = 222
下載完成時間1608443874759, 線程:5, 下載完成: start=297, end = 370
下載完成時間1608443874760, 線程:10, 下載完成: start=667, end = 740
下載完成時間1608443874760, 線程:1, 下載完成: start=0, end = 74
下載完成時間1608443874779, 線程:8, 下載完成: start=519, end = 592
下載完成時間1608443874781, 線程:6, 下載完成: start=371, end = 444
下載完成時間1608443874784, 線程:9, 下載完成: start=593, end = 666
下載完成時間1608443874788, 線程:4, 下載完成: start=223, end = 296
本地文件和遠程文件一致,md5 = 1FFD36BD1B06EB6C287AF8D788458808, Etag = "1FFD36BD1B06EB6C287AF8D788458808"

從運行結果能夠出,子線程下載完成時間並無徹底按着咱們for循環指定的1-10線程標號順序完成,說明子線程之間是並行在寫入文件。其中還能夠看到,子線程10和子線程1是在同一時間完成了文件的下載和寫入,這也很好的驗證了咱們上面提到的RandomAccessFile類的效果。

5.3.6 完整性判斷

完整性校驗

/**
 * 校驗文件一致性,咱們判斷Etag和本地文件的md5是否一致
 * 注:Etag攜帶了雙引號
 * @param localFilePath
 * @param netUrl
 */
private static void validateCompleteness(String localFilePath, String netUrl) throws Exception{
    File file = new File(localFilePath);
    InputStream data = new FileInputStream(file);
    String md5 = DigestUtils.md5Hex(data);
    HttpURLConnection httpURLConnection = getHttpUrlConnection(netUrl);
    String etag = httpURLConnection.getHeaderField("Etag");
    if (etag.toUpperCase().contains(md5.toUpperCase())) {
        System.out.println(String.format("本地文件和遠程文件一致,md5 = %s, Etag = %s", md5.toUpperCase(), etag));
    } else {
        System.out.println(String.format("本地文件和遠程文件不一致,md5 = %s, Etag = %s", md5.toUpperCase(), etag));
    }
}

6、寫在最後

文件斷點下載的優點在於提高下載速度,可是也不是每種業務場景都適合,好比說業務網絡環境很好,下載的單個文件大小几十兆的狀況下,使用斷點下載也沒有太大的優點,反而增長了實現方案的複雜度。這就要求咱們開發人員在使用時酌情考慮,而不是盲目使用。

做者:vivo-Tang Aibo
相關文章
相關標籤/搜索