代码优化的目的是最大化flash程序的性能,尽可能少的占用RAM和CPU的系统资源。在本教程中,我们以制作一个占用资源的flash应用程序开始讲解,我们会逐渐在代码中应用很多优化技巧,最后完成一个高效、精简的SWF。
最后结果的预览
让我们一起看看我们到最后会面对的结果:
(图片为截图 请到原文查看效果)
“内存占用”和“CPU使用率”的统计是基于你打开的所有浏览器窗口的,包括flash横幅广告和其他类似的程序。这会使得SWF比实际本身占用更多的内存资源。
步骤一:理解Flash动画
FLASH动画有两个要素:粒子模拟火灾效果、一个随时间显示动画消耗资源的图表。图的粉红色线追踪资源消耗兆级别的动画,绿线图以百分比的形式表示CPU使用率所消耗的总内存。
ActionScript对象占用的大部分内存分配给Flash Player和更多的ActionScript对象集合,其内存消耗较高。为了保持程序的内存消耗低,在Flash Player中定期做一些面对所有的ActionScript对象和从内存中释放那些不再使用的资源的内存回收。
内存消耗图表正常情况下会表现得高低起伏,在内存回收的时刻下沉,在新的对象被创造的时候又慢慢上升。图形线仅仅走到某个点表示内存回收,它意味着新的对象将被添加到内存中,此时没有什么东西可以移除的。如果趋势继续,flash播放器会逐渐因为内存用完而崩溃。
CPU使用率是追踪动画的帧频来计算的。Flash动画的帧频比很像它的心跳。每一次心跳,flash播放器会更新一次并重新渲染屏幕上所有元素,执行所有要求的AS任务。
帧频决定了flash播放器每一次心跳的时间,因此1/10fps的帧频意味着至少每100毫秒一次心跳。如果所有的AS任务都以这个帧频执行,flash播放器在下一次心跳之前还有剩余时间等待任务完成。另一方面,如果既定的任务在特定节拍相对CPU太密集而不能以给定帧频完成,帧频就会自动下降允许一些额外的时间。一旦使用率减少,帧频就又加速,回到设定的速率。
(当当前程序窗口失去焦点或者关掉的时候flash播放器会将帧频自动下降到4fps。这样做是管用户的焦点当前在何处都能节省系统资源。)
这所有的方法实际上也就是两种类型的帧频:一种是原始设定的,希望你的动画以此频率播放。一种就是它实际上运行的帧频。我们设定的是你的目标帧频,另一种为实际帧频。
CPU使用率的图标是以实际帧频和目标帧频的比例来计算的。计算公式如下:
CPU使用率率 = (目标帧频-实际帧频)/实际帧频*100
例如:如果目标帧频是50fps,但动画实际上是以25fps运行的,CPU的使用率就是50%——即(50-25)/50*100。
请注意这不是CPU实际运行动画时的使用百分比,这只是一个实际值的估算。优化提纲已经在这里了,估算就是一个足够好的手上练习。为了获得实际的CPU使用率,要用到你的操作系统提供的工具,例如窗口任务管理器。看看我现在的任务管理器,它显示了没有优化过的动画CPU占有率是53%,而动画图表显示的CPU使用率41.7%。
请注意:本教程中的所有影片截图来自独立版本的Flash Player。该图将很有可能和你的系统上显示的数字不同,这取决于你的操作系统、浏览器、Flash Player版本。有任何其他在不同的浏览器窗口正在运行的Flash应用程序或flash播放器也可能会影响某些系统内存使用报告。当分析你的程序效能时,始终保证没有其他的Flash程序正在运行,因为它们可能会破坏你的指标。
随着CPU的使用率,预想它超过90%的时候动画屏幕就黑屏了——例如,如果您切换到另一个浏览器选项卡或向下滚动页面。较低的帧频导致它不会引起CPU密集型任务,但闪存节流下来的帧频无论每当你在看哪里。每当发生这种情况,等待几秒钟后的CPU使用率图,在正常的帧频开始工作后来使恢复到合适的CPU使用率值。
步骤二:这种代码会使我的flash看起来很“肥”吗?
下面是动画影片的源代码,它只有一个类,叫Flames,也是文档类。这个类包含了一些属性来追踪影片内存使用和CPU使用率历史,被用来画一个图表。内存和CPU使用率数据的计算和更新都是在Flames.getStates() 这个函数中,并且图表是由Flames.drawGraph()逐帧绘制的,为了创造火焰效果,Flames.createParticles()这个函数首先每秒生成几百个粒子,粒子都被存储在fireParticles这个数组里。这个数组在Flames.drawParticles()中不断循环,用粒子属性创造效果。
花一点时间研究一下Flames这个类。你已经意识到任何迅速的变化在优化程序上还有很长的路要走吗?
- Package com.pjtops{
- Import flash.display.MovieClip;
- Import flash.events.Event;
- Import fl.motion.Color;
- Import flash.geom.Point;
- Import flash.geom.Rectangle;
- Import flash.text.TextField;
- Import flash.system.System;
- Import flash.utils.getTimer;
- Public class Flames extends MovieClip{
- Private var memory Log = newArray(); // stores System.totalMemory values for display in the graph
- Private var memory Max = 0; // the highest value of System.totalMemory recorded so far
- Private var memory Min = 0; // the lowest value of System.totalMemory recorded so far
- Private var memory Color; // the color used by text displaying memory info
- Private var ticks = 0; // counts the number of times getStats() is called before the next frame rate value is set
- Private var frameRate = 0; //the original frame rate value as set in Adobe Flash
- Private var cpuLog = newArray(); // stores cpu values for display in the graph
- Private var cpuMax = 0; // the highest cpu value recorded so far
- Private var cpuMin = 0; // the lowest cpu value recorded so far
- Private var cpuColor; // the color used by text displaying cpu
- Private var cpu; // the current calculated cpu use
- Private var lastUpdate = 0; // the last time the framerate was calculated
- Private var sampleSize = 30; // the length of memoryLog & cpuLog
- Private var graphHeight;
- Private var graphWidth;
- Private var fireParticles = new Array(); // stores all active flame particles
- Private var fireMC = new MovieClip(); // the canvas for drawing the flames
- Private var palette = new Array(); // stores all available colors for the flame particles
- Private var anchors = new Array(); // stores horizontal points along fireMC which act like magnets to the particles
- Private var frame; // the movieclips bounding box
- // class constructor. Set up all the events, timers and objects
- Public functionFlames() {
- addChildAt( fireMC, 1);
- frame = new Rectangle( 2, 2, stage.stageWidth - 2, stage.stageHeight - 2);
- var colWidth = Math.floor( frame.width / 10);
- for( var i = 0; i < 10; i++ ){
- anchors[i] = Math.floor( i * colWidth );
- }
- SetPalette();
- memoryColor = memoryTF.textColor;
- cpuColor = cpuTF.textColor;
- graphHeight = graphMC.height;
- graphWidth = graphMC.width;
- frameRate = stage.frameRate;
- addEventListener( Event.ENTER_FRAME, drawParticles );
- addEventListener( Event.ENTER_FRAME, getStats );
- addEventListener( Event.ENTER_FRAME, drawGraph );
- }
- //creates a collection of colors for the flame particles, and stores them in palette
- Private function setPalette(){
- Var black = 0x000000;
- Var blue = 0x0000FF;
- Var red = 0xFF0000;
- Var orange = 0xFF7F00;
- Var yellow = 0xFFFF00;
- Var white = 0xFFFFFF;
- palettepalette = palette.concat( getColorRange( black, blue, 10) );
- palettepalette = palette.concat( getColorRange( blue, red, 30) );
- palettepalette = palette.concat( getColorRange( red, orange, 20) );
- palettepalette = palette.concat( getColorRange( orange, yellow, 20) );
- palettepalette = palette.concat( getColorRange( yellow, white, 20) );
- }
- //returns a collection of colors, made from different mixes of color1 and color2
- Private function getColorRange( color1, color2, steps){
- Var output = new Array();
- for( var i = 0; i < steps; i++ ){
- var progress = i / steps;
- var color = Color.interpolateColor( color1, color2, progress );
- output.push( color );
- }
- Return output;
- }
- // calculates statistics for the current state of the application, in terms of memory used and the cpu %
- Private function getStats( event ){
- ticks++;
- var now = getTimer();
- if( now - lastUpdate < 1000){
- return;
- }else{
- lastUpdate = now;
- }
- cpu = 100- ticks / frameRate * 100;
- cpuLog.push( cpu );
- ticks = 0;
- cpucpuTF.text = cpu.toFixed(1) + '%';
- if( cpu > cpuMax ){
- cpucpuMax = cpu;
- cpuMaxTF.text = cpuTF.text;
- }
- if( cpu < cpuMin || cpuMin == 0){
- cpucpuMin = cpu;
- cpuMinTF.text = cpuTF.text;
- }
- Var memory = System.totalMemory / 1000000;
- memoryLog.push( memory );
- memoryTF.text = String( memory.toFixed(1) ) + 'mb';
- if( memory > memoryMax ){
- memorymemoryMax = memory;
- memoryMaxTF.text = memoryTF.text;
- }
- if( memory < memoryMin || memoryMin == 0){
- memorymemoryMin = memory;
- memoryMinTF.text = memoryTF.text;
- }
- }
- //render's a graph on screen, that shows trends in the applications frame rate and memory consumption
- Private function drawGraph( event ){
- graphMC.graphics.clear();
- var ypoint, xpoint;
- var logSize = memoryLog.length;
- if( logSize > sampleSize ){
- memoryLog.shift();
- cpuLog.shift();
- logSize = sampleSize;
- }
- Var widthRatio = graphMC.width / logSize;
- graphMC.graphics.lineStyle( 3, memoryColor, 0.9);
- var memoryRange = memoryMax - memoryMin;
- for( var i = 0; i < memoryLog.length; i++ ){
- ypoint = ( memoryLog[i] - memoryMin ) / memoryRange * graphHeight;
- xpoint = (i / sampleSize) * graphWidth;
- if( i == 0){
- graphMC.graphics.moveTo( xpoint, -ypoint );
- continue;
- }
- graphMC.graphics.lineTo( xpoint, -ypoint );
- }
- graphMC.graphics.lineStyle( 3, cpuColor, 0.9);
- for( var j = 0; j < cpuLog.length; j++ ){
- ypoint = cpuLog[j] / 100* graphHeight;
- xpoint = ( j / sampleSize ) * graphWidth;
- if( j == 0){
- graphMC.graphics.moveTo( xpoint, -ypoint );
- continue;
- }
- graphMC.graphics.lineTo( xpoint, -ypoint );
- }
- }
- //renders each flame particle and updates it's values
- Private function drawParticles( event ) {
- createParticles( 20);
- fireMC.graphics.clear();
- for( vari infireParticles ) {
- varparticle = fireParticles[i];
- if(particle.life == 0) {
- delete( fireParticles[i] );
- continue;
- }
- Var size = Math.floor( particle.size * particle.life/100);
- Var color = palette[ particle.life ];
- Var transperency = 0.3;
- if( size < 3){
- size *= 3;
- color = 0x333333;
- particle.x += Math.random() * 8- 4;
- particle.y -= 2;
- transperency = 0.2;
- }else{
- particle.y = frame.bottom - ( 100- particle.life );
- if( particle.life > 90){
- size *= 1.5;
- }elseif( particle.life > 45){
- particle.x += Math.floor( Math.random() * 6- 3);
- size *= 1.2;
- }else{
- transperency = 0.1;
- size *= 0.3;
- particle.x += Math.floor( Math.random() * 4- 2);
- }
- }
- fireMC.graphics.lineStyle( 5, color, 0.1);
- fireMC.graphics.beginFill( color, transperency );
- fireMC.graphics.drawCircle( particle.x, particle.y, size );
- fireMC.graphics.endFill();
- particle.life--;
- }
- }
- //generates flame particle objects
- Private function createParticles( count ){
- Var anchorPoint = 0;
- for(var i = 0; i < count; i++){
- var particle = newObject();
- particle.x = Math.floor( Math.random() * frame.width / 10) + anchors[anchorPoint];
- particle.y = frame.bottom;
- particle.life = 70+ Math.floor( Math.random() * 30);
- particle.size = 5+ Math.floor( Math.random() * 10);
- fireParticles.push( particle );
- if(particle.size > 12){
- particle.size = 10;
- }
- particle.anchor = anchors[anchorPoint] + Math.floor( Math.random() * 5);
- anchorPoint = (anchorPoint == 9)? 0: anchorPoint + 1;
- }
- }
- }
- }
- 复制代码
这要学习的太多了,没关系——在接下来的导课中我们会做一些不一样的改进。
步骤三:对所有数据变量使用强制类型
第一个我们要改动的一类就是所有声明的数据类型变量、函数参数和函数返回值。例如:对它进行改动:Protect var memoryLog = new Array();Protected var memoryMax = 0;改成这样:Protected var memoryLog:Array = new array();Protected var memoryMax:Number = 0; 当声明变量时指定数据类型会使得flash编译器在生成SWF文件时进行一些额外的优化,仅仅这样就已经能使得优化得到很大的提升了,我们会看着例子来说。另一个强类型的好处就是编译器能够捕捉到而且警告你数据类型相关的一些错误。
步骤四:验证结果
这个屏幕截图显示了在应用强类型后新的Flash动画。我们看到这对当前CPU使用率和最大值没有影响,但是最小值却已经从8.2%降到了4.2%。最大内存消耗已经从9MB降到了8.7MB。图表内存线的斜坡也发生变化了,和第二部相比它仍然有锯齿形的形状,但是以更低的速率在上升和下跌。这是好事情,如果你考虑到在内存消耗上的突然下降会是flash播放器内存回收的结果,它会在配置的内存将被用光的情况下触发。自从flash播放器不得不逆向检查所有对象来看是否有无用的东西继续占用着内存,内存回收会是高代价的操作。它进行得越少越好。
步骤五:高效存储数据
AS提供了三种数据类型:Number,uint和int。在这三种数据类型中,Number消耗了最多的内存,因为它能存储比其它两种更大的数据值。这也是唯一的能存储小数类型的数据类型。 Flames类有很多数据属性,它们都使用了Number数据类型。在不必使用小数的情况下使用Int和uint比起来能比Number节省更多的内存。
- for( var i:Number= 0; i < 10; i++ ){
- anchors[i] = Math.floor( i * colWidth );
- }
- 复制代码
改成:
- for( vari:int= 0; i < 10; i++ ){
- anchors[i] = Math.floor( i * colWidth );
- }
- 复制代码
Cpu、cpuMax和memoryMax会保持Number类型,因为它们存储的是小数数据,但memoryColor cpuColor 和ticks可以改成uint类型,因为它们经常存储的是正整数。