Unity资源内存管理--webstream控制

摘要:
首先,使用前提1,您需要使用资源热更新2,使用资产包资源热更新(资产包是生成Web流的罪魁祸首)。第二,为什么使用Asset BundleAsset Bundle本质上是一种压缩算法,但它比zip有更多的信息,例如平台信息(Ios、android)、依赖关系信息等。由于它是压缩的,所以很容易理解Asset Bundle旨在减小包的大小。资产/资源

一 使用前提

1,需要使用资源热更新

2,使用Assetbundle资源热更(AssetBundle是产生webstream的元凶)

二 为什么要用AssetBundle

AssetBundle本质上就是一个压缩算法,只不过比起zip等一些压缩多了一些信息,比如平台信息(Ios,android),依赖信息等,既然是压缩,那就很好理解了,AssetBundle就是为了减少包体的大小的。Assets/Resources目录其实也是一样的

三 怎么生成AssetBundle

using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
public class PushAndPop
{
    [MenuItem("Build/test")]
    static void Test()
    {
        string path1 ="Assets/Resources/UI1.prefab";
        List<string> deps1 = GetDepends(path1);
        Execute(path1, deps1);

        string path2 = "Assets/Resources/UI2.prefab";
        List<string> deps2 = GetDepends(path2);
        Execute(path2, deps2);

    }
    //获取path的依赖资源路径
    static List<string> GetDepends(string path)
    {
        List<string> deps = AssetDatabase.GetDependencies(new string[] { path }).ToList<string>();
        return deps;
    }
    //打包资源path成AssetBundle
    static void Execute(string path, List<string> deps)
    {
        string SavePath = Application.streamingAssetsPath + "/";

        BuildAssetBundleOptions buildOp = BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets
            | BuildAssetBundleOptions.DeterministicAssetBundle;

        //先打包依赖
        BuildPipeline.PushAssetDependencies();
        string bundleName = "";
        foreach (var dep in deps)
        {
            Debug.Log(dep);
            string ext = System.IO.Path.GetExtension(dep);
            if (ext == ".png" || ext==".shader" || ext==".FBX" || ext==".ttf")
            {

                Object sharedAsset = AssetDatabase.LoadMainAssetAtPath(dep);
                bundleName = sharedAsset.name.Replace('/', '_') + ext.Replace('.','_');
                BuildPipeline.BuildAssetBundle(sharedAsset, null, SavePath + bundleName + ".assetbundle", buildOp, BuildTarget.StandaloneWindows);
            }
        }

        //再打包主资源
        BuildPipeline.PushAssetDependencies();
        Object mainAsset = AssetDatabase.LoadMainAssetAtPath(path);
        bundleName = mainAsset.name.Replace('/', '_');
        BuildPipeline.BuildAssetBundle(mainAsset, null, SavePath + mainAsset.name + ".assetbundle", buildOp, BuildTarget.StandaloneWindows);
        BuildPipeline.PopAssetDependencies();

        BuildPipeline.PopAssetDependencies();
        AssetDatabase.Refresh();
    }

}

实例代码把Resources目录下的UI1和UI2打成了AssetBundle,并把他们放到了StreamingAssets目录,如下

Unity资源内存管理--webstream控制第1张

其中myAtlas_png和Unlit_Transparent Colored_shader是UI1和UI2的依赖资源

四 加载AssetBundle并且实例化资源

加载要先依赖再主资源

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

public class AssetLoad : MonoBehaviour
{
    void OnGUI()
    {

        //依赖加载按钮
        if (GUI.Button(new Rect(0f, 30f, 100f, 20f), "Load Share Res"))
        {
            StartCoroutine(Load(@"file://" + Application.streamingAssetsPath + "/myAtlas_png.assetbundle"));
            StartCoroutine(Load(@"file://" + Application.streamingAssetsPath + "/Unlit_Transparent Colored_shader.assetbundle"));
        }

        if (GUI.Button(new Rect(0f, 60f, 100f, 20f), "Load UI1"))
        {
            StartCoroutine(LoadAndInstantiate(@"file://"+Application.streamingAssetsPath + "/UI1.assetbundle"));
        }

        if (GUI.Button(new Rect(0f, 90f, 100f, 20f), "Load UI2"))
        {
            StartCoroutine(LoadAndInstantiate(@"file://" + Application.streamingAssetsPath + "/UI2.assetbundle"));
        }

    }

    // 加载
    IEnumerator Load(string url)
    {
        WWW www = new WWW(url);
        yield return www;
        AssetBundle ab = www.assetBundle;
        //Object obj = ab.mainAsset;
        ab.LoadAll();
        Debug.Log("load" + url);
    }

    // 加载并实例化
    IEnumerator LoadAndInstantiate(string url)
    {
        WWW www = new WWW(url);
        yield return www;

        if (!System.String.IsNullOrEmpty(www.error))
        {
            Debug.Log(www.error);
        }
        else
        {
            Object main = www.assetBundle.mainAsset;
            GameObject.Instantiate(main);
        }
    }

}

实例用www从StreamingAssets目录加载资源,加载完成后

Unity资源内存管理--webstream控制第2张

 五 AssetBundle的内存占用

我们先看看加进来的AssetBudle都使用了哪些内存,点击unity的菜单栏Window->Profiler,打开如下窗口

Unity资源内存管理--webstream控制第3张

点击到Memory那一栏,在点击Simple旁边的下三角,选择Detailed

Unity资源内存管理--webstream控制第4张

点击other,看到下面有一栏WebStream

Unity资源内存管理--webstream控制第5张

这就是我们加进来的4个AssetBundle的占用的内存,后面带有各自占用内存

但是还没完,我们点开Assets这一栏

Unity资源内存管理--webstream控制第6张

我们看到Texture2D下面有一张我们用到的图片,Shader下面也有

Unity资源内存管理--webstream控制第7张

那么问题来了,为什么加进来的AssetBundle占有两处内存(WebStream和Asset),其实也很好理解,我们上面知道AssetBundle和压缩一样,这就好比我们在网站下载了一个zip的压缩文件,如果我们不解压是看不了里面的内容的,这就相当于WebStream下面的内存,Unity是识别不了的。这时候你用压缩软件把zip文件解压了,并且把文件解压在另外的目录,这个解压的操作和unity的ab.loadAll()操作是一样一样的,有木有,有木有,加载出来的Asset资源,Unity就能识别了。

六 AssetBundle内存释放

上面我们说过AssetBundle的内存占用分为WebStream和Asset,那么他们分别是怎么释放的,在什么时候释放,这是个问题

首先,我们释放WebStream的内存,调用ab.Unload(false)函数,可以释放AssetBundle的WebStream的内存 

修改Load函数

    // 加载
    List<AssetBundle> ablist = new List<AssetBundle>();
    IEnumerator Load(string url)
    {
        WWW www = new WWW(url);
        yield return www;
        AssetBundle ab = www.assetBundle;
        //Object obj = ab.mainAsset;
        ab.LoadAll();
        //缓存ab
        ablist.Add(ab);
        Debug.Log("load" + url);
    }

    // 加载并实例化
    IEnumerator LoadAndInstantiate(string url)
    {
        WWW www = new WWW(url);
        yield return www;

        if (!System.String.IsNullOrEmpty(www.error))
        {
            Debug.Log(www.error);
        }
        else
        {
            Object main = www.assetBundle.mainAsset;
            GameObject.Instantiate(main);
        }
        //释放WebStream
        foreach(var ab in ablist)
        {
            if(ab != null)
            {
                ab.Unload(false);
            }
        }
    }

运行我们在看看

Unity资源内存管理--webstream控制第8张

只剩下两个ui主资源的Webstream了,把他俩的ab加入释放列表,也是可以释放的。

unload(false)释放了Webstream的内存,我们再看看怎么释放Asset的内存,就是类似Texture2D下的图片内存等

释放Asset的内存使用Resources.UnloadAsset(obj) obj就是LoadAll加载出来的资源

我们再次修改我们的加载函数

    // 加载
    List<AssetBundle> ablist = new List<AssetBundle>();
    List<Object> objlist = new List<Object>();
    IEnumerator Load(string url)
    {
        WWW www = new WWW(url);
        yield return www;
        AssetBundle ab = www.assetBundle;
        //Object obj = ab.mainAsset;
        Object[] objs = ab.LoadAll();
        //缓存ab
        ablist.Add(ab);
        objlist.AddRange(objs.ToList<Object>());
        Debug.Log("load" + url);
    }

添加一个按钮作为释放的操作

        if(GUI.Button(new Rect(0f, 120f, 100f, 20f), "Unload"))
        {
            foreach (var obj in objlist)
            {
                Resources.UnloadAsset(obj);
            }
        }

当点击Unload的时候释放了png和shader的Asset内存,我们发现UI也不可见了,所以这个释放操作是在所有引用到这个依赖的GameObject都Destroy的时候调用的,不然会出现资源丢失的情况

 七 WebStream的释放时机


我们知道Webstream是用unlaod(false)释放的,但是我们发现,如果UI1和UI2都引用了png,当你实例化UI1后释放png的Webstream,在实例化UI2就不成功了,因为UI2依赖png,所以Webstream和Asset内存一样,也要在所有引用到这个依赖的GameObject都Destroy的时候才能释放。

最后修改的代码如下

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
public class AssetLoad : MonoBehaviour
{

    void OnGUI()
    {

        //依赖加载按钮
        if (GUI.Button(new Rect(0f, 30f, 100f, 20f), "Load Share Res"))
        {
            StartCoroutine(Load(@"file://" + Application.streamingAssetsPath + "/myAtlas_png.assetbundle"));
            StartCoroutine(Load(@"file://" + Application.streamingAssetsPath + "/Unlit_Transparent Colored_shader.assetbundle"));
        }

        if (GUI.Button(new Rect(0f, 60f, 100f, 20f), "Load UI1"))
        {
            StartCoroutine(LoadAndInstantiate(@"file://"+Application.streamingAssetsPath + "/UI1.assetbundle"));
        }

        if (GUI.Button(new Rect(0f, 90f, 100f, 20f), "Load UI2"))
        {
            StartCoroutine(LoadAndInstantiate(@"file://" + Application.streamingAssetsPath + "/UI2.assetbundle"));
        }

        if (GUI.Button(new Rect(0f, 120f, 100f, 20f), "Detroy"))
        {
            GameObject go = GameObject.Find("UI1(Clone)");
            GameObject.Destroy(go);
            go = GameObject.Find("UI2(Clone)");
            GameObject.Destroy(go);
        }

         if (GUI.Button(new Rect(0f, 150f, 100f, 20f), "Unload"))
        {
            //释放WebStream
            foreach (var ab in ablist)
            {
                if (ab != null)
                {
                    ab.Unload(false);
                }
            }
            //释放Asset
            foreach (var obj in objlist)
            {
                Resources.UnloadAsset(obj);
            }
        }
    }

    // 加载
    List<AssetBundle> ablist = new List<AssetBundle>();
    List<Object> objlist = new List<Object>();
    IEnumerator Load(string url)
    {
        WWW www = new WWW(url);
        yield return www;
        AssetBundle ab = www.assetBundle;
        //Object obj = ab.mainAsset;
        Object[] objs = ab.LoadAll();
        //缓存ab
        ablist.Add(ab);
        objlist.AddRange(objs.ToList<Object>());
        Debug.Log("load" + url);
    }

    // 加载并实例化
    IEnumerator LoadAndInstantiate(string url)
    {
        WWW www = new WWW(url);
        yield return www;

        if (!System.String.IsNullOrEmpty(www.error))
        {
            Debug.Log(www.error);
        }
        else
        {
            AssetBundle ab = www.assetBundle;
            ablist.Add(ab);
            Object main = ab.mainAsset;
            GameObject.Instantiate(main);
        }


    }

}

八 游戏项目中的内存解决方案

一般在游戏中,游戏资源很多,依赖关系很更复杂,有可能一个png有很多个GameObject引用,如果不及时的释放没有引用的资源,游戏很可能因为内存不足变得卡顿,甚至闪退,

一个很好的办法就是关联主资源和所有实例化的GameObject,并且给主资源引用的依赖计数,每当有一个主资源引用到依赖,依赖引用计数就+1,每当实例化一个GameObject,主资源的计数+1,当删除一个GamObject的时候,主资源计数-1,当主资源计数为0,主资源依赖计数分别-1,如果依赖的计数为0,释放这个依赖的Webstream和Asset内存。

免责声明:文章转载自《Unity资源内存管理--webstream控制》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇业务系统部署。。。MySql数据类型下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

sqlserver 中含有某字符串

查找 sqlserver 中字符串的ascii码SET TEXTSIZE 0-- Create variables for the character string and for the current -- position in the string.DECLARE @position int, @string char(8)-- Initializ...

java生成解析xml的另外两种方法Xstream

Xstream生成和解析xm和JAXB生成和解析xml的方法。 一,Xstream Xstream非jdk自带的,需要到入Xstream-1.4.3.jar和xpp3_min-1.1.4.jar 1.Xstream简介;  使用限制: JDK版本不能<1.5. 虽然预处理注解是安全的,但自动侦查注解可能发生竞争条件. 特点: 简化的API; 无映射文...

Java获取Linux上指定文件夹下所有第一级子文件夹

说明:需要只获得第一级文件夹目录 packagecom.sunsheen.jfids.studio.monitor.utils; importjava.io.BufferedReader; importjava.io.IOException; importjava.io.InputStream; importjava.io.InputStreamRead...

干货分享:ASP.NET CORE(C#)与Spring Boot MVC(JAVA)异曲同工的编程方式总结

我(梦在旅途,http://zuowj.cnblogs.com; http://www.zuowenjun.cn)最近发表的一篇文章《.NET CORE与Spring Boot编写控制台程序应有的优雅姿势》看到都上48小时阅读排行榜(当然之前发表的文章也有哦!),说明关注.NET CORE及Spring Boot的人很多,也是目前的主流方向,于是我便决定系...

在springboot启动时给钉钉群发通知

1.因为springboot启动后会加载所用的配置文件,所以我们可以在main方法下写DingTalk的bean来注入DingTalk配置。 @ServletComponentScan public classApplication { //DingTalk Bean变量 private static String DING_TALK_U...

RSA加密和数字签名在Java中常见应用【原创】

相关术语解释: RSA,参考: https://en.wikipedia.org/wiki/RSA_(cryptosystem) 非对称加密算法 ,参考:https://baike.baidu.com/item/%E9%9D%9E%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86%E7%AE%97%E6%B3%95/1208...