unity Assetbundle资源管理与更新比对下载

在做项目时为了减少包体的大小,我们可以用unity自带的AssetBundle进行资源打包管理,本篇博客采用的方案是在资源打包时给每个资源一个特定的MD5值,写入文本进行保存资源名称和对应的MD5值,并把资源和存储的文本放入服务器中;然后本地运行项目时首先判断本地是否有已下载好的资源和文本,然后把本地的文本与服务器的文本进行比对,如果资源的MD5值不对应那么就下载此资源到本地进行替换同时更新相应的资源名和对应的MD5值;如果本地没有此资源也要重新下载资源到本地并把资源名和对应的MD5值下载到本地进行保存。

每次项目启动时首先进行资源比对看是否需要更新下载。

1.把已带有bundle名的资源build出来,打包资源的脚本放在Editor文件夹下: 

using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Text;
using System;

public class BuildAssetBundle : EditorWindow
{
    /// <summary>
    /// 获取不同平台下的包裹存储路径
    /// </summary>
    public static string GetAssetBundlePath(BuildTarget target)
    {
        var path = PathConfig.buildAssetPath + "/" + PathConfig.GetBuildTargetPath(target) + "/";

        //当在硬盘目录结构里不存在该路径时,创建文件夹
        if (!Directory.Exists(path))
        {
            Directory.CreateDirectory(path);
        }
        return path;
    }

    [MenuItem("MrCAssetBundles/Build Windows")]
    public static void CustomBuildAssetBundle_Win()
    {
        BuildBundle(BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows);
    }

    [MenuItem("MrCAssetBundles/Build IOS")]
    public static void CustomBuildAssetBundle_IOS()
    {
        BuildBundle(BuildAssetBundleOptions.None, BuildTarget.iOS);
    }

    [MenuItem("MrCAssetBundles/Build MAC")]
    public static void CustomBuildAssetBundle_MAC()
    {
        BuildBundle(BuildAssetBundleOptions.None, BuildTarget.StandaloneOSX);
    }

    [MenuItem("MrCAssetBundles/Build Android")]
    public static void CustomBuildAssetBundle_Android()
    {
        BuildBundle(BuildAssetBundleOptions.None, BuildTarget.Android);
    }

    [MenuItem("MrCAssetBundles/Build WebGL")]
    public static void CustomBuildAssetBundle_WebGL()
    {
        BuildBundle(BuildAssetBundleOptions.None, BuildTarget.WebGL);
    }

    private static void BuildBundle(BuildAssetBundleOptions bundleOptions, BuildTarget buildTarget)
    {
        //设置资源读取版本号

        //包裹存储的路径...
        string outputPath = GetAssetBundlePath(EditorUserBuildSettings.activeBuildTarget);
        if (!Directory.Exists(outputPath)) Directory.CreateDirectory(outputPath);
        //打包过程..
        BuildPipeline.BuildAssetBundles(outputPath, bundleOptions, buildTarget);
        CreateVersion(outputPath);
        Debug.Log("打包完成!位置: " + outputPath);

    }


    #region MD5版本号

    public static void CreateVersion(string resPath)
    {
        // 获取Res文件夹下所有文件的相对路径和MD5值
        string[] files = Directory.GetFiles(resPath, "*", SearchOption.AllDirectories);

        StringBuilder versions = new StringBuilder();
        for (int i = 0, len = files.Length; i < len; i++)
        {
            string filePath = files[i];

            if (filePath.Contains("."))
            {
                string extension = filePath.Substring(files[i].LastIndexOf("."));
                if (extension == ".unity3d")
                {
                    string relativePath = filePath.Replace(resPath, "").Replace("\\", "/");
                    string md5 = PathConfig.MD5File(filePath);
                    versions.Append(relativePath).Append(",").Append(md5).Append("\r\n");
                }
            }
            else
            {
                string test = filePath.Substring(files[i].LastIndexOf("/") + 1);
                if (test == PathConfig.GetBuildTargetPath(EditorUserBuildSettings.activeBuildTarget))
                {
                    string relativePath = filePath.Replace(resPath, "").Replace("\\", "/");
                    string md5 = PathConfig.MD5File(filePath);
                    versions.Append(relativePath).Append(",").Append(md5).Append("\r\n");
                }
            }
        }

        // 生成配置文件
        FileStream stream = new FileStream(resPath + PathConfig.version_file, FileMode.Create);

        byte[] data = Encoding.UTF8.GetBytes(versions.ToString());
        stream.Write(data, 0, data.Length);
        stream.Flush();
        stream.Close();
    }
    #endregion


}

2.资源build完成之后,后续更新比对,下载资源进行资源的加载

资源的路径管理脚本PathConfig

​
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif

public class PathConfig
{
    //最大build加载数量
    public static readonly int MAXBUNDLECOUNT = 5;

    public static readonly string bundleSuffix = ".unity3d";
    //保存打包的资源名与对应的MD5值
    public static readonly string version_file = "version.txt";
    public static readonly string localUrl = Application.persistentDataPath;
    //服务器下载资源地址
    public static readonly string serverUrl = "";
    public static readonly string buildAssetPath = Application.streamingAssetsPath;

    //当前程序版本号(默认从1.0算起)
    public static string ProductVersion = "Asset_1.0";


    public static string GetFileHeader
    {
        get
        {
#if UNITY_EDITOR
            return "file:///";
#elif UNITY_IOS
        return "";
#elif UNITY_STANDALONE_OSX
        return "";
#elif UNITY_ANDROID
        return "jar:file://";
#elif UNITY_WEBGL
        return "";
#else
        return "file:///";
#endif
        }
    }

    public static string GetManifestFileName()
    {
        var version = ProductVersion;
#if UNITY_EDITOR
        return "Others/" + version;
#elif UNITY_IOS
        return "IOS/"+ version;
#elif UNITY_STANDALONE_OSX
        return "MacOS/"+ version;
#elif UNITY_ANDROID
        return "Android/"+ version;
#elif UNITY_WEBGL
        return "WebGL/"+ version;
#else
        return "Others/"+ version;
#endif
    }

#if UNITY_EDITOR
    public static string GetBuildTargetPath(BuildTarget buildTarget)
    {
        var version = ProductVersion;
        switch (buildTarget)
        {
            case BuildTarget.iOS:
                return "IOS/" + version;
            case BuildTarget.StandaloneOSX:
                return "MacOS/" + version;
            case BuildTarget.Android:
                return "Android/" + version;
            case BuildTarget.WebGL:
                return "WebGL/" + version;
            default:
                return "Others/" + version;
        }
    }
#endif

    //检查url,如果末尾有'/'不处理,无添加
    public static string CheckUrl(string url)
    {
        return url.Replace('\\', '/').TrimEnd('/') + '/';
    }

    //生成MD5值
    public static string MD5File(string file)
    {
        try
        {
            return MD5Checker.Check(file);
        }
        catch (System.Exception ex)
        {
            Debug.Log(ex.Message);
            return string.Empty;
        }
    }
}

​

生成MD5管理MD5类:

using System;
using System.IO;
using System.Security.Cryptography;

public class MD5Checker
{
    public delegate void AsyncCheckHeadler(AsyncCheckEventArgs e);
    public event AsyncCheckHeadler AsyncCheckProgress;

    //支持所有哈希算法
    private HashAlgorithm hashAlgorithm;
    //文件读取流
    private Stream inputStream;
    //缓存
    private byte[] asyncBuffer;

    public AsyncCheckState CompleteState { get; private set; }
    public float Progress { get; private set; }
    public string GetMD5 { get; private set; }

    /// <summary>
    /// 返回指定文件的MD5值
    /// </summary>
    /// <param name="path"></param>
    /// <returns></returns>
    public static string Check(string path)
    {
        try
        {
            var fs = new FileStream(path, FileMode.Open);
            MD5CryptoServiceProvider md5Provider = new MD5CryptoServiceProvider();
            byte[] buffer = md5Provider.ComputeHash(fs);
            string resule = BitConverter.ToString(buffer);
            resule = resule.Replace("-", "");
            fs.Close();
            return resule;
        }
        catch (ArgumentException aex)
        {
            throw new ArgumentException(string.Format("<{0}>, 不存在: {1}", path, aex.Message));
        }
        catch (Exception ex)
        {
            throw new Exception(string.Format("读取文件 {0} ,MD5失败: {1}", path, ex.Message));
        }
    }

    public static string Check_Stream(string path)
    {
        try
        {
            int bufferSize = 1024 * 256;//自定义缓冲区大小256K
            var buffer = new byte[bufferSize];
            Stream inputStream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read);
            HashAlgorithm hashAlgorithm = new MD5CryptoServiceProvider();
            int readLength = 0;//每次读取长度
            var output = new byte[bufferSize];
            while ((readLength = inputStream.Read(buffer, 0, buffer.Length)) > 0)
            {
                //计算MD5
                hashAlgorithm.TransformBlock(buffer, 0, readLength, output, 0);
            }
            //完成最后计算,必须调用(由于上一部循环已经完成所有运算,所以调用此方法时后面的两个参数都为0)
            hashAlgorithm.TransformFinalBlock(buffer, 0, 0);
            string md5 = BitConverter.ToString(hashAlgorithm.Hash);
            hashAlgorithm.Clear();
            inputStream.Close();
            md5 = md5.Replace("-", "");
            return md5;
        }
        catch (ArgumentException aex)
        {
            throw new ArgumentException(string.Format("<{0}>, 不存在: {1}", path, aex.Message));
        }
        catch (Exception ex)
        {
            throw new Exception(string.Format("读取文件 {0} ,MD5失败: {1}", path, ex.Message));
        }
    }

    public void AsyncCheck(string path)
    {
        CompleteState = AsyncCheckState.Checking;
        try
        {
            int bufferSize = 1024 * 256;//缓冲区大小,1MB 1048576

            asyncBuffer = new byte[bufferSize];

            //打开文件流
            inputStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.None, bufferSize, true);
            hashAlgorithm = new MD5CryptoServiceProvider();

            //异步读取数据到缓冲区
            inputStream.BeginRead(asyncBuffer, 0, asyncBuffer.Length, new AsyncCallback(AsyncComputeHashCallback), null);
        }
        catch (ArgumentException aex)
        {
            throw new ArgumentException(string.Format("<{0}>, 不存在: {1}", path, aex.Message));
        }
        catch (Exception ex)
        {
            throw new Exception(string.Format("读取文件{0} ,MD5失败: {1}", path, ex.Message));
        }
    }

    private void AsyncComputeHashCallback(IAsyncResult result)
    {
        int bytesRead = inputStream.EndRead(result);
        //检查是否到达流末尾
        if (inputStream.Position < inputStream.Length)
        {
            //输出进度
            Progress = (float)inputStream.Position / inputStream.Length;
            string pro = string.Format("{0:P0}", Progress);

            if (null != AsyncCheckProgress)
                AsyncCheckProgress(new AsyncCheckEventArgs(AsyncCheckState.Checking, pro));

            var output = new byte[asyncBuffer.Length];
            //分块计算哈希值
            hashAlgorithm.TransformBlock(asyncBuffer, 0, asyncBuffer.Length, output, 0);

            //异步读取下一分块
            inputStream.BeginRead(asyncBuffer, 0, asyncBuffer.Length, new AsyncCallback(AsyncComputeHashCallback), null);
            return;
        }
        else
        {
            //计算最后分块哈希值
            hashAlgorithm.TransformFinalBlock(asyncBuffer, 0, bytesRead);
        }

        Progress = 1;
        string md5 = BitConverter.ToString(hashAlgorithm.Hash).Replace("-", "");
        CompleteState = AsyncCheckState.Completed;
        GetMD5 = md5;
        if (null != AsyncCheckProgress)
            AsyncCheckProgress(new AsyncCheckEventArgs(AsyncCheckState.Completed, GetMD5));

        inputStream.Close();
    }
}

public enum AsyncCheckState
{
    Completed,
    Checking
}

public class AsyncCheckEventArgs : EventArgs
{
    public string Value { get; private set; }

    public AsyncCheckState State { get; private set; }

    public AsyncCheckEventArgs(AsyncCheckState state, string value)
    {
        this.Value = value; this.State = state;
    }
}

3.Assetbundle自我管理的类:

MrCAssetBundle类管理bundle自身

using UnityEngine;


namespace MrCAssetFramework
{
    public sealed class MrCAssetBundle
    {
        internal string m_AssetBundleName;

        public AssetBundle AssetBundle { get; set; }

        public float Progress { get; set; }

        public bool IsDone { get; set; }

        internal MrCAssetBundle(string assetBundleName)
        {
            this.m_AssetBundleName = assetBundleName;
            this.m_ReferencedCount = 1;
        }

        private int m_ReferencedCount;

        //保留调用次数
        public void RetainCall()
        {
            this.m_ReferencedCount++;
        }

        //卸载资源
        public void Release()
        {
            //this.m_ReferencedCount--;
            当引用计数为0时,卸载资源
            //if (this.m_ReferencedCount == 0)
            //{
            //    if(AssetBundle!=null)
            //        this.AssetBundle.Unload(true);
            //    MrCAssetCache.FreeBundle(this.m_AssetBundleName);
            //}
            if (AssetBundle != null)
                this.AssetBundle.Unload(true);
            MrCAssetCache.FreeBundle(this.m_AssetBundleName);
            Debug.Log("卸载资源: " + m_AssetBundleName);
        }

        //如果是新创建的,不首先消毁
        public int RetainCount()
        {
            var newbundle = MrCAssetManager.Instance.NewAssetBundle;
            if (newbundle != null && newbundle == this)
            {
                return 65535;
            }
            return m_ReferencedCount;
        }
    }
}


    MrCAssetCache类缓存bundle

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


namespace MrCAssetFramework
{
    internal sealed class MrCAssetCache                      //缓存管理
    {
        #region 包裹缓存机制
        //创建缓存字典
        private static Dictionary<string, MrCAssetBundle> assetBundleCache;
        //缓存字典属性
        internal static Dictionary<string, MrCAssetBundle> AssetBundleCache
        {
            get
            {
                if (assetBundleCache == null)
                {
                    assetBundleCache = new Dictionary<string, MrCAssetBundle>();
                }
                return assetBundleCache;
            }
        }

        //创建缓存WWW对象
        private static Dictionary<string, AssetBundleCreateRequest> wwwCache;
        //创建缓存WWW对象属性
        private static Dictionary<string, AssetBundleCreateRequest> WwwCache
        {
            get
            {
                if (wwwCache == null)
                {
                    wwwCache = new Dictionary<string, AssetBundleCreateRequest>();
                }
                return wwwCache;
            }
        }

        //创建依赖缓存对象
        private static Dictionary<string, string[]> dependCache;
        //创建依赖缓存属性
        private static Dictionary<string, string[]> DependCache
        {
            get
            {
                if (dependCache == null)
                {
                    dependCache = new Dictionary<string, string[]>();
                }
                return dependCache;
            }
        }


        /// <summary>
        /// Instantiate the Cache
        /// </summary>
        /// <param name="assetbundleName"></param>
        /// <returns></returns>
        internal static bool InCache(string assetbundleName)
        {
            return AssetBundleCache.ContainsKey(assetbundleName);
        }
        #endregion



        #region 卸载系列函数

        /// <summary>
        /// 卸载资源包和依赖包
        /// </summary>
        /// <param name="assetBundleName"></param>
        public static void UnloadAssetBundle(string assetBundleName)
        {
            UnloadAssetBundleInternal(assetBundleName);
            UnloadDependencies(assetBundleName);
        }

        internal static void UnloadDependencies(string assetBundleName)
        {
            string[] depends = null;

            //获取所有的依赖包名称
            if (!DependCache.TryGetValue(assetBundleName, out depends))
                return;

            //卸载依赖包
            foreach (var dependency in depends)
            {
                UnloadAssetBundleInternal(dependency);
            }
            //删除依赖缓存策略
            DependCache.Remove(assetBundleName);
        }

        internal static void UnloadAssetBundleInternal(string assetBundleName)
        {
            MrCAssetBundle bundle;
            AssetBundleCache.TryGetValue(assetBundleName, out bundle);

            if (bundle == null)
                return;
            bundle.Release();
        }
        #endregion

        #region GetFunction
        internal static AssetBundleCreateRequest GetWWWCache(string key)
        {
            if (WwwCache.ContainsKey(key))
            {
                return WwwCache[key];
            }
            return null;
        }
        internal static MrCAssetBundle SetWWWCache(string key, AssetBundleCreateRequest value)
        {
            if (!WwwCache.ContainsKey(key))
                WwwCache.Add(key, value);
            else
                WwwCache[key] = value;
            var bundleObject = new MrCAssetBundle(key);
            SetBundleCache(key, bundleObject);
            return bundleObject;
        }

        internal static MrCAssetBundle GetBundleCache(string key)
        {
            MrCAssetBundle ab;
            AssetBundleCache.TryGetValue(key, out ab);
            return ab;
        }

        internal static void SetBundleCache(string key, MrCAssetBundle value)
        {
            if (!AssetBundleCache.ContainsKey(key))
            {
                AssetBundleCache.Add(key, value);
            }
            else
            {
                AssetBundleCache[key] = value;
            }
        }

        internal static string[] GetDependCache(string key)
        {
            string[] depends;
            DependCache.TryGetValue(key, out depends);
            return depends;
        }
        internal static void SetDependCache(string key, string[] value)
        {
            if (!DependCache.ContainsKey(key))
            {
                DependCache.Add(key, value);
            }
        }
        #endregion

        internal static void FreeBundle(string key)
        {
            if (AssetBundleCache.ContainsKey(key))
                AssetBundleCache.Remove(key);
        }

        #region Update
        private static List<string> keysToRemove = new List<string>();

        internal static void Update()
        {
            foreach (var keyValue in WwwCache)
            {
                var download = keyValue.Value;
                string bundleName = keyValue.Key;

                var bundleObject = GetBundleCache(bundleName);
                if (bundleObject == null)
                {
                    bundleObject = new MrCAssetBundle(bundleName);
                    SetBundleCache(bundleName, bundleObject);
                }

                bundleObject.Progress = download.progress;

                //下载成功
                if (download.isDone)
                {
                    bundleObject.AssetBundle = download.assetBundle;
                    bundleObject.IsDone = true;
                    bundleObject.Progress = 1.1f;
                    keysToRemove.Add(bundleName);
                }
            }

            //删除下载成功的WWW对象
            foreach (var key in keysToRemove)
            {
                if (wwwCache.ContainsKey(key))
                {
                    var download = WwwCache[key];
                    WwwCache.Remove(key);
                }
            }
            keysToRemove.Clear();
        }
        #endregion
    }
}

MrCAssetManager类

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace MrCAssetFramework
{
    //AssetBundle管理
    public class MrCAssetManager : MonoBehaviour
    {
        #region Singleton
        private static MrCAssetManager _instance = null;

        public static MrCAssetManager Instance
        {
            get
            {
                if (_instance == null)
                {
                    var obj = new GameObject("LoadAssetManager");
                    _instance = obj.AddComponent<MrCAssetManager>();
                }
                return _instance;
            }
        }

        #endregion

        //最新生成的bundle
        public MrCAssetBundle NewAssetBundle { get; set; }

        public void Init()
        {
            StartCoroutine(LoadManifestBundle());
        }


        // 异步加载场景
        public IEnumerator LoadLevelAsync(string assetBundleName, string assetName)
        {
            var bundleObject = MrCAssetCache.GetBundleCache(assetBundleName);
            //缓存中已经存在请求bundle,中止..
            if (bundleObject == null)
            {
                //通过网络下载AssetBundle
                bundleObject = LoadAssetBundleAtInternal(assetBundleName);
                yield return LoadAssetBundleProgress(bundleObject);
                //yield return GetAssetAsyc(assetBundleName, assetName);
                //StartCoroutine(LoadDependencies(assetBundleName));
            }
            else
            {
                bundleObject.RetainCall();
            }
        }

        private void Update()
        {
            MrCAssetCache.Update();
        }

        public IEnumerator LoadManifestBundle()
        {
            var manifestName = PathConfig.GetManifestFileName();

            var bundleObject = MrCAssetCache.GetBundleCache(manifestName);
            //缓存中已经存在请求bundle,中止..
            if (bundleObject == null)
            {
                //通过网络下载AssetBundle
                bundleObject = LoadAssetBundleAtInternal(manifestName);
                yield return LoadAssetBundleProgress(bundleObject);
                //StartCoroutine(GetAssetAsyc<AssetBundleManifest>(manifestName, "AssetBundleManifest"));
            }
            else
            {
                bundleObject.RetainCall();
            }
        }

        //private IEnumerator GetAssetAsyc(string assetbundleName, string assetName)
        //{
        //    MrCAssetBundle lab = MrCAssetCache.GetBundleCache(assetbundleName);
        //    if (lab != null)
        //    {
        //        var ab = lab.AssetBundle.LoadAssetAsync(assetName);
        //        ab.allowSceneActivation = false;
        //        //当前进度
        //        int currentProgress = 0;
        //        //目标进度
        //        int targetProgress = 1000;
        //        while (true)
        //        {
        //            if (ab.isDone || currentProgress++ > targetProgress)
        //            {
        //                print("加载场景");
        //                break;
        //            }
        //            DownLoadProgress.Instance.Progress = ab.progress;
        //            yield return null;
        //        }
        //        ab.allowSceneActivation = true;
        //    }
        //    else
        //    {
        //        print("资源: " + assetbundleName + " 未加载或正在加载!");
        //        yield break;
        //    }
        //}

        #region 加载包裹系列函数

        ///检查是否已经从网络下载
        protected MrCAssetBundle LoadAssetBundleAtInternal(string assetBundleName)
        {
            var bundleObject = MrCAssetCache.GetBundleCache(assetBundleName);
            //如果WWW缓存策略中包含有对应的关键字,则返回true
            if (bundleObject == null)
            {
                MustBundleHandle();

                var url = PathConfig.localUrl + "/"
                    + PathConfig.GetManifestFileName() + "/"
                    + assetBundleName;
                //创建下载链接
                var request = AssetBundle.LoadFromFileAsync(url);
                //WWW www = new WWW(url); 
                //+ assetBundleName
                //按版本号,按需要通过网络下载AssetBundle,一般在正式游戏版本中,不使用上面的,因为它会每次打开游戏重新下载
                //WWW www = WWW.LoadFromCacheOrDownload(LOAssetManager.URI + assetBundleName, nowVersion);
                //加入缓存策略
                NewAssetBundle = MrCAssetCache.SetWWWCache(assetBundleName, request);
                return NewAssetBundle;
            }
            return bundleObject;
        }

        //超过最大bunld数处理
        private void MustBundleHandle()
        {
            var bundles = MrCAssetCache.AssetBundleCache;
            if (bundles != null && bundles.Count >= PathConfig.MAXBUNDLECOUNT)
            {
                int min = int.MaxValue;
                string findKey = string.Empty;
                foreach (var item in bundles.Values)
                {
                    if (item.RetainCount() < min)
                    {
                        min = item.RetainCount();
                        findKey = item.m_AssetBundleName;
                    }
                }
                var bundle = MrCAssetCache.GetBundleCache(findKey);
                if (bundle != null) bundle.Release();
            }
        }

        private IEnumerator LoadDependencies(string assetBundleName)
        {
            var manifestName = PathConfig.GetManifestFileName();
            var manifest = this.GetAsset<AssetBundleManifest>(manifestName, "AssetBundleManifest");
            if (manifest == null)
            {
                if (MrCAssetCache.InCache(manifestName))
                {
                    manifest = this.GetAsset<AssetBundleManifest>(manifestName, "AssetBundleManifest");
                }
                else
                    yield return null;
            }

            //获取依赖包裹
            string[] depends = manifest.GetAllDependencies(assetBundleName);

            if (depends.Length == 0)
            {
                yield return null;
            }

            //记录并且加载所有的依赖包裹
            MrCAssetCache.SetDependCache(assetBundleName, depends);

            for (int i = 0; i < depends.Length; i++)
            {
                yield return LoadAssetBundleAtInternal(depends[i]);
            }
        }

        // To Get The Asset...
        private T GetAsset<T>(string assetbundleName, string assetName) where T : UnityEngine.Object
        {
            MrCAssetBundle lab = MrCAssetCache.GetBundleCache(assetbundleName);
            if (lab != null)
            {
                return lab.AssetBundle.LoadAsset<T>(assetName);
            }
            else
            {
                print("资源: " + assetbundleName + " 未加载或正在加载!");
                return null;
            }
        }
        #endregion

        #region  Loading阶段
        private IEnumerator LoadAssetBundleProgress(MrCAssetBundle _bundleObject)
        {
            TestManager.Instance.IsBundleSuccess = true;
            if (_bundleObject == null)
            {
                TestManager.Instance.IsBundleSuccess = false;
                yield break;
            }
            //当前进度
            //int initNum = 0;
            //目标进度
            //int maxNum = 2000;
            while (!_bundleObject.IsDone)
            {
                //if (initNum++ > maxNum)
                //    break;
                //var progress = _bundleObject.Progress;
                //sliderImage.rectTransform.sizeDelta = new Vector2(700f * progress, 12);
                //progressText.text = string.Format("{0:f1}{1}", progress * 100, '%');
                yield return new WaitForEndOfFrame();
            }
            TestManager.Instance.IsBundleSuccess = _bundleObject.IsDone;
        }
        #endregion
    }
}

UpdateAssets类:负责比对更新下载

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;


public class UpdateAssets : MonoBehaviour
{
    //保存本地的assetbundle名和对应的MD5值
    private Dictionary<string, string> LocalResVersion;
    private Dictionary<string, string> ServerResVersion;

    //保存需要更新的AssetBundle名
    private List<string> NeedDownFiles;

    private bool NeedUpdateLocalVersionFile = false;

    private string _localUrl;
    private string _serverUrl;

    public IEnumerator OnStart()
    {
        yield return Init();
    }

    private IEnumerator Init()
    {
        LocalResVersion = new Dictionary<string, string>();
        ServerResVersion = new Dictionary<string, string>();
        NeedDownFiles = new List<string>();

        //加载本地version配置
        _localUrl = PathConfig.localUrl + "/" + PathConfig.GetManifestFileName() + "/";
        yield return DownLoad(PathConfig.GetFileHeader + _localUrl, PathConfig.version_file, LocalVersionCallBack);


        //加载服务端version配置
        var serverUrl = PathConfig.serverUrl;
        _serverUrl = serverUrl + PathConfig.GetManifestFileName() + "/";
        Debug.Log("下载文件服务器: " + _serverUrl);
        yield return DownLoad(_serverUrl, PathConfig.version_file, ServerVersionCallBack);
    }

    private IEnumerator LocalVersionCallBack(UnityWebRequest request, string param = "")
    {
        //保存本地的version
        var context = request.downloadHandler.text;
        ParseVersionFile(context, LocalResVersion);

        yield return ClearIncompleteFile();
    }

    private IEnumerator ServerVersionCallBack(UnityWebRequest request, string param = "")
    {
        //保存服务端version
        var context = request.downloadHandler.text;
        ParseVersionFile(context, ServerResVersion);

        //根据用户名过滤下载资源
        //FilterServerDownLoadFile();

        //计算出需要重新加载的资源
        CompareVersion();
        if (NeedUpdateLocalVersionFile)
        {
            //DownLoadProgress.Instance.ShowDownLoad(NeedDownFiles.Count);
            Debug.Log("需要更新的资源个数为:" + NeedDownFiles.Count);
        }
        //加载需要更新的资源
        yield return DownLoadRes();
    }

    //对比本地配置,清除缺失文件
    private IEnumerator ClearIncompleteFile()
    {
        if (LocalResVersion != null)
        {
            List<string> removeKey = new List<string>();
            foreach (var local in LocalResVersion)
            {
                string filePath = _localUrl + local.Key;
                if (!Directory.Exists(_localUrl))
                {
                    Directory.CreateDirectory(_localUrl);
                }
                if (!File.Exists(filePath))
                {
                    removeKey.Add(local.Key);
                }
                else
                {
                    //异步
                    yield return MD5FileAsync(filePath, delegate (string md5)
                    {
                        if (md5 != local.Value)
                        {
                            File.Delete(filePath);
                            removeKey.Add(local.Key);
                        }
                    });
       
                }
            }
            foreach (var key in removeKey)
            {
                if (LocalResVersion.ContainsKey(key))
                    LocalResVersion.Remove(key);
            }
        }
    }

    //过滤服务器下载文件,根据用户可访问项目
    //private void FilterServerDownLoadFile()
    //{
    //    var tabdatas = DataManager.Instance.projectTabDatas;
    //    if (tabdatas == null)
    //        return;
    //    var localSet = ProjectSetting.Instance.projectSetData;
    //    if (localSet != null && localSet.subProjectInfo != null)
    //    {
    //        //获取所选项目所有Id
    //        var allProjectIds = tabdatas
    //            .Where(t => t.project_list != null && t.project_list.Count > 0)
    //            .SelectMany(t => t.project_list)
    //            .Where(t => t != null)
    //            .Select(t => t.Id.ToString())
    //            .ToList();
    //        //获取所有需要加载场景的项目Id
    //        var localProjectIds = localSet.subProjectInfo.SelectMany(t => t.sceneDatas.Select(f => f.Key));
    //        //根据所选项目,推出所有需要下载的Id (Intersect交集)
    //        var needDownloadIds = allProjectIds.Intersect(localProjectIds);
    //        var needDownloadScenes = localSet.subProjectInfo
    //            .SelectMany(t => t.sceneDatas
    //                .Where(f => needDownloadIds.Contains(f.Key))
    //                .Select(s => (s.Value + PathConfig.bundleSuffix))
    //                .ToList());
    //        //从而得出需要过滤的Id(Except获取不在list2中所有list1中元素)
    //        var filterIds = ServerResVersion.Select(t => t.Key).Except(needDownloadScenes).ToList();
    //        for (int i = 0; i < filterIds.Count; i++)
    //        {
    //            if (ServerResVersion.ContainsKey(filterIds[i]))
    //            {
    //                ServerResVersion.Remove(filterIds[i]);
    //            }
    //        }
    //        //测试-----------------------
    //        //string message = string.Empty;
    //        //foreach (var item in allProjectIds)
    //        //{
    //        //    message += item + " ,";
    //        //}
    //        //print("用户所有Ids: " + message);

    //        //message = string.Empty;
    //        //foreach (var item in localProjectIds)
    //        //{
    //        //    message += item + " ,";
    //        //}
    //        //print("本地所有需要加载Ids: " + message);

    //        //message = string.Empty;
    //        //foreach (var item in needDownloadIds)
    //        //{
    //        //    message += item + " ,";
    //        //}
    //        //print("需要加载Ids: " + message);

    //        //message = string.Empty;
    //        //foreach (var item in needDownloadScenes)
    //        //{
    //        //    message += item + " ,";
    //        //}
    //        //print("需要加载Scenes: " + message);

    //        //message = string.Empty;
    //        //foreach (var item in filterIds)
    //        //{
    //        //    message += item + " ,";
    //        //}
    //        //print("过滤Ids: " + message);
    //    }
    //}

    //依次加载需要更新的资源

    private IEnumerator DownLoadRes()
    {
        if (NeedDownFiles.Count == 0)
        {
            UpdateLocalVersionFile();
            yield break;
        }

        string file = NeedDownFiles[0];

        NeedDownFiles.RemoveAt(0);
        yield return DownLoad(_serverUrl, file, DownLoadCallBack);
    }

    private IEnumerator DownLoadCallBack(UnityWebRequest request, string param = "")
    {
        //将下载的资源替换本地就的资源
        var download = request.downloadHandler;
        if (!request.isNetworkError && !request.isHttpError)
        {
            ReplaceLocalRes(param, download.data);
            if (ServerResVersion.ContainsKey(param))
            {
                if (LocalResVersion.ContainsKey(param))
                    LocalResVersion[param] = ServerResVersion[param];
                else
                    LocalResVersion.Add(param, ServerResVersion[param]);
            }
        }
        yield return DownLoadRes();
    }


    private void ReplaceLocalRes(string fileName, byte[] data)
    {
        string filePath = _localUrl + fileName;

        if (!Directory.Exists(_localUrl))
        {
            Directory.CreateDirectory(_localUrl);
        }
        FileStream stream = new FileStream(filePath, FileMode.Create);
        stream.Write(data, 0, data.Length);
        stream.Flush();
        stream.Close();
    }


    //更新本地的version配置

    private void UpdateLocalVersionFile()
    {
        if (NeedUpdateLocalVersionFile)
        {
            StringBuilder versions = new StringBuilder();
            foreach (var item in LocalResVersion)
            {
                versions.Append(item.Key).Append(",").Append(item.Value).Append("\r\n");
            }
            if (!Directory.Exists(_localUrl))
            {
                Directory.CreateDirectory(_localUrl);
            }
            FileStream stream = new FileStream(_localUrl + PathConfig.version_file, FileMode.Create);
            byte[] data = Encoding.UTF8.GetBytes(versions.ToString());
            stream.Write(data, 0, data.Length);
            stream.Flush();
            stream.Close();
        }

        //加载显示对象
        //StartCoroutine(Show());
    }


    private void CompareVersion()
    {
        foreach (var version in ServerResVersion)
        {
            string fileName = version.Key;                 //assetbundleName
            string serverMd5 = version.Value;              // asset MD5值

            //新增的资源
            if (!LocalResVersion.ContainsKey(fileName))
            {
                NeedDownFiles.Add(fileName);
            }
            else
            {
                //需要替换的资源
                string localMd5;

                LocalResVersion.TryGetValue(fileName, out localMd5);
                if (!serverMd5.Equals(localMd5))
                {
                    NeedDownFiles.Add(fileName);
                }
            }
        }

        if (NeedDownFiles.Count > 0)
        {
            for (int i = 0; i < NeedDownFiles.Count; i++)
            {
                Debug.Log("需要更新的资源:" + NeedDownFiles[i]);
            }
        }
        //本次有更新,同时更新本地的version.txt
        NeedUpdateLocalVersionFile = NeedDownFiles.Count > 0;
    }


    private void ParseVersionFile(string content, Dictionary<string, string> dict)
    {
        if (content == null || content.Length == 0)
        {
            return;
        }

        string[] items = content.Split('\n');
        foreach (string item in items)
        {
            string str = item.Replace("\r", "").Replace("\n", "").Replace(" ", "");
            string[] info = str.Split(',');
            if (info != null && info.Length == 2)
            {
                dict.Add(info[0], info[1]);
            }
        }
    }

    private IEnumerator DownLoad(string url, string fileName, HandleFinishDownload finishFun)
    {
        url = PathConfig.CheckUrl(url);
        var request = UnityWebRequest.Get(url + fileName);
        if (NeedUpdateLocalVersionFile)
        {
            yield return LoadRegularRequest(request);
        }
        else
        {
            yield return request.SendWebRequest();
        }

        if (finishFun != null && request.isDone)
        {
            yield return finishFun(request, fileName);
        }
        if (request.isNetworkError)
        {
            Debug.LogError("更新资源出错: " + url + " error: " + request.error);
        }
        request.Dispose();
    }

    public delegate IEnumerator HandleFinishDownload(UnityWebRequest request, string param = "");


    //异步生成MD5值
    private IEnumerator MD5FileAsync(string file, Action<string> action)
    {
        var asyncChecker = new MD5Checker();
        asyncChecker.AsyncCheck(file);
        var endframe = new WaitForEndOfFrame();
        while (asyncChecker.CompleteState == AsyncCheckState.Checking)
        {
            //SeerLogger.Log("load...{0:P0}" + asyncChecker.Progress);
            yield return endframe;
        }
        action(asyncChecker.GetMD5);
    }

    //整齐的下载资源
    public IEnumerator LoadRegularRequest(UnityEngine.Networking.UnityWebRequest request)
    {
        var ao = request.SendWebRequest();
        bool downError = false;
        //ao.allowSceneActivation = false;
        while (true)
        {
            if (downError) break;

            if (ao.webRequest.isNetworkError || ao.webRequest.isHttpError)
            {
                downError = true;
            }
            else if (ao.isDone)
                break;
        }
        yield return new WaitForEndOfFrame();
    }
    //ao.allowSceneActivation = true;

}


最后是我们自己的测试登陆:TestManager类:

using MrCAssetFramework;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class TestManager : MonoBehaviour {

    private static TestManager _instance;

    public UpdateAssets updateAssets;



    private void Awake()
    {
        if (_instance == null)
            _instance = this;
    }

    public static TestManager Instance
    {
        get { return _instance; }
    }

    public bool IsBundleSuccess { get; set; }

    private void Start()
    {
        StartCoroutine(InitUpdate(LoadScene));
    }

    //登陆时首先需要进行资源比对,看是否需要重新下载
    private IEnumerator InitUpdate(System.Action action)
    {
        if(updateAssets)
        {
            yield return updateAssets.OnStart();
        }
        if (action != null)
            action();

    }

    public void LoadScene()
    {
        //MainScene   测试场景的bundle名称,可替换成你所需加载的场景名

        StartCoroutine(LoadSceneCoroutine("MainScene", null));
    }

    /// <summary>
    /// 异步解压bundle,加载场景
    /// </summary>
    /// <param name="sceneName">要加载登陆的场景打包的名字</param>
    /// <param name="action"></param>
    /// <returns></returns>
    private IEnumerator LoadSceneCoroutine(string sceneName, System.Action action)
    {
        print("开始加载场景.............");
        // yield return MrCAssetManager.Instance.LoadManifestBundle();
        yield return MrCAssetManager.Instance.LoadLevelAsync(sceneName + ".unity3d", sceneName);
        if (IsBundleSuccess)
        {
            if(action!=null)
                action();
            LoadScene(sceneName);
        }
        else
        {
            Debug.LogError("场景加载出现了问题!");
        }


    }

    private void LoadScene(string sceneName)
    {
        StartCoroutine(LoadAsync(sceneName));
    }

    //异步对象  
    private AsyncOperation _asyncOperation;
    /// <summary>
    /// 携程进行异步加载场景
    /// </summary>
    /// <param name="sceneName">需要加载的场景名</param>
    /// <returns></returns>
    IEnumerator LoadAsync(string sceneName)
    {
        //当前进度
        float currentProgress = 0;
        //目标进度
        float targetProgress = 0;
        float speed = 0.02f;
        _asyncOperation = SceneManager.LoadSceneAsync(sceneName);
        if (_asyncOperation == null)
        {
            Debug.LogError("没有找到场景: " + sceneName);
            yield break;
        }
        //unity 加载90%
        _asyncOperation.allowSceneActivation = false;
        while (_asyncOperation.progress < 0.9f)
        {
            targetProgress = _asyncOperation.progress;
            //平滑过渡
            while (currentProgress < targetProgress)
            {
                //progressText.text = string.Format("{0:f0}{1}", currentProgress * 100, "%");
                //sliderImage.rectTransform.sizeDelta = new Vector2(704f * currentProgress, 12);
                yield return null;
                currentProgress += speed;
            }
            yield return null;
        }
        //自行加载剩余的10%
        targetProgress = 1f;
        while (currentProgress < targetProgress)
        {
            //progressText.text = string.Format("{0:f0}{1}", currentProgress * 100, "%");
            //sliderImage.GetComponent<RectTransform>().sizeDelta = new Vector2(704f * currentProgress, 12);
            yield return null;
            currentProgress += speed;
        }
        _asyncOperation.allowSceneActivation = true;
        currentProgress = 1;
        //progressText.text = string.Format("{0:f0}{1}", currentProgress * 100, "%");
        //sliderImage.GetComponent<RectTransform>().sizeDelta = new Vector2(704f * currentProgress, 12);
        //等待一会等场景中资源加载一会儿再显示(设置0.5f切换到登录界面会有显示问题)
        yield return new WaitForSeconds(1f); //(0.5f);//(1f);

    }
}

以上就是assetbundle打包到比对更新,下载资源,解压应用的整套流程。浏览之前最好对unity  Assetbundle的打包流程到应用有所了解之后再结合应用到自己的项目中。
 

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值