unity3d webplayer中谨慎使用WWW.Dispose()
这两天一直在解决Unity Webplayer崩溃的bug,分享一下经验教训,希望遇到类似问题的人可以提前避开危险的WWW.Dispose()。
先看结论
使用WWW.Dispose()中断正在加载过程中的WWW有可能导致Webplayer崩溃,中断越频繁,崩溃概率越高。
目前没有万无一失的中断WWW的方法(变通方案见后文)
关于WWW.Dispose()…
介绍一下WWW.Dispose()这个方法:它的出现是因为WWW实现了.Net的IDisposable接口,所以WWW可以Dispose自己,也就是关闭加载进程并释放相关资源。此前Dispose()曾出现在WWW的ScriptReference里,但没有描述,现在不再出现了,但是可以使用。
在Unity 3.0或更早的时代,大家都习惯使用WWW.Dispose()来清空加载进程,甚至被写在了Unity的帮助文档里,那时经常可以看到这样的脚本:
WWW www = new WWW ("SOME_URL");
while ( !www.isDone ) yield return 1;
// Do sth when www is done.
www.Dispose();
www = null;
现在再查阅WWW的脚本帮助文档,看不到Dispose()的踪影了——很明显,这前后的变化代表从Unity官方的角度已不再推荐使用Dispose()。
现在官方文档中的加载流程是这样的:
WWW www = new WWW ("SOME_URL");
while ( !www.isDone ) yield return 1;
// Do sth when www is done.
也就是说,现在不用我们操心何时释放WWW了,只管用,届时Unity自己会在合适的时机清空加载进程——这一点可以通过监测加载进程看出来,当下载过程完毕后一段时间,加载进程才会被移除。所以:
正常地去使用WWW不会有任何问题(不使用Dispose)。
如果你有在WWW加载完毕后Dispose掉它的好习惯,也没问题,这一点我已经反复测试过了。
WWW.Dispose带来的隐患
但是,有些时候我们希望中断WWW的加载过程,例如我们现在所做的网络应用会根据玩家的空间位置控制场景元素的加载顺序,并且如果玩家离开某一区域,之前该区域中正在加载的元素会自动中断加载,将流量省下给其他距离玩家更近的加载元素——问题出在“中断WWW”。简单来说:
Dispose确实具有中断WWW的作用
Webplayer中,使用Dispose中断正在加载的WWW,可能导致插件崩溃
WWW中断的频率越高,Webplayer插件崩溃的概率越高
如果只是偶尔中断一下WWW,插件崩溃的概率会非常低,但是也不万无一失的,我测试过每隔2秒中断一个WWW,隔不了太长时间还是会崩溃,如果同时中断数个WWW,崩溃几乎是必然的。
那么应该如何安全地中断WWW?
由于Dispose()存在的隐患,在Webplayer中(其他平台我没有做过太多测试)我们还没有找到真正安全的中断方法,如果确实有大量的WWW请求需要管理,我目前的做法是:
为所有的加载请求创建一个控制器,比如叫做LoadingManager
加载时不直接调用WWW,而是把请求发送给控制器,例如LoadingManager.Load(“URL”);
LoadingManager把需要加载的URL缓存起来,控制同时只能开启有限个(例如3个)WWW进程。
在加载期间如果需要中断某个加载请求,调用LoadManager.StopLoading(“URL”):如果URL还未开始真正加载,则直接删除缓存的url即可;如果正在加载,那么中断不会成功(规避Dispose的bug)——由于加载进程数很少,无法中断的代价因而被削弱——最大代价出现在当前所有正在加载的WWW都需要中断的时候,此时真正需要加载的内容需要等待它们全部加载完。
这个Bug是有时效性的
以上是我对WWW.Dispose()使用的一些经验,显然它是有时效性的,因为这个Bug可能会在后面被Unity修复,或者给出真正安全的中断WWW的方法(WWW.Stop()?),目前我能重复验证bug的环境是:
Unity Webplayer 3.5
所有浏览器(出现错误报告:The content was stopped because a fatal content error has been detected.)
测试案例
我做了一个简单的测试网页,亲手验证崩溃事实:http://spotlightor.com/lab/Unity3d/www-dispose-crash-test
点击按钮开启加载,在未加载完毕之前再次点击按钮中断加载,反复几次,就会崩溃,速度越快,崩溃越快。
using UnityEngine;
using System.Collections;
public class WwwLoader : MonoBehaviour
{
public string url;
private WWW www;
void OnGUI ()
{
if (www == null) {
if (GUI.Button (new Rect (0, 0, 150, 40), "Load"))
Load ();
} else {
if (GUI.Button (new Rect (0, 0, 150, 40), "Dispose"))
StopLoading ();
GUI.Box (new Rect (0, 45, 150, 30), string.Format ("prOGREss:{0:00}%", www.prOGREss * 100));
}
}
public void Load ()
{
StartCoroutine ("DoLoad");
}
private IEnumerator DoLoad ()
{
www = new WWW (url);
while (!www.isDone)
yield return 1;
InstantiateAsset ();
}
private void InstantiateAsset ()
{
if (www != null) {
AssetBundle bunlde = www.assetBundle;
GameObject prefab = GameObject.Instantiate (bunlde.mainAsset) as GameObject;
prefab.transform.position = Vector3.zero;
}
}
public void StopLoading ()
{
if (www != null) {
StopCoroutine ("DoLoad");
www.Dispose ();
www = null;
}
}
}
>>相关产品