Unity5 AssetBundle的一些整理(五)

前面四篇讲解了打包AB的相关知识,思路和工程也都贴出了,由于功能代码量还是蛮多的,这里暂时就先不一一讲解了,注释的应该还是比较详细的。这一篇就主要讲解AB包的使用,总不能打好AB包而不会用吧。

如同Resource.Load()一般,读取AB的方法也很简单,AssetBundle.LoadFromFile(assetBundlePath),这个方法是官方推荐使用的,也是读取最快的。当然如果你的AB资源是放在服务器上做热更新用的,应该用WWW去读取,这边暂时不对此做详细的描述。通过上面的方法读取到AssetBundle对象后,自然要获取里面的Asset。使用assetBundle.LoadAsset(assetName)我们就可以获取到打包成AB前的Asset,进行使用。相关代码在工程里面Assets\Scripts\Utilities\AssetBundle文件夹内。

注意:AssetBundle对象其实只是存储AB包的文件信息,内存占用相对较小,只有LoadAsset时才读取AB包的内容到内存。


这里先讲讲关于AB的依赖按照前面的内容,我们知道,如果一个UI界面上,用到一张图片。然后我们将其打包AB资源,需要打包成两个,一个是ui.ab,一个是image.ab,(ui.ab依赖于image.ab)。当我们获取ui.ab并且取出里面的Asset并实例化出来后,会发现里面的图片并不能显示出来。其原因在于,我们没有获取其依赖的image.ab。只要我们事先读取image.ab,生成对应的AssetBundle即可。同时我们根据前面的知识知道,AB包所有的依赖关系Unity都帮我们存好放在了与AB包父目录同名的文件当中(文中为StreamingAssets)。其实这个也是一个AB文件,我们只需要从程序运行的时候读取该AB,获取AssetBundleManifest对象存放在内存当中,然后每次读取AB包的时候都根据AB的包名从AssetBundleManifest对象中获取其相关依赖的包名获取对应AB即可。

注意:依赖的AB包,我们读取的时候只需要生成对应的AssetBundle对象,无需手动去LoadAsset我们需要的资源,Unity会自动替我们处理。

读取AssetBundleManifest的代码如下:

public static readonly string streamingAssetsFileName = "StreamingAssets";

static AssetBundleManifest m_mainfest;
static AssetBundleManifest mainfest {
    get {
        if(m_mainfest == null) {
            AssetBundle ab = AssetBundle.LoadFromFile(streamingAssetsPath + "/" + streamingAssetsFileName);
            if(ab != null) {
                m_mainfest = (AssetBundleManifest)ab.LoadAsset("AssetBundleManifest");

                //释放AssetBundle对象
                ab.Unload(false);
                ab = null;
            } else {
                Debug.LogError("Get Mainfest Error");
                return null;
            }
        }
        return m_mainfest;
    }
}
/// 
/// StreamingAssets 路径
/// 
public static string streamingAssetsPath {
    get {
#if UNITY_ANDROID
        return Application.dataPath + "!assets";   // 安卓平台
#else
        return Application.streamingAssetsPath;  // 其他平台
#endif
    }
    //get { return Application.streamingAssetsPath; }
}

这段代码还有一个需要注意的就是读取路径啦,安卓和其他的有所不同。关于unity各个路径对应的手机路径看见一篇不错的博客有兴趣的可以看看。


下面再讲讲关于AB的释放,官方给出的方法是assetBundle.Unload(bool);关于参数的bool值,若为false表示只释放AssetBundle本身,而要是为true,则会连AssetBundle里面的Asset一起释放。

举个例子:一张包含图片的AB,我们读取该AB的时候,生成一个AssetBundle对象,然后使用LoadAsset获取,生成一个Texture对象,最后在UGUI里面的RawImage里面设置Texture为该texture。这一段功能一共多出两个新的实例:assetbundle和texture。当我们调用的assetBundle.Unload(false);时,assetbundle会被释放掉,而texture仍然存在内存当中,RawImage可以继续正常显示该图片,但是如果Unload(true);时,assetbundle和texture都将被释放掉,RawImage原来指定的图片也将消失。如图:

Unity5 AssetBundle的一些整理(五)_第1张图片


Unity5 AssetBundle的一些整理(五)_第2张图片

可能大家会有疑问,为什么panel3的背景图没了,这个和Asset的使用有关,Texture的Asset,我们RawImage指向它只是增加一个引用,并不会重新clone一份分配新的内存,所有Texture Asset释放的时候,所有用到改Texture的地方都引用不到了。而prefab的Asset,我们Instantiate使用的时候,会clone这个Asset,形成一个新的实例。所以prefab asset释放后,这个clone对象依旧存在,那么为什么背景图没了呢?依照texture可以知道,即便clone了新的对象,他们的texture和sprite依旧引用同一个,即prefab Asset里面的,释放后就引用不到了。

备注:除了Unload方法,还有以下几个方法可以释放AB的Asset资源,可以再Profiler里面查看资源情况:

Resources.UnloadAsset(assetname);回收指定的缓存。

Resources.UnloadUnusedAsset();回收没有被引用的缓存。

GC.Collect();加上这句可立即释放内存。

Unity5 AssetBundle的一些整理(五)_第3张图片

从上面的这些知识又可以衍生出几个注意点:

比如:当我们有两个AB,a.ab和b.ab都依赖于c.ab,那么按照前面的读取这两个AB,在不加特殊处理的情况下就会去读取两次c.ab,这种情况Unity是不允许的(当然你可以读取一个AB的Asset后立刻把AB Unload掉,但是这样会导致过多的AB读取与释放)。因此我们需要添加一个Dictionary,以abname和AssetBundle为键值对。当读一个新的AB的时候先判断Dictionary中是否存储了该AB,存储了则直接使用该AB,若没存储再使用LoadFromFile去读取,读取成功后存入Dictionary。

接着便是释放AB时机的时候,因为手游肯定要尽可能的节省内存的开支,所以用不到AB以及其Asset一定要释放掉。当AB包越多时,其相互依赖的关系也会越复杂。一个AB可能被多个AB所依赖或者Asset还被使用,所以释放的时候一定要确定没有其他依赖并且Asset没有引用的时候。所以我们可以引入一个计数器的概念。每个Asset被加载的时候AB的计数器++,Asset需要被卸载的时候AB的计数器--。当计数器为0的时候,执行Unload(true)。

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

namespace Utility {
    /// 
    /// 读取AB的工具类
    /// 
	public class AssetBundleUtility {

        //public static readonly string suffix = ".assetbundle";
        public static readonly string streamingAssetsFileName = "StreamingAssets";

        static AssetBundleManifest m_mainfest;
        static AssetBundleManifest mainfest {
            get {
                if(m_mainfest == null) {
                    AssetBundle ab = AssetBundle.LoadFromFile(streamingAssetsPath + "/" + streamingAssetsFileName);
                    if(ab != null) {
                        m_mainfest = (AssetBundleManifest)ab.LoadAsset("AssetBundleManifest");

                        //释放AssetBundle对象
                        ab.Unload(false);
                        ab = null;
                    } else {
                        Debug.LogError("Get Mainfest Error");
                        return null;
                    }
                }
                return m_mainfest;
            }
        }
        /// 
        /// 缓存使用到的AssetBundle
        /// 
        static Dictionary cacheAssetBundleItemDic = new Dictionary();

        /// 
        /// 同步加载AB资源
        /// 
        /// AB资源路径
        /// AB资源文件名
        /// 是否存在依赖关系
        /// AB包信息
        public static AssetBundleItem Load(string path, string fileName, bool isHasDependence = true) {
            if(mainfest != null) {
                path = path.ToLower();
                fileName = fileName.ToLower();

                if(isHasDependence) {
                    //读取依赖
                    string[] dps = mainfest.GetAllDependencies(path);
                    int len = dps.Length;
                    for(int i = 0; i < len; i++) {
                        AssetBundleItem dItem;
                        if(cacheAssetBundleItemDic.ContainsKey(dps[i])) {
                            dItem = cacheAssetBundleItemDic[dps[i]];
                        } else {
                            dItem = new AssetBundleItem(dps[i], fileName, false);
                            dItem.assetBundle = AssetBundle.LoadFromFile(dItem.pathName);
                            cacheAssetBundleItemDic.Add(dps[i], dItem);
                        }
                        Debug.Log("wjr---ABLoad---Dependence---" + dps[i]);
                        dItem.refCount++;
                    }
                }
                
                AssetBundleItem ab;
                if(cacheAssetBundleItemDic.ContainsKey(path)) {
                    ab = cacheAssetBundleItemDic[path];
                } else {
                    ab = new AssetBundleItem(path, fileName, isHasDependence);
                    ab.assetBundle = AssetBundle.LoadFromFile(ab.pathName);
                    cacheAssetBundleItemDic.Add(path, ab);
                }
                Debug.Log("wjr---ABLoad---" + path);
                ab.refCount++;
                return ab;
            }
            return null;
        }

        /// 
        /// AB的引用-1,若为0则删除该AB
        /// 
        /// 
        public static void Delete(string path) {
            path = path.ToLower();
            if(cacheAssetBundleItemDic.ContainsKey(path)) {
                AssetBundleItem ab = cacheAssetBundleItemDic[path];

                if(ab.isHasDependence) {
                    //删除依赖
                    string[] dps = mainfest.GetAllDependencies(path);
                    for(int i = 0, len = dps.Length; i < len; i++) {
                        Delete(dps[i]);
                    }
                }

                ab.refCount--;
                if(ab.refCount <= 0) {
                    Debug.Log("wjr---ABDelete---" + ab.pathName);
                    ab.assetBundle.Unload(true);
                    ab = null;
                    cacheAssetBundleItemDic.Remove(path);
                }
            }
        }

        private static StringBuilder getPathResult = new StringBuilder();
        private static string tmpPath = string.Empty;
        /// 
        /// 资源同步加载路径(无 file:///)
        /// 
        public static string GetAssetPath(string path) {
            // 先尝试从 persist 目录加载
            if(true) {
                getPathResult.Length = 0;
                getPathResult.Append(sandboxPath);
                getPathResult.Append("/");
                getPathResult.Append(path);
                tmpPath = getPathResult.ToString();
                if(File.Exists(tmpPath)) {
                    getPathResult.Length = 0;
                    return tmpPath;
                }
            }
            getPathResult.Length = 0;
            getPathResult.Append(streamingAssetsPath);
            getPathResult.Append("/");
            getPathResult.Append(path);
            tmpPath = getPathResult.ToString();
            return tmpPath;
        }

        /// 
        /// 沙盒路径
        /// 可读可写,一般存放网上下载的资源
        /// 
        public static string sandboxPath {
            get { return Application.persistentDataPath; }
        }

        /// 
        /// StreamingAssets 路径
        /// 
        public static string streamingAssetsPath {
            get {
#if UNITY_ANDROID
                return Application.dataPath + "!assets";   // 安卓平台
#else
                return Application.streamingAssetsPath;  // 其他平台
#endif
            }
            //get { return Application.streamingAssetsPath; }
        }
    }

    /// 
    /// 存储单个AB资源信息
    /// 
    public class AssetBundleItem {
        public string pathName;
        public string fileName;
        public AssetBundle assetBundle;
        public int refCount;
        public bool isHasDependence;

        public AssetBundleItem(string path, string file, bool isHasDependence) {
            pathName = AssetBundleUtility.GetAssetPath(path);
            fileName = file;
            assetBundle = null;
            refCount = 0;
            this.isHasDependence = isHasDependence;
        }

        public Object LoadAsset(System.Type type) {
            return LoadAsset(fileName, type);
        }

        public Object LoadAsset(string name, System.Type type) {
            if(assetBundle != null) {
                return assetBundle.LoadAsset(name, type);
            }
            return null;
        }
    }
}
希望喜欢文章的朋友们给点个赞。

你可能感兴趣的:(AssetBundle)