Flash Platform的性能优化——节省内存
Flash Platform的性能优化——节省内存
2012-08-30 17:24:13| 分类: FlashPlayer底层|举报|字号 订阅
显示对象
ActionScript 3.0 包含很多显示对象。要限制内存用量,最简单的优化技巧之一是使用合适类型的显示对象。对于非交互式简单形状,请使用 Shape 对象。对于不需要时间轴的交互式对象,请使用 Sprite 对象。对于使用时间轴的动画,请使用 MovieClip 对象。应始终为应用程序选择最有效的对象类型。
以下代码显示不同显示对象的内存使用量:
trace(getSize(new Shape())); // output: 236 trace(getSize(new Sprite())); // output: 412 trace(getSize(new MovieClip())); // output: 440
getSize() 方法显示对象在内存中占用的字节数。可以看到,如果不需要 MovieClip 对象的功能,使用多个 MovieClip 对象(而不是使用简单的 Shape 对象)会浪费内存。
原始类型
重用对象
对象池
另一个重要优化称为对象池,涉及到不断重复使用对象。在初始化应用程序期间创建一定数量的对象并将其存储在一个池中,例如 Array 或 Vector 对象。对一个对象完成操作后,停用该对象以免它占用 CPU 资源,然后删除所有相互引用。然而,不要将引用设置为 null,这将使它符合垃圾回收条件。只需将该对象放回到池中,在需要新对象时可以对其进行检索。
重用对象可减少实例化对象的需求,而实例化对象成本很高。还可以减少垃圾回收器运行的机会,从而提高应用程序运行速度。以下代码演示对象池技术:
package { import flash.display.Sprite; public final class SpritePool { private static var MAX_VALUE:uint; private static var GROWTH_VALUE:uint; private static var counter:uint; private static var pool:Vector.<Sprite>; private static var currentSprite:Sprite; public static function initialize( maxPoolSize:uint, growthValue:uint ):void { MAX_VALUE = maxPoolSize; GROWTH_VALUE = growthValue; counter = maxPoolSize; var i:uint = maxPoolSize; pool = new Vector.<Sprite>(MAX_VALUE); while( --i > -1 ) pool[i] = new Sprite(); } public static function getSprite():Sprite { if ( counter > 0 ) return currentSprite = pool[--counter]; var i:uint = GROWTH_VALUE; while( --i > -1 ) pool.unshift ( new Sprite() ); counter = GROWTH_VALUE; return getSprite(); } public static function disposeSprite(disposedSprite:Sprite):void { pool[counter++] = disposedSprite; } } }
const MAX_SPRITES:uint = 100; const GROWTH_VALUE:uint = MAX_SPRITES >> 1; const MAX_NUM:uint = 10; SpritePool.initialize ( MAX_SPRITES, GROWTH_VALUE ); var currentSprite:Sprite; var container:Sprite = SpritePool.getSprite(); addChild ( container ); for ( var i:int = 0; i< MAX_NUM; i++ ) { for ( var j:int = 0; j< MAX_NUM; j++ ) { currentSprite = SpritePool.getSprite(); currentSprite.graphics.beginFill ( 0x990000 ); currentSprite.graphics.drawCircle ( 10, 10, 10 ); currentSprite.x = j * (currentSprite.width + 5); currentSprite.y = i * (currentSprite.width + 5); container.addChild ( currentSprite ); } }
以下代码在当单击鼠标时,将删除显示列表中的所有显示对象,并在以后的其他任务中重复使用这些对象:
stage.addEventListener ( MouseEvent.CLICK, removeDots ); function removeDots ( e:MouseEvent ):void { while (container.numChildren > 0 ) SpritePool.disposeSprite (container.removeChildAt(0) as Sprite ); }
释放内存
请记住,当对象设置为 null 时,不必将其从内存中删除。如果系统认为可用内存不是足够低,垃圾回收器可能不会运行。垃圾回收的执行时间不可预知。内存分配(而不是对象删除)会触发垃圾回收。当垃圾回收器运行时,它将查找尚未收集的对象的图形。垃圾回收器通过在这些图形中查找相互引用但应用程序不再使用的对象,从而检测出处于非活动状态的对象。将删除通过这种方式检测到的处于非活动状态的对象。
在大型应用程序中,此进程会占用大量 CPU 并影响性能,还可导致应用程序运行速度显著降低。通过尽量重复使用对象,尝试限制使用垃圾回收。此外,尽可能地将引用设置为 null,以便垃圾回收器用较少处理时间来查找对象。将垃圾回收看作一项保护措施,并始终尽可能明确地管理对象生存期。
可使用 Adobe AIR 和 Flash Player 的调试版中提供的 System.gc() 方法启动垃圾回收器。将此设置与 Adobe? Flash? Builder? 捆绑还可以手动启动垃圾回收器。通过运行垃圾回收器,可以了解应用程序的响应方式以及是否已将对象从内存中正确删除。
幸运的是,这样可以立即减少位图使用的内存量。例如,BitmapData 类包括一个 dispose() 方法。尽管 dispose() 方法可删除内存中的像素,但仍必须将引用设置为 null 才可完全释放内存。当不再需要 BitmapData 对象时,要始终调用 dispose() 方法并将引用设置为 null,以便立即释放内存。
使用位图
位图采样
为了更充分利用内存,当 Flash Player 检测到 16 位屏幕时,32 位不透明图像将缩小为 16 位图像。采样只占用一半内存资源,图像的呈现速度会更快。此功能只在适用于 Windows Mobile 的 Flash Player 10.1 中可用。
在移动设备上,可能难以区分图像是以 16 位呈现还是以 32 位呈现。对于只包含几种颜色的简单图像,检测不到区别。即使对于更复杂的图像,也很难检测到区别。但是,图像放大时可能褪色,因此从外观上看,16 位的变化趋势比 32 位明显。
BitmapData 单个引用
通过尽量重复使用实例来优化 BitmapData 类的使用,这一点非常重要。Flash Player 10.1 和 AIR 2.5 引入了一个适用于所有平台的新功能,称为 BitmapData 单个引用。从嵌入图像创建 BitmapData 实例时,将对所有 BitmapData 实例使用位图的同一版本。如果稍后修改位图,则在内存中为该位图提供其自身的唯一位图。嵌入的图像可来自库或 [Embed] 标签。
滤镜和动态位图卸载
尝试尽量减少使用滤镜效果,包括通过 Pixel Bender 在移动设备中处理的滤镜。将滤镜应用于显示对象时,运行时将在内存中创建两个位图。其中每个位图的大小与显示对象相同。将第一个位图创建为显示对象的栅格化版本,然后用于生成应用滤镜的另一个位图:
当修改滤镜的某个属性时,内存中的两个位图都将更新以创建生成的位图。此过程涉及一些 CPU 处理,这两个位图可能会占用大量内存。
Flash Player 10.1 和 AIR 2.5 在所有平台上引入一种新的滤镜行为。如果滤镜在 30 秒内没有进行修改,或者将其隐藏或置于屏幕之外,将释放未过滤的位图占用的内存。
此功能可节省所有平台上的滤镜占用的一半内存。以应用模糊滤镜后的文本对象为例。在这种情况下,文本用于简单装饰,不会进行修改。30 秒后,将释放内存中未过滤的位图。如果文本隐藏 30 秒或置于屏幕之外,将会产生同样的效果。修改滤镜的某个属性后,将重新创建内存中未过滤的位图。此功能称为动态位图卸载。即使进行了这些优化,在使用滤镜时仍要谨慎小心;在对它们进行修改时,仍要求大量 CPU 或 GPU 处理。
最佳做法是,尽可能使用通过创作工具(例如 Adobe? Photoshop?)创建的位图来模拟滤镜。避免在 ActionScript 中使用运行时创建的动态位图。使用外部创作的位图可帮助运行时减少 CPU 或 GPU 负载,特别是当滤镜属性不随时间更改时。如果可能,在创作工具中创建位图所需的任何效果。然后,可以在运行时中显示该位图,而无需对它进行任何处理,这样速度要快得多。
直接进行 mip 映射
在所有平台上,Flash Player 10.1 和 AIR 2.5 中还提供了另一个新功能,该功能与 mipmap 处理有关。Flash Player 9 和 AIR 1.0 引入了 mipmap 处理功能,可改进缩小位图的品质和性能。
例如,假设加载了一个 1024 x 1024 图像,但开发人员想对该图像进行缩放以在库中创建一个缩略图。Mip 映射功能在使用中间采样版本的位图作为纹理缩放图像时可以正确呈现该图像。运行时的早期版本在内存中创建中间缩小版本的位图。如果加载了一个 1024 x 1024 图像并以 64 x 64 显示,则运行时早期版本创建的每个位图大小只有原来的一半。例如,在这种情况下将会创建 512 x 512、256 x 256、128 x 128 和 64 x 64 位图。
Flash Player 10.1 和 AIR 2.5 当前支持直接从原始源 mipmap 处理到所需的目标大小。 在上一示例中,将仅创建 4 MB (1024 x 1024) 的原始位图和 16 KB (64 x 64) 的经过 mip 映射处理的位图。
Mip 映射逻辑同样适用于动态位图卸载功能。如果仅使用 64 x 64 的位图,则从内存中释放 4MB 的原始位图。如果必须重新创建 mip 映射,则将重新加载原始位图。另外,如果需要其他各种大小经过 mip 映射处理的位图,则使用位图的 mip 映射链来创建位图。例如,如果必须创建 1:8 位图,则会检查 1:4、1:2 和 1:1 位图以确定首先将哪个位图加载到内存中。如果找不到其他版本,则将从资源中加载 1:1 原始位图并使用该位图。
JPEG 解压缩程序可以使用自己的格式执行 mip 映射。通过直接进行 mip 映射,可将大型位图直接压缩为 mip 映射格式,而无需加载整个解压缩后的图像。生成 mip 映射的速度明显加快,并且不会为大型位图分配占用的内存然后再将其释放。JPEG 图像品质相当于常规 mip 映射技术。
使用 3D 效果
Flash Player 10 和 AIR 1.5 引入了一个 3D 引擎,允许您对显示对象应用透视转换。您可以使用 rotationX 和 rotationY 属性或 Graphics 类的 drawTriangles() 方法应用这些转换。您还可以使用 z 属性应用深度。请记住,每个经过透视转换的显示对象都将被栅格化为位图,因此需要更多内存空间。
如果您可以手动创建 3D 效果,而无需依赖本机 API,则可以减少内存使用量。不过,Flash Player 10 和 AIR 1.5 中引入的新 3D 功能更便于进行纹理映射,因为 drawTriangles() 等方法可以本机处理纹理映射。
作为开发人员,应确定要创建的 3D 效果在通过本机 API 或手动处理后是否会提供更好的性能。请考虑 ActionScript 执行和呈现性能以及内存使用量。
在 AIR 2.0.1 和 AIR 2.5 移动应用程序中,您将 renderMode 应用程序属性设置为 GPU,则 GPU 会执行 3D 转换。 但是,如果 renderMode 为 CPU,则由 CPU 而不是 GPU 执行 3D 转换。在 Flash Player 10.1 应用程序中,CPU 执行 3D 转换。
当 CPU 执行 3D 转换时,如果要对显示对象应用任何 3D 转换,则需要内存中有两个位图。一个位图用于源位图,另一个用于透视转换的版本。在这种情况下,3D 转换的工作原理与滤镜类似。因此,当 CPU 执行 3D 转换时尽量少用 3D 属性。
文本对象和内存
Flash Player 10 和 AIR 1.5 引入了一种强大的新文本引擎,即 Adobe Flash 文本引擎 (FTE),可节省系统内存。但是,FTE 是一个低级 API,要求在其上使用 flash.text.engine 包中提供的其他 ActionScript 3.0 图层。
对于只读文本,最好使用 Flash 文本引擎,它占用较少的内存并提供更好的呈现效果。对于输入文本,最好使用 TextField 对象,因为在创建典型行为(例如,输入处理和自动换行)时需要的 ActionScript 代码较少。
事件模型与回调
ActionScript 3.0 事件模型基于对象调度的概念。事件模型是面向对象的,可以进行优化以重复使用代码。dispatchEvent() 方法循环访问侦听器列表,并对各个注册的对象调用事件处理函数方法。然而,事件模型的一个缺点是可能要在应用程序的生存期内创建许多对象。
假设您必须从时间轴调度一个事件来指示动画序列的末尾。要实现此通知,您可以从时间轴中的特定帧调度一个事件,如以下代码所示:
dispatchEvent( new Event ( Event.COMPLETE ) );
Document 类可以使用以下代码行侦听此事件:
addEventListener( Event.COMPLETE, onAnimationComplete );
虽然此方法是正确的,但使用本机事件模型与使用传统的回调函数相比,速度更慢且占用的内存更多。必须创建 Event 对象并为其分配内存,而这会降低性能。例如,当侦听 Event.ENTER_FRAME 事件时,将在各个帧上为事件处理函数创建一个新事件对象。在捕获和冒泡阶段(如果显示列表很复杂,此成本会很高),显示对象的性能可能会特别低。