Flash Player 9中的资源管理策略
原文
Resource management strategies in Flash Player 9
翻译
Actionscript 3.0带给Flash开发人员更加快的代码执行和许多新的API增强,站在开发人员的立足点来说,相对之前的版本这些改变需要更高级别的可靠性。本文重点讨论Actionscript 3.0中新资源管理特性,以及粗略的讨论下Actionscript 3.0中那些可以帮助您跟踪和更加有效的管理内存的工具。
Actionscript 3.0中影响资源管理的最大的改变是其新的显示列表模型。在Flash Player 8及之前版本中,当一个显示(display)对象被从屏幕被移除时(使用removeMovie 或 unloadMovie),该显示对象及其子对象将被立即从内存总移除并且代码即可终止,Flash Player 9带来了更加灵活的显示列表模型,在该模型中,将显示对象(sprites、movie clips等)作为普通对象一样对待。
这意味着开发人员现在可以做一些真正酷的事情,比如重排根目录(reparenting:将显示对象从一个显示列表移到另一中)和从已经载入的SWF中实例化显示对象。不幸的是,它也同时意味着现在显示对象与其他别的对象一样被垃圾收集器同等对待,它带来了大量有意思(可能不明显)的问题。
为什么资源管理是个问题
Flash开发人员看了Actionscript 3.0中这些新的资源管理考虑可能觉得概念很复杂,另一方面,Java开发人员可能觉得没什么。这些差距是可以理解的:Flash开发人员不习惯在基本的最佳的实践之外实现手工资源管理——如当不再使用时删除引用——反之Java开发人员之前就将其贯穿所有中了。这些问题对于大多数现代的内存管理语言也同样会出现,不幸的是,现在没有完全避免它们的方法。
尽管资源管理是生活之本,Flash遇见了很多在其他语言中罕见的挑战(包括Flex)。Flash内容往往包含许多闲置或易交互的执行代码——不像Java和Flex他们大多数是交互式的。这意味着只有用户交互时才执行CPU密集代码。另外,Flash工程比其他平台更频繁的从第三方资源载入(可能使用贫编码标准)外部内容。Flash开发人员也只有更少的工具、剖析器和框架可以使用。
最后,Flash开发人员通常有着很少的非正式的编程工作背景。我所知道的大部分Flash开发人员有着音乐、艺术、商业、哲学或只是除编程外的任何背景。这种多元化的结果带来另人震撼的创意和内容,但是该社区却没有真正准备好处理资源管理问题。
问题1:动态内容
在资源管理中遇到的其中一个明显的问题是与sprites(或其他显示对象)有关,您动态实例化它们,然后希望在以后的时间里面移除它们。当您将显示对象从场景中移除后,因为它们不再活动并且挂死在显示列表中,它仍会一直存在内存中。如果您做好了清除它的所有引用的工作,那们在下次垃圾收集器运行收集时,该剪接将会被从内存中移除。基于松散内存管理使用的特性,这将有很多不确定性。
注意到这一点非常的重要,显示对象不只一直占用内存,它还会一直执行“空闲”代码,如定时器、进入帧以及监听是否从某一范围出来的监听器。
以下几个示例可以帮助您说明该问题:
某一游戏sprite监听它自己的enterFrame事件,每次帧移动时,该应用就会处理某些计算以决定它是否接近其他游戏元素。在Actionscript 3.0中,即使您已经将该sprite从显示列表中移除并将所有对它的应用都置为null,除非它已经被垃圾收集器给移除后,在每个帧移动时,该应用仍会一直运行该代码。您必须记住要当该sprite并移除时必须明确将enterFrame监听器移除。
考虑一个通过注册场景的mouseMove来跟随鼠标的电影片段(movie clip)——在新的事件模型下这是达到该效果的唯一方式。除非您记得移除监听器,每次鼠标移动时,该片段将一直会执行该代码,即使在片段被“删除”后。缺省的,因为场景中为了事件发布有一个指向它的引用,该片段一直会执行。我将在后面的文章中讨论如何避免这样的问题
现在想象一下以上示例蕴含的含义,在垃圾收集器回收前移除多个sprites——或者如果移除某一sprites所有的引用失败会发生什么。您很容易一不小心就超出CPU最大处理能力,进而使得您的应用或游戏慢得像在爬,甚至搞得用户计算机完全停顿。当前还没有办法强行让Flash Player杀死一个显示对象并停止它的执行。至多只能在该对象被从显示中移除时由Flash开发人员在手动这样做。
问题2:已载入的内容
记住现在已载入的SWF的内容也是和其他别的对象一样被同等对待的,并且您可以开始想象下您在载入内容时可能会遭遇到的某些问题。类似其他显示对象,当前没有方法可以直接将已载入SWF和它的内容从内容中移除。调用Loader.unload只是简单将载入器指向SWF的应用置空;它将继续存在并保持执行直到它被下次垃圾收集器回收掉(确保所有对已载入内容的其他引用都已被完整的清除)。
考虑以下两个场景:
您创建一个用来载入您的实验Flash工程的shell。这一实验工作是尖端技术,并把CPU的资源已经用到了极限 。某一用户点击某一按钮来载入一个实验,查看它,然后再点击某一按钮来载入第二个实验,如果到第一个实验的所有引用都已经被清除,它将继续在后台运行,当第二个实验在同一时刻运行时,很可能会出现最大处理能力溢出。
某一客户委托您创建一个应用来载入其他开发人员创建的Actionscript 3.0 SWFs。该开发人员增加了到场景的监听器或其他别的如创建了一个到其自己内容的外部引用,它将内存中活动并且继续消耗CPU资源直到用户退出您的应用。就算该载入的内容没有任何外部引用,它仍然会继续无限期的执行直到被下次垃圾收集器回收。
当您设计一个载入不可信内容的应用时,觉察到这非常重要——在您卸载它后该代码仍会继续执行。虽然该内容会遵循Flash Player安全模型规范运行,但在您的应用开发过程中考虑一些潜在的漏洞仍然是一个好主意。
使用System.totalMemory
尽管System.totalMemory是一个简单工具,但它是重要的因为在Flash中它是开发人员可以使用的第一个运行时剖析工具。它可以让您监控Flash Player运行时用了多少内存。这使得您在开发中有一定的能力调整您自己工作而不用使用系统监视器。更重要的,它使得您可以在给用户带来一系列问题前超前的处理您的内容中的重大内存泄露成为可能。释出一个错误然后终止您的应用总是好于使用户系统停顿或者甚至完全的死机。
这是一个您如何可以做到这的简单示例:
import flash.system.System;
import flash.net.navigateToURL;
import flash.net.URLRequest;
...
// check our memory every 1 second:
// 每秒检查一下我们的内存:
var checkMemoryIntervalID:uint = setInterval(checkMemoryUsage,1000);
...
var showWarning:Boolean = true;
var warningMemory:uint = 1000*1000*500;
var abortMemory:uint = 1000*1000*625;
...
function checkMemoryUsage() {
if (System.totalMemory > warningMemory && showWarning) {
// show an error to the user warning them that we're running out of memory and might quit
// 向用户显示一个错误警告他们我们内存溢出并且可能要退出了
// try to free up memory if possible
// 如果可能的话试图释放内存
showWarning = false; // so we don't show an error every second
} else if (System.totalMemory > abortMemory) {
// save current user data to an LSO for recovery later?
// 将用户数据保存到LSO中以在以后恢复?
abort();
}
}
function abort() {
// send the user to a page explaining what happpened:
// 发送给用户一个页面解释发生了什么:
navigateToURL(new URLRequest("memoryError.html"));
}
很明显以上代码还有很多方式可以增强,但是希望该代码能够演示该处理背后的基本概念。
注意到总内存(totalMemory)是单一进程中的共享值是重要的。一个单一进程可能只是一个浏览器窗口,或者所有打开的浏览器窗口,视乎于浏览器、操作系统以及该窗口是符合打开的。例如,在Mac OS X中,所有的Safari浏览器窗口共享一个单一的进程和总内存(totalMemory)值。而在Microsoft Windows中进程数和占用内存值就更加的费解。
弱引用
在Actionscript 3.0 中其中一个我真的很高兴看到的特性是弱引用的实现。它可以描述为不被垃圾收集器计算以决定某一对象是否可以被收集的对对象的引用。如果某一对象剩余的唯一的引用是弱引用的话,那们该对象将在垃圾收集器的下次运行时被移除。
不幸的是,弱引用只在两种情况下支持。第一是事件监听器——这太伟大了因为事件监听器是导致垃圾收集问题的最常见引用之一。我强烈的推荐您总是在监听器上使用弱引用。要做到这点,要在调用addEventListener时给第五个参数传递true,如下所示:
someObj.addEventListener("eventName",listenerFunction,useCapture,priority,weakReference);
stage.addEventListener(Event.CLICK,handleClick,false,0,true);
// the reference back to handleClick (and this object) will be weak.
// 到 handleClick(和该对象)的引用将是弱的。
关于本章节要更多了解,请阅读我blog上的关于弱引用监听的文章。
Actionscript 3.0还在字典对象(Dictionary object)中支持弱引用。只需要在实例一个新的字典事向第一个参数传递true即可让它使用弱引用作为它的关键字。如下所示:
var dict:Dictionary = new Dictionary(true);
dict[myObj] = myOtherObj;
// the reference to myObj is weak, the reference to myOtherObj is strong
// 指向myObj的引用是弱的,指向myOtherObj的引用是强的。
从这里通往何方
资源管理是Actionscript 3.0 开发的重要部分。忽略本文中描述的问题可能的结果就是迟缓的内容(应用),此外,也有潜在的完全拖垮用户系统的风险。现在再也没有任何方法可以直接将显示对象从内存中移除并且停止它的代码执行——这意味着在某一应用中Flash开发人员有责任在对象不再需要使用的时候将其完全的清理妥当。
虽然Actionscript 3.0实质上提高了开发人员在他们应用中管理资源必须做的工作量,但是在Flash Player 9提供了新的工具来帮助管理内存的使用。将这些新工具和有效的策略和方法配对起来(关于该主题,请查看本文的姊妹篇:理解Flash Player 9垃圾收集)可以使得您在即将到来的Flash和Flex工程中成功的管理资源。
更多信息,请务必访问Flash开发人员中心和Flash Player开发人员中心。