Java 斷點下載(下載續傳)服務端及客戶端(Android)代碼

来源:https://www.cnblogs.com/stars-one/archive/2022/08/21/16592706.html
-Advertisement-
Play Games

原文: Java 斷點下載(下載續傳)服務端及客戶端(Android)代碼 - Stars-One的雜貨小窩 最近在研究斷點下載(下載續傳)的功能,此功能需要服務端和客戶端進行對接編寫,本篇也是記錄一下關於貼上關於實現服務端(Spring Boot)與客戶端(Android)是如何實現下載續傳功能 ...


原文: Java 斷點下載(下載續傳)服務端及客戶端(Android)代碼 - Stars-One的雜貨小窩

最近在研究斷點下載(下載續傳)的功能,此功能需要服務端和客戶端進行對接編寫,本篇也是記錄一下關於貼上關於實現服務端(Spring Boot)與客戶端(Android)是如何實現下載續傳功能

斷點下載功能(下載續傳)解釋:

客戶端由於突然性網路中斷等原因,導致的下載失敗,這個時候重新下載,可以繼續從上次的地方進行下載,而不是重新下載

原理

首先,我們先說明瞭斷點續傳的功能,實際上的原理比較簡單

客戶端和服務端規定好一個規則,客戶端傳遞一個參數,告知服務端需要數據從何處開始傳輸,服務端接收到參數進行處理,之後文件讀寫流從指定位置開始傳輸給客戶端

實際上,上述的參數,在http協議中已經有規範,參數名為Range

而對於服務端來說,只要處理好Range請求頭參數,即可實現下載續傳的功能

我們來看下Range請求頭數據格式如下:

格式如下:

Range:bytes=300-800
//客戶端需要文件300-800位元組範圍的數據(即500B數據)

Range:bytes=300-
//客戶端需要文件300位元組之後的數據

我們根據上面的格式,服務端對Range欄位進行處理(String字元串數據處理),在流中返回指定的數據大小即可

那麼,如何讓流返回指定的數據大小或從指定位置開始傳輸數據呢?

這裡,Java提供了RandomAccessFile類,通過seekTo()方法,可以讓我們將流設置從指定位置開始讀取或寫入數據

這裡讀取和寫入數據,我是採用的Java7之後新增的NIO的Channel進行流的寫入(當然,用傳統的文件IO流(BIO)也可以)

這裡,我所說的客戶端是指的Android客戶端,由於App開發也是基於Java,所以也是可以使用RandomAccessFile這個類

對於客戶端來說,有以下邏輯:

先讀取本地已下載文件的大小,然後請求下載數據將文件大小的數據作為請求頭的數值傳到服務端,之後也是利用RandomAccessFile移動到文件的指定位置開始寫入數據即可

擴展-大文件快速下載思路

利用上面的思路,我們還可以可以得到一個大文件快速下載的思路:

如,一份文件,大小為2000B(這個大小可以通過網路請求,從返回數據的請求頭content-length獲取獲取)

客戶端拿回到文件的總大小,根據調優演算法,將平分成合適的N份,通過線程池,來下載這個N個單文件

在下載完畢之後,將N個文件按照順序合併成單個文件即可

代碼

上面說明瞭具體的思路,那麼下麵就是貼出服務端和客戶端的代碼示例

服務端

服務端是採用的spring boot進行編寫

/**
 * 斷點下載文件
 *
 * @return
 */
@GetMapping("download")
public void download( HttpServletRequest request, HttpServletResponse response) throws IOException {
    //todo 這裡文件按照你的需求調整
    File file = new File("D:\\temp\\測試文件.zip");
    if (!file.exists()) {
        response.setStatus(HttpStatus.NOT_FOUND.value());
        return;
    }
    long fromPos = 0;
    long downloadSize = file.length();

    if (request.getHeader("Range") != null) {
        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
        String[] ary = request.getHeader("Range").replaceAll("bytes=", "").split("-");
        fromPos = Long.parseLong(ary[0]);
        downloadSize = (ary.length < 2 ? downloadSize : Long.parseLong(ary[1])) - fromPos;
    }
    //註意下麵設置的相關請求頭
    response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
    //相當於設置請求頭content-length
    response.setContentLengthLong(downloadSize);

    //使用URLEncoder處理中文名(否則會出現亂碼)
    response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(file.getName(), "UTF-8"));
    response.setHeader("Accept-Ranges", "bytes");
    response.setHeader("Content-Range", String.format("bytes %s-%s/%s", fromPos, (fromPos + downloadSize), downloadSize));

    RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
    randomAccessFile.seek(fromPos);

    FileChannel inChannel = randomAccessFile.getChannel();
    WritableByteChannel outChannel = Channels.newChannel(response.getOutputStream());

    try {
        while (downloadSize > 0) {
            long count = inChannel.transferTo(fromPos, downloadSize, outChannel);
            if (count > 0) {
                fromPos += count;
                downloadSize -= count;
            }
        }
        inChannel.close();
        outChannel.close();
        randomAccessFile.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

客戶端

Android客戶端,是基於Okhttp的網路框架寫的,需要先引用依賴

implementation 'com.squareup.okhttp3:okhttp:3.9.0'

下麵給出的是封裝好的方法(含進度,下載失敗和成功回調):

package com.tyky.update.utils;

import com.blankj.utilcode.util.ThreadUtils;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;

import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class FileDownloadUtil {

    public static void download(String url, File file, OnDownloadListener listener) {

        //http://10.232.107.44:9060/swan-business/file/download
        // 利用通道完成文件的複製(非直接緩衝區)
        ThreadUtils.getIoPool().submit(new Runnable() {
            @Override
            public void run() {
                try {

                    //續傳開始的進度
                    long startSize = 0;
                    if (file.exists()) {
                        startSize = file.length();
                    }
                    OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
                    Request request = new Request.Builder().url(url)
                            .addHeader("Range", "bytes=" + startSize)
                            .get().build();
                    Call call = okHttpClient.newCall(request);
                    Response resp = call.execute();

                    double length = Long.parseLong(resp.header("Content-Length")) * 1.0;
                    InputStream fis = resp.body().byteStream();
                    ReadableByteChannel fisChannel = Channels.newChannel(fis);

                    RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
                    //從上次未完成的位置開始下載
                    randomAccessFile.seek(startSize);
                    FileChannel foschannel = randomAccessFile.getChannel();

                    // 通道沒有辦法傳輸數據,必須依賴緩衝區
                    // 分配指定大小的緩衝區
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

                    // 將通道中的數據存入緩衝區中
                    while (fisChannel.read(byteBuffer) != -1) {  // fisChannel 中的數據讀到 byteBuffer 緩衝區中
                        byteBuffer.flip();  // 切換成讀數據模式
                        // 將緩衝區中的數據寫入通道
                        foschannel.write(byteBuffer);

                        final double progress = (foschannel.size() / length);
                        BigDecimal two = new BigDecimal(progress);
                        double result = two.setScale(2,BigDecimal.ROUND_HALF_UP).doubleValue();
                        //計算進度,回調
                        if (listener != null) {
                            listener.onProgress(result);
                        }
                        byteBuffer.clear();  // 清空緩衝區
                    }
                    foschannel.close();
                    fisChannel.close();
                    randomAccessFile.close();

                    if (listener != null) {
                        listener.onSuccess(file);
                    }
                } catch (IOException e) {
                    if (listener != null) {
                        listener.onError(e);
                    }

                }
            }
        });


    }

    public interface OnDownloadListener {
        void onProgress(double progress);

        void onError(Exception e);

        void onSuccess(File outputFile);
    }
}

使用:

FileDownloadUtil.download(downloadUrl, file, new FileDownloadUtil.OnDownloadListener() {
    @Override
    public void onProgress(double progress) {
        KLog.d("下載進度: " + progress);
    }

    @Override
    public void onError(Exception e) {
        KLog.e("下載錯誤: " + e.getMessage());
    }

    @Override
    public void onSuccess(File outputFile) {
        KLog.d("下載成功");
    }
});

提問之前,請先看提問須知 點擊右側圖標發起提問 聯繫我 或者加入QQ群一起學習 Stars-One安卓學習交流群 TornadoFx學習交流群:1071184701
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在『伺服器部署 Vue 和 Django 項目的全記錄』一文中,介紹了在伺服器中使用 Nginx 部署前後端項目的過程。然而,當 Web 應用流量增多時,需要考慮負載均衡、流量分發、容災等情況,原生的部署方式通常難以滿足需求。此時,引入 Docker 部署多節點,能夠在單台高性能伺服器或伺服器集群中... ...
  • 類成員函數指針(member function pointer),是 C++ 語言的一類指針數據類型,用於存儲一個指定類具有給定的形參列表與返回值類型的成員函數的訪問信息。一般我們是不會使用的,都是直接將帶有返回值的函數作為參數或者另存後使用;像函數指針我們只會在定義包含多個函數的結構體類型時使用, ...
  • 目錄 一.簡介 二.效果演示 三.源碼下載 四.猜你喜歡 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 基礎 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 轉場 零基礎 O ...
  • 從發展階段來看,國內的互聯網公司大多都處於原始積累階段,大家都是 你有什麼功能,我也要有,本質上就是抄來抄去;這就導致然後大家都拼命擴軍、拼命提速,進入一種低維度的競爭狀態。 那麼這種局面應該如何打破呢? 如果發明出來更高級的“武器”,就能打破這一局面,那麼就算再快也沒用(我覺得發明更高級的武器就意 ...
  • 1. 獲取bean 在上圖的測試類中我們是通過id來獲取bean的。實際上獲取bean的方式有很多種,下麵我們就一一說明。 1.1 方式一:根據id獲取 由於 id 屬性指定了 bean 的唯一標識,所以根據 bean 標簽的 id 屬性可以精確獲取到一個組件對象。 如開頭中我們使用的就是這種方式。 ...
  • 某天,產品經理給了這麼一個需求技術小哥,能不能幫用戶添加一個搜索欄,查詢包含某個關鍵字的所有類目。技術小哥稍微想了一下,目前跟類目相關的表有兩個,一個是content_category類目表,一個是content_system內容系統表。而用戶要查找的關鍵字是存在content_system表裡面, ...
  • ==>>MyBatis中文網 1、第一個 mybastis程式 1.1 導入jar包 <mybatis.version>3.4.1</mybatis.version> <mysql.version>5.1.47</mysql.version> <!-- mybatis begin --> <depe ...
  • 1. 入門案例--hello spring 創建Maven Module 在pom.xml中引入依賴 <dependencies> <!-- 基於Maven依賴傳遞性,導入spring-context依賴即可導入當前所需所有jar包 --> <dependency> <groupId>org.spr ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...