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

猜你喜欢

转载自blog.csdn.net/yangxuan0261/article/details/104870419