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