[Unity]AssetBundle资源更新以及多线程下载

时间:2024-05-23 17:31:17
此文章适合不太了解资源加载的萌新,有了入门基础之后再去github上搜大牛写的专业的资源加载方案才能得心应手,不然的话会看的很吃力或者说一脸懵逼。Unity里面关于资源加载我们都知道是下载更新AssetBundle,关于AssetBundle我之前的文章已经详细介绍过,没看过的朋友可以在看一下。下面介绍的资源加载的Demo有以下几点: 
1.WWW下载图片资源 
2.HTTP下载apk文件,并且支持断点续传,并且显示加载进度条 
3.HTTP多线程下载文件
部分核心代码和讲解WWW下载思路:
WWW是Unity给我们封装的一个基于HTTP的简单类库,如果我们做很简单的下载,或者网络请求可以用这个类库,个人觉得这个封装的并不是很好,所以一般商业项目开发都不会使用这个,宁可自己去封装一个HTTP请求和下载的类库,可控性更好。仅仅是个人观点,不喜勿喷。
代码:
[C#] 纯文本查看 复制代码
?
 
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
using UnityEngine;
using System.Collections;
using System;
using System.IO;
 
public class WWWLoad
{
    private WWW www = null;
    static System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch();
    /// <summary>
    /// 下载文件
    /// </summary>
    public IEnumerator DownFile(string url, string savePath, Action<WWW> process)
    {
        FileInfo file = new FileInfo(savePath);
        stopWatch.Start();
        UnityEngine.Debug.Log("Start:" + Time.realtimeSinceStartup);
        www = new WWW(url);
        while (!www.isDone)
        {
            yield return 0;
            if (process != null)
                process(www);
        }
        yield return www;
        if (www.isDone)
        {
            byte[] bytes = www.bytes;
            CreatFile(savePath, bytes);
        }
    }
 
    /// <summary>
    /// 创建文件
    /// </summary>
    /// <param name="bytes"></param>
    public void CreatFile(string savePath, byte[] bytes)
    {
        FileStream fs = new FileStream(savePath, FileMode.Append);
        BinaryWriter bw = new BinaryWriter(fs);
        fs.Write(bytes, 0, bytes.Length);
        fs.Flush();     //流会缓冲,此行代码指示流不要缓冲数据,立即写入到文件。
        fs.Close();     //关闭流并释放所有资源,同时将缓冲区的没有写入的数据,写入然后再关闭。
        fs.Dispose();   //释放流
        www.Dispose();
 
        stopWatch.Stop();
        Debug.Log("下载完成,耗时:" + stopWatch.ElapsedMilliseconds);
        UnityEngine.Debug.Log("End:" + Time.realtimeSinceStartup);
    }
 
}


HTTP下载并加载AB资源思路:
主要用的核心类是HttpWebRequest,用这个类创建的对象可以申请下载的文件的大小以及下载的进度。移动上可读写的目录是PersidentDataPath,并且各个移动设备的路径不同,这点要注意,所以我们下载的AB资源就会下载到这个目录。
效果图:
[Unity]AssetBundle资源更新以及多线程下载
核心代码:
3yangyang9,如果您要查看本帖隐藏内容请回复

多线程下载文件思路:
多线程下载思路是计算一个文件包大小,然后创建几个线程,计算每一个线程下载的始末下载的位置,最后是合并成一个整体的文件包写入到本地。
效果图:
[Unity]AssetBundle资源更新以及多线程下载
核心代码:
[C#] 纯文本查看 复制代码
?
 
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
using UnityEngine;
using System.Collections;
using System.Threading;
using System.IO;
using System.Net;
using System;
 
/// <summary>
/// 通过http下载资源
/// </summary>
public class HttpDownLoad {
    //下载进度
    public float progress{get; private set;}
    //涉及子线程要注意,Unity关闭的时候子线程不会关闭,所以要有一个标识
    private bool isStop;
    //子线程负责下载,否则会阻塞主线程,Unity界面会卡主
    private Thread thread;
    //表示下载是否完成
    public bool isDone{get; private set;}
    const int ReadWriteTimeOut = 2 * 1000;//超时等待时间
    const int TimeOutWait = 5 * 1000;//超时等待时间
 
 
    /// <summary>
    /// 下载方法(断点续传)
    /// </summary>
    /// <param name="url">URL下载地址</param>
    /// <param name="savePath">Save path保存路径</param>
    /// <param name="callBack">Call back回调函数</param>
    public void DownLoad(string url, string savePath,string fileName, Action callBack, System.Threading.ThreadPriority threadPriority = System.Threading.ThreadPriority.Normal)
    {
        isStop = false;
        System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch();
        //开启子线程下载,使用匿名方法
        thread = new Thread(delegate() {
            stopWatch.Start();
            //判断保存路径是否存在
            if (!Directory.Exists(savePath))
            {
                Directory.CreateDirectory(savePath);
            }
            //这是要下载的文件名,比如从服务器下载a.zip到D盘,保存的文件名是test
            string filePath = savePath + "/"+ fileName;
 
            //使用流操作文件
            FileStream fs = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write);
            //获取文件现在的长度
            long fileLength = fs.Length;
            //获取下载文件的总长度
            UnityEngine.Debug.Log(url+" "+fileName);
            long totalLength = GetLength(url);
            Debug.LogFormat("<color=red>文件:{0} 已下载{1}M,剩余{2}M</color>",fileName,fileLength/1024/1024,(totalLength- fileLength)/ 1024/1024);        
 
            //如果没下载完
            if(fileLength < totalLength)
            {
 
                //断点续传核心,设置本地文件流的起始位置
                fs.Seek(fileLength, SeekOrigin.Begin);
 
                HttpWebRequest request = HttpWebRequest.Create(url) as HttpWebRequest;
 
                request.ReadWriteTimeout = ReadWriteTimeOut;
                request.Timeout = TimeOutWait;
 
                //断点续传核心,设置远程访问文件流的起始位置
                request.AddRange((int)fileLength);
 
                Stream  stream = request.GetResponse().GetResponseStream();
                byte[] buffer = new byte[1024];
                //使用流读取内容到buffer中
                //注意方法返回值代表读取的实际长度,并不是buffer有多大,stream就会读进去多少
                int length = stream.Read(buffer, 0, buffer.Length);
                //Debug.LogFormat("<color=red>length:{0}</color>" + length);
                while (length > 0)
                {
                    //如果Unity客户端关闭,停止下载
                    if(isStop) break;
                    //将内容再写入本地文件中
                    fs.Write(buffer, 0, length);
                    //计算进度
                    fileLength += length;
                    progress = (float)fileLength / (float)totalLength;
                    //UnityEngine.Debug.Log(progress);
                    //类似尾递归
                    length = stream.Read(buffer, 0, buffer.Length);
 
                }
                stream.Close();
                stream.Dispose();
 
            }
            else
            {
                progress = 1;
            }
            stopWatch.Stop();
            Debug.Log("耗时: " + stopWatch.ElapsedMilliseconds);
            fs.Close();
            fs.Dispose();
            //如果下载完毕,执行回调
            if(progress == 1)
            {
                isDone = true;
                if (callBack != null) callBack();
                thread.Abort();
            }
            UnityEngine.Debug.Log ("download finished");   
        });
        //开启子线程
        thread.IsBackground = true;
        thread.Priority = threadPriority;
        thread.Start();
    }
 
 
    /// <summary>
    /// 获取下载文件的大小
    /// </summary>
    /// <returns>The length.</returns>
    /// <param name="url">URL.</param>
    long GetLength(string url)
    {
        UnityEngine.Debug.Log(url);
 
        HttpWebRequest requet = HttpWebRequest.Create(url) as HttpWebRequest;
        requet.Method = "HEAD";
        HttpWebResponse response = requet.GetResponse() as HttpWebResponse;
        return response.ContentLength;
    }
 
    public void Close()
    {
        isStop = true;
    }
 
}


线程下载速度跟线程的关系呈钟罩式关系,也就是说适量的线程数量会提高下载速度,但并不是说线程数越多就越好,因为线程的切换和资源的整合也是需要时间的。下面就列举下载单个文件,创建的线程数和对应的下载时间:
  • 单线程 
    [Unity]AssetBundle资源更新以及多线程下载
  • 5个线程 
    [Unity]AssetBundle资源更新以及多线程下载
  • 15个线程 
    [Unity]AssetBundle资源更新以及多线程下载
这里我是1M的带宽,下载的是一个300KB左右的资源,一般不会做多线程下载单一资源,多线程下载一般用于下载多个资源,除非单一资源真的很大才有多线程下载,然后做合包操作。