unity-UnityWebRequest斷點續傳


title: unity-UnityWebRequest斷點續傳
categories: Unity3d
tags: [unity, UnityWebRequest, 下載, 斷點續傳]
date: 2020-03-14 22:31:28
comments: false

適用於下載較大的文件, 弱網環境, 及 網絡抖動 的情況.


前篇

http 下載時, 一旦網絡遇到斷掉 (網絡切換) 時, 就會下載失敗. 但是 http 中可以設置 請求頭Range 值, 開決定下載文件流的起點, 斷點續傳就是通過這個實現.

原理:

下載文件 a.txt 時, 先將其下載爲一個臨時文件 a.txt 保存爲 a.txt.temp (隨便起名), 遇到網絡抖動斷掉時, 再次下載前, 先讀取 a.txt.temp 的文件大小, 然後在 http 請求頭 中設置 Range 值的起點值爲 a.txt.temp 的文件大小. 往復循環直至最後下載完成, 再講 a.txt.temp 重命名爲 a.txt.

附: 如果下載的文件時自己的文件, 加上一個 md5 值去校驗一下下載的文件是否完整.

下面直接丟代碼, lua + csharp


源碼

csharp 部分

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using LuaInterface;
using UnityEngine;
using UnityEngine.Networking;

public class ResumeDownHandler : DownloadHandlerScript {
    private FileStream fs;

    // 要下載的文件總長度
    private int mContentLength = 0;
    public int ContentLength {
        get { return mContentLength; }
    }

    private int mDownedLength = 0;
    public int DownedLength {
        get { return mDownedLength; }
    }

    private string mFileName;
    public string FileName {
        get { return mFileName; }
    }

    public string FileNameTemp {
        get { return mFileName + ".temp"; }
    }

    private string mSavePath = "";
    public string FilePath {
        get { return mSavePath; }
    }

    LuaFunction luaProgressFn = null;
    LuaFunction luaCompleteFn = null;

    public void RegProgress(LuaFunction fn) {
        luaProgressFn = fn;
    }

    public void RegComplete(LuaFunction fn) {
        luaCompleteFn = fn;
    }

    // 初始化下載句柄,定義每次下載的數據上限爲 512 kb
    public ResumeDownHandler(string filePath) : base(new byte[1024 * 512]) {
        mSavePath = filePath.Replace('\\', '/');
        mFileName = Path.GetFileName(mSavePath);

        string dirPath = System.IO.Path.GetDirectoryName(mSavePath);
        if (!Directory.Exists(dirPath)) {
            Directory.CreateDirectory(dirPath);
        }

        this.fs = new FileStream(mSavePath + ".temp", FileMode.Append, FileAccess.Write);
        mDownedLength = (int) fs.Length;
    }

    // 當從網絡接收數據時的回調,每幀調用一次, 返回true表示當下載正在進行,返回false表示下載中止
    protected override bool ReceiveData(byte[] data, int dataLength) {
        if (data == null || data.Length == 0) {
            return false;
        }

        fs.Write(data, 0, dataLength);
        mDownedLength += dataLength;

        if (luaProgressFn != null) {
            luaProgressFn.Call(mDownedLength, mContentLength);
        }
        return true;
    }

    public void OnComplete(int code) {
        if (luaCompleteFn != null) {
            luaCompleteFn.Call(code, mDownedLength, mContentLength);
        }
    }

    // 下載完成
    protected override void CompleteContent() {
        string TempFilePath = fs.Name; //臨時文件路徑
        OnDispose();

        if (File.Exists(TempFilePath)) {
            Utils.MoveFile(TempFilePath, mSavePath);
        }
    }

    public void OnDispose() {
        if (fs != null) {
            fs.Close();
            fs.Dispose();
        }
    }

    protected override void ReceiveContentLength(int contentLength) {
        mContentLength = contentLength + mDownedLength;
    }
}

lua 部分

--======================================================================
-- descrip: 斷點續傳 下載
--======================================================================

local CResumeDownManager = class()
CResumeDownManager.__name = "CResumeDownManager"

local table = table
local tostring = tostring
local pairs = pairs

function CResumeDownManager.Init(self)
    self._requestTbl = {}
end

function CResumeDownManager.StartDownCnt(self, url, savePath, completeFn, progressFn, cnt)
    local wrapFn
    if completeFn and cnt then
        wrapFn = function(isSucc, ...)
            if not isSucc and cnt > 1 then
                cnt = cnt - 1
                local isOk = self:StartDown(url, savePath, wrapFn, progressFn)
                if not isOk then -- 防止連續調用
                    cnt = 0
                end
            else
                completeFn(isSucc, ...)
            end
        end
    end
    self:StartDown(url, savePath, wrapFn, progressFn)
end

function CResumeDownManager.StartDown(self, url, savePath, completeFn, progressFn)
    if self._requestTbl[url] then
        return false
    end
    
    if table.count(self._requestTbl) == 0 then
        UpdateBeat:Add(self.Update, self)
    end

    local rdHdl = ResumeDownHandler.New(savePath)
    if completeFn then
        rdHdl:RegComplete(function(code, downLen, totalLen)
            -- 跳幾幀在回調回去
            gTimeMgr:SetTimeOut(0.2, function ()
                local isSucc = code == 206 and totalLen > 0 and downLen == totalLen
                completeFn(isSucc, downLen, totalLen, url, savePath)
            end)    
        end)
    end

    if progressFn then
        rdHdl:RegProgress(progressFn)
    end
    
    local request = UnityEngine.Networking.UnityWebRequest.Get(url)
    self._requestTbl[url] = request
    request.chunkedTransfer = true
    request.disposeDownloadHandlerOnDispose = true
    request:SetRequestHeader("Range", string.formatExt("bytes={0}-", rdHdl.DownedLength))
    request.downloadHandler = rdHdl
    request.useHttpContinue = true
    request:SendWebRequest()
    return true
end

function CResumeDownManager.StopDown(self, url)
    local request = self._requestTbl[url]
    if not request then
        return
    end
    self._requestTbl[url] = nil

    request.downloadHandler:OnDispose()
    request:Abort()
    request:Dispose()

    if table.count(self._requestTbl) == 0 then
        UpdateBeat:Remove(self.Update, self)
    end
end

function CResumeDownManager.StopAll(self)
    local keyTbl = table.keys(self._requestTbl)
    for _,url in ipairs(keyTbl) do
        self:StopDown(url)    
    end
end

function CResumeDownManager.Update(self)
    local rmTbl
    for url,request in pairs(self._requestTbl) do
        if request.isDone then
            request.downloadHandler:OnComplete(Utils.LongToInt(request.responseCode))

            if not rmTbl then
                rmTbl = {}
            end
            table.insert(rmTbl, url)
        end
    end

    if rmTbl then
        for _,url in ipairs(rmTbl) do
            self:StopDown(url)
        end
        rmTbl = nil
    end
end

return CResumeDownManager

測試代碼

local function testUpgrade()
    local dlPath = gPlatformMgr:CallLuaFunc("GetApkPath")
    local url = "http://192.168.1.200:8080/download/hello_39b001d018b558d96ff15eccf3777ef1.apk";
    local function progressFn(downLen, totalLen)
    end
    local function completeFn(isSucc, downLen, totalLen, url, savePath)
        gLog("--- completeFn, isSucc:{0}, downLen:{1}, totalLen:{2}", isSucc, downLen, totalLen)
        if isSucc then
        else
        end
    end
    gResumeDownMgr:StartDownCnt(url, dlPath, completeFn, progressFn, 5) -- 最多嘗試 5 次斷點續傳
end
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章