Flash Platform的性能优化——节省内存 显示对象 原始类型 重用对象 对象池 释放内存 使用位图 位图采样 BitmapData 单个引用 滤镜和动态位图卸载 直接进行 mip 映射 使用 3D 效果 文本对象和内存 事件模型与回调

Flash Platform的性能优化——节省内存  

2012-08-30 17:24:13|  分类: FlashPlayer底层|订阅

节省内存对于应用程序(尤其台式机应用程序)开发一直非常重要。然而,内存的使用量对移动设备来说非常重要,因此有必要限制应用程序占用的内存量。

Flash Platform的性能优化——节省内存

显示对象

原始类型

重用对象

对象池

释放内存

使用位图

位图采样

BitmapData 单个引用

滤镜和动态位图卸载

直接进行 mip 映射

使用 3D 效果

文本对象和内存

事件模型与回调选择适当的显示对象。

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 对象)会浪费内存。


原始类型

所有原始类型(String 除外)在内存中都是占用 4 – 8 个字节。如果没有为表示 64 位值的数字分配值, ActionScript 虚拟机 (AVM) 将为其分配 8 个字节。所有其他原始类型存储时均占用 4 个字节。

使用 getSize() 方法为代码设置基准确定任务的最有效对象。

重用对象

优化内存的另一种简单方法是尽可能重复使用对象并避免重新创建对象。

对象池

Flash Platform的性能优化——节省内存

显示对象

原始类型

重用对象

对象池

释放内存

使用位图

位图采样

BitmapData 单个引用

滤镜和动态位图卸载

直接进行 mip 映射

使用 3D 效果

文本对象和内存

事件模型与回调请尽可能使用对象池。

另一个重要优化称为对象池,涉及到不断重复使用对象。在初始化应用程序期间创建一定数量的对象并将其存储在一个池中,例如 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;          }      }  }
SpritePool 类在初始化应用程序时创建新的对象池。getSprite() 方法返回这些对象的实例,而 disposeSprite() 方法释放这些实例。代码允许池容量用尽时可以增长。还可以创建固定大小的池,这样当池容量用尽时将不会分配新对象。尽可能避免在循环中创建新对象。有关详细信息,请参见释放内存。以下代码使用 SpritePool 类检索新实例:
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 );  }
注: 池矢量始终引用 Sprite 对象。如果要从内存中完全删除对象,需要对 SpritePool 类使用 dispose() 方法,从而删除所有剩余引用。

释放内存

在 Flash Player 的发行版中无法直接启动垃圾回收器。要确保将一个对象作为垃圾回收,请删除对该对象的所有引用。请记住,在 ActionScript 1.0 和 2.0 中使用的旧 delete 运算符在 ActionScript 3.0 中有不同的行为。它只能用于删除动态对象的动态属性。

注: 在 Adobe? AIR? 和 Flash Player 的调试版中可以直接调用垃圾回收器。

请记住,当对象设置为 null 时,不必将其从内存中删除。如果系统认为可用内存不是足够低,垃圾回收器可能不会运行。垃圾回收的执行时间不可预知。内存分配(而不是对象删除)会触发垃圾回收。当垃圾回收器运行时,它将查找尚未收集的对象的图形。垃圾回收器通过在这些图形中查找相互引用但应用程序不再使用的对象,从而检测出处于非活动状态的对象。将删除通过这种方式检测到的处于非活动状态的对象。

在大型应用程序中,此进程会占用大量 CPU 并影响性能,还可导致应用程序运行速度显著降低。通过尽量重复使用对象,尝试限制使用垃圾回收。此外,尽可能地将引用设置为 null,以便垃圾回收器用较少处理时间来查找对象。将垃圾回收看作一项保护措施,并始终尽可能明确地管理对象生存期。

注: 将对显示对象的引用设置为 null 不能确保冻结该对象。该对象在作为垃圾回收之前,仍将占用 CPU 周期。在将对对象的引用设置为 null 之前,先确保正确地停用对象。

可使用 Adobe AIR 和 Flash Player 的调试版中提供的 System.gc() 方法启动垃圾回收器。将此设置与 Adobe? Flash? Builder? 捆绑还可以手动启动垃圾回收器。通过运行垃圾回收器,可以了解应用程序的响应方式以及是否已将对象从内存中正确删除。

注: 如果将某个对象用作事件侦听器,则其他对象可以引用它。如果是这样,先使用 removeEventListener() 方法删除事件侦听器,然后再将引用设置为 null

幸运的是,这样可以立即减少位图使用的内存量。例如,BitmapData 类包括一个 dispose() 方法。尽管 dispose() 方法可删除内存中的像素,但仍必须将引用设置为 null 才可完全释放内存。当不再需要 BitmapData 对象时,要始终调用 dispose() 方法并将引用设置为 null,以便立即释放内存。

注: Flash Player 10.1 和 AIR 1.5.2 在 System 类中引入了一个名为 disposeXML() 的新方法。借助此方法,通过将 XML 树作为参数传递,可立即使 XML 对象可用于垃圾回收。

使用位图

使用矢量(而不是位图)是节省内存的好方法。然而,使用矢量(特别是大量矢量),会显著增加对 CPU 或 GPU 资源的需求。使用位图是优化呈现的一个好方法,因为运行时在屏幕上绘制像素比呈现矢量内容需要的处理资源要少。

位图采样

为了更充分利用内存,当 Flash Player 检测到 16 位屏幕时,32 位不透明图像将缩小为 16 位图像。采样只占用一半内存资源,图像的呈现速度会更快。此功能只在适用于 Windows Mobile 的 Flash Player 10.1 中可用。

注: 在 Flash Player 10.1 之前的版本中,在内存中创建的所有像素都以 32 位(4 个字节)存储。一个简单的 300 x 300 像素的徽标会占用 350 KB 内存 (300*300*4/1024)。使用此新功能,同样的不透明徽标仅占用 175 KB。如果徽标透明,则不会将采样缩小为 16 位,而在内存中保持原大小。此功能仅适用于嵌入位图或运行时加载的图像(PNG、GIF、JPG)。

在移动设备上,可能难以区分图像是以 16 位呈现还是以 32 位呈现。对于只包含几种颜色的简单图像,检测不到区别。即使对于更复杂的图像,也很难检测到区别。但是,图像放大时可能褪色,因此从外观上看,16 位的变化趋势比 32 位明显。


BitmapData 单个引用

通过尽量重复使用实例来优化 BitmapData 类的使用,这一点非常重要。Flash Player 10.1 和 AIR 2.5 引入了一个适用于所有平台的新功能,称为 BitmapData 单个引用。从嵌入图像创建 BitmapData 实例时,将对所有 BitmapData 实例使用位图的同一版本。如果稍后修改位图,则在内存中为该位图提供其自身的唯一位图。嵌入的图像可来自库或 [Embed] 标签。

注: 此新功能同样适用于当前内容,因为 Flash Player 10.1 和 AIR 2.5 会自动重复使用位图。
当实例化嵌入的图像时,将在内存中创建相关联的位图。在 Flash Player 10.1 和 AIR 2.5 之前的版本中,内存中为每个实例都提供了一个单独的位图

借助此方法,更新原始的源 BitmapData 对象时将自动更新其在舞台上的所有使用,这可能是一项功能强大的技术。然而,此方法不允许单独缩放各个星形

滤镜和动态位图卸载

Flash Platform的性能优化——节省内存

显示对象

原始类型

重用对象

对象池

释放内存

使用位图

位图采样

BitmapData 单个引用

滤镜和动态位图卸载

直接进行 mip 映射

使用 3D 效果

文本对象和内存

事件模型与回调避免使用滤镜,包括经过 Pixel Bender 处理的滤镜。

尝试尽量减少使用滤镜效果,包括通过 Pixel Bender 在移动设备中处理的滤镜。将滤镜应用于显示对象时,运行时将在内存中创建两个位图。其中每个位图的大小与显示对象相同。将第一个位图创建为显示对象的栅格化版本,然后用于生成应用滤镜的另一个位图:


Flash Platform的性能优化——节省内存

显示对象

原始类型

重用对象

对象池

释放内存

使用位图

位图采样

BitmapData 单个引用

滤镜和动态位图卸载

直接进行 mip 映射

使用 3D 效果

文本对象和内存

事件模型与回调

当修改滤镜的某个属性时,内存中的两个位图都将更新以创建生成的位图。此过程涉及一些 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 处理功能,可改进缩小位图的品质和性能。

注: Mip 映射功能仅适用于动态加载的图像或嵌入位图。Mip 映射不适用于经过过滤或缓存的显示对象。仅当位图的宽度和高度为偶数时才可以进行 mip 映射。当位图的宽度或高度为奇数时,mip 映射将停止。例如,可将 250 x 250 图像通过 mip 映射缩小到 125 x 125,但无法对其进行进一步的 mip 映射。在这种情况下,至少其中一个尺寸是奇数。尺寸为 2 的若干次幂的位图的效果最佳,例如,256 x 256、512 x 512、1024 x 1024 等。

例如,假设加载了一个 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 映射技术。

注: 尽量少用 mip 映射。尽管它可以改进缩小位图的品质,但它对带宽、内存和速度都有影响。在某些情况下,最好选择使用通过外部工具预缩放的位图版本,并将其导入到您的应用程序中。如果只需缩小位图,不要一开始就使用较大位图。

使用 3D 效果

Flash Platform的性能优化——节省内存

显示对象

原始类型

重用对象

对象池

释放内存

使用位图

位图采样

BitmapData 单个引用

滤镜和动态位图卸载

直接进行 mip 映射

使用 3D 效果

文本对象和内存

事件模型与回调考虑手动创建 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 Platform的性能优化——节省内存

显示对象

原始类型

重用对象

对象池

释放内存

使用位图

位图采样

BitmapData 单个引用

滤镜和动态位图卸载

直接进行 mip 映射

使用 3D 效果

文本对象和内存

事件模型与回调将 Adobe? Flash? 文本引擎用于只读文本;将 TextField 对象用于输入文本。

Flash Player 10 和 AIR 1.5 引入了一种强大的新文本引擎,即 Adobe Flash 文本引擎 (FTE),可节省系统内存。但是,FTE 是一个低级 API,要求在其上使用 flash.text.engine 包中提供的其他 ActionScript 3.0 图层。

对于只读文本,最好使用 Flash 文本引擎,它占用较少的内存并提供更好的呈现效果。对于输入文本,最好使用 TextField 对象,因为在创建典型行为(例如,输入处理和自动换行)时需要的 ActionScript 代码较少。


事件模型与回调

Flash Platform的性能优化——节省内存

显示对象

原始类型

重用对象

对象池

释放内存

使用位图

位图采样

BitmapData 单个引用

滤镜和动态位图卸载

直接进行 mip 映射

使用 3D 效果

文本对象和内存

事件模型与回调考虑使用简单的回调代替事件模型。

ActionScript 3.0 事件模型基于对象调度的概念。事件模型是面向对象的,可以进行优化以重复使用代码。dispatchEvent() 方法循环访问侦听器列表,并对各个注册的对象调用事件处理函数方法。然而,事件模型的一个缺点是可能要在应用程序的生存期内创建许多对象。

假设您必须从时间轴调度一个事件来指示动画序列的末尾。要实现此通知,您可以从时间轴中的特定帧调度一个事件,如以下代码所示:

dispatchEvent( new Event ( Event.COMPLETE ) );

Document 类可以使用以下代码行侦听此事件:

addEventListener( Event.COMPLETE, onAnimationComplete );

虽然此方法是正确的,但使用本机事件模型与使用传统的回调函数相比,速度更慢且占用的内存更多。必须创建 Event 对象并为其分配内存,而这会降低性能。例如,当侦听 Event.ENTER_FRAME 事件时,将在各个帧上为事件处理函数创建一个新事件对象。在捕获和冒泡阶段(如果显示列表很复杂,此成本会很高),显示对象的性能可能会特别低。