Flash Player 会以SWF内容的帧频速度来刷新需要变化的内容,而这个刷新的过程,我们通常称为“重绘(redraw)”,相信即便是初级的菜鸟也知道,只要使用的是Debug版本的Flash Player, 右键菜单里就会有“Show Redraw Regions (显示重绘区域)” 这个选项,当此选项打开的时候,我们就能清楚地看到此刻场景内被重绘的区域。
那么什么情况下会发生重绘呢??
1、最常见的是情况就是舞台上的可视组件在形状、位置、状态(alpha, scale…)发生改变的时候会触发Flash Player 的重绘。
2、当一个DisplayObject的层级(ChildIndex)发生改变的时候也会导致重绘。
3、当你将一个Sprite / MovieClip 的buttonMode 设置为 true 的时候,即便是单帧动画,重绘会在MouseEvent.MOUSE_DOWN的时候触发。又或者你对DisplayObject设置层级的时候,即便DisplayObject的层级没有发生改变,也会使得 Flash Player对此显示对象进行重绘。
既然重绘是为了能够将显示内容进行更新,那么一个Flash应用程序就不可避免的要触发重绘。而重绘却是性能消耗的主要根源,一个有经验的Flash开发人员写出的Flash应用,其性能可能70% - 90%(甚至更高)是消耗在重绘上,那么提高Flash应用程序的运行效率和减少重绘有着莫大的关系。
要减少重绘,首先我们需要对单位每帧重绘大小这个概念进行量化:重绘的大小应该取决于数量和面积。数量取决(但不是完全取决)于可视范围内的需要更新的显示对象的数量,假设场景上有两个闪烁的小圆点并且宽和高都为20,那么重绘的数量为3,而重绘的总面积就为: 20 * 20 * 3 = 1200 (平方像素)。
?
简单的看看上面的公式是不是觉得重绘面积的计算很容易呢?那么继续估计下下面这个情况的重绘面积是多少:
如果你计算的结果是: 20 * 20 * 6 = 2400 (平方像素),那么恭喜你,答错了…
根据上面的介绍,重绘的大小理应就是重绘的数量 * 单个区域的面积,可是为什么说这个情况就错呢?让我们看看问题究竟出在什么地方:
让人觉得奇怪的地方出现了,重绘区域的数量依旧是3,而面积却增大了不少,按照图上给出的坐标信息,我们不难算出,总重绘面积的大小为:
20 * (70 – 20 + 20) * 3 = 4200 (平方像素),比起预先估算的 2400 (平方像素)整整大了 75%!
或许从这个地方开始,大家就开始觉得困惑了。的确,Flash Player的重绘面积并没有按照我们之前的设想那样来计算,但是依旧能找到一些规律,仔细来看以下几种况:
如果你够细心,应该不难看出每次 Flash Player 重绘的区域不会超过3个,即便舞台上有多于三个的显示对象需要被重绘,Flash Player 会将其中的两个或者多个集合(根据位置来判断)在一起,然后重绘在一个大区域里面,至此,我们暂时可以将上面两个重绘法则命名为三区域法则和就近合并法则,通过总结出来的这两个法则,我们就能更加容易地理解Flash Player 重绘的机制,以及解释在日常调试中遇到的一些重绘现象了。比如QQ牧场里几个靠得比较近得小动物被放在一个区域里面重绘,而有些动物则不然,被单独重绘。
当我们了解了重绘机制后,那么接下来就应该进一步去了解如何避免多余的重绘,下面列举的方法可能大部分都是被大家所熟悉的。
1、当一个带有动画效果的DisplayObject在不显示的时候,不仅仅是将其 visible 属性设置为 false, 因为重绘依旧会进行,这里你可以选择暂停掉动画,又或者利用removeChild(displayObject) 直接将此对象移除出显示列表。
2、不出现在可视范围内外观变化的显示对象其实是不会发生重绘的,这点相信是Flash本身做了优化,也就是说我的Stage.stageWidth 和 Stage.stageHeight 都为200的情况下,一个处在舞台上的变化的显示物体,坐标为(200, 200) 宽高都为10,此时Flash Player重绘内容并不会包括此对象,新版本的Flash Player 甚至在浏览器窗口最小化的时候会关闭掉所有的重绘,这个时候你往往会发现CPU占用率骤降,但是应用程序依旧在运行。
3、在设置DisplayObject的层级的时候请先做一个判断:
if (myContainer.getChildIndex(myChild) != 0) { myContainer.setChildIndex(myChild, 0); }
运气好的话,这个判断最高能带来200%以上的效率提升。
4、当你的Sprite / MovieClip 设置 cacheAsBitmap = true 这个属性的时候,当此显示对象内很小的一个区域(甚至是被遮盖着的物体)发生变化,会导致整个Sprite / MovieClip重绘。
5、尽量确保活动的显示内容在非可见区域被暂停活动或者干脆直接移除出显示列表,这里提到的不可视区域不仅仅是舞台外不可见的,还包括舞台内被其他物体遮盖住的显示对象。
正如之前提到的,重绘是Flash Player性能消耗的主要大户,所以去优化减小重绘区域面积,减少不必要的重绘操作次数,往往能够带来比较大的性能优化回报。
补遗:
Aone
另外几种触发渲染的情况为:
1、在mouseChild = true的情况下,鼠标移动中从A对象移动到B对象上时,显示列表中已有满足触发渲染条件的变动时会立即触发渲染。
2、所有的键盘事件触发时,显示列表中已有满足触发渲染条件的变动时会立即触发渲染。
3、开启debug模式中的显示重绘区域时,显示列表中已有满足触发渲染条件的变动时会立即触发渲染(这个请无视吧-_-)。
而且以上3个情况都会无视FP可视范围内的BitmapData中已经lock()的对象强制unlock()。
另外补充一点,其实重绘区域合并并不可怕。因为重绘区域面积最多也就是整屏大小,真正产生高消耗的是在重绘区域内的可渲染目标数量。这也是为什么用位图引擎能提高显示效率的的一大原因。控制渲染容器的数量才是重点。
举两个例子:
A:500*500尺寸的Bitmap,每帧随机刷色,那么每帧渲染面积为 500*500
var d:BitmapData = new BitmapData(500,500,true,0)
addChild(new Bitmap(d))
addEventListener(Event.ENTER_FRAME,test)
function test(e:Event):void{
d.fillRect(d.rect,uint.MAX_VALUE * Math.random());
}
B:5000个 1*1大小的shape重叠每帧X坐标移动1,每帧渲染面积为1*1。
var list:Array=new Array ;
for (var i:int =0; i<5000; i++) {
var b:Shape=new Shape ;
b.graphics.beginFill(uint.MAX_VALUE * Math.random());
b.graphics.drawRect(0,0,1,1);
b.graphics.endFill();
b.x=10;
b.y=10;
addChild(b);
list[i]=b;
}
addEventListener(Event.ENTER_FRAME,test);
function test(e:Event):void {
var max:int=list.length;
for (var i:int=0; i<max; i+=1) {
var target:Shape=list[i];
target.x += 1;
}
}
WXSR
此文所提只能段对作者所观察到的现象的感性认识为实.
就源文的这句话:
"如果你够细心,应该不难看出每次 Flash Player 重绘的区域不会超过3个,即便舞台上有多于三个的显示对象需要被重绘,Flash Player 会将其中的两个或者多个集合(根据位置来判断)在一起,然后重绘在一个大区域里面,至此,我们暂时可以将上面两个重绘法则命名为三区域法则和就近合并法则,通过总结出来的这两个法则,我们就能更加容易地理解Flash Player 重绘的机制,以及解释在日常调试中遇到的一些重绘现象了"
Flash Player 重绘的区域就真的不会超过3个? 实为错也,或许在适量素材的重绘上你可能有幸的总结出上述现象,但再位图素材的重绘下我可以很确定的告诉的,绝非只有3个n个都会有可能出现,但你的素材都为位图类型时,就我观察说见是以矩形相交的方式合并的.而非作者所说的自由3个区域.
再说的就是尽管对于适量型渲染,3个区域的显像可能很常见,但他的大小更多的跟距离关系不大,相应的他的重绘面积跟素材所在图层关系更加密切.像一个容器如果放入3个或者多个适量素材mc,尽管他们相隔较远,但还是按正块重绘的.