存储技术
在写程序初期由于是面向midp2.0的手机而且不是游戏的开发,所以没有对性能方面做太多的考虑,大胆的使用了很多vector数组(使用起来方便:))等等,而且程序写的也是随心所欲,似乎不是在手机上开发j2me的东西。结果让我大跌眼镜,没办法只能重新优化,这期间可想而知浪费了很多时间,也使开发效率降低到了最低。说到这,只有一句话奉劝大家,不管你是开发什么样手机程序,一定要把性能放在第一位,不要重蹈我的覆辙。
我的几个vector存放的是链接的内容,文本框的内容,图片的内容和普通文本的内容,每个vector中包含的其他信息还有它所占的行数,它的位置和它的标志。我用线程控制paint(只要目的是实现持续按键的机制),这样的话,每次重绘都要把所有内容重新画到屏幕上,而且还要对当前焦点进行重绘。在重绘的过程中每一次都要遍历一遍几乎所有的vector然后重绘,这样肯定是浪费资源。结果也一样,在稍微低端一点手机上响应明显的慢。优化这样程序,我首先想到的就是把vector用别的方法替换掉,正常的性能使用是字符串-〉一维数组-〉二维数组-》vector。一维数组肯定是替代不了我的vector了,那只能是使用二维数组了。费了好大劲把vector转换位二维数组,发现性能上并没有提升多少。大概只节省了20k左右的内存,只能再想别的办法。这里简单说下在手机上获得当前内存和剩余内存的方式:
longtempMin=1000000;
longtemp=Runtime.getRuntime().freeMemory();
if(tempMin》temp){
tempMin=temp;
}
g.drawString(total=+Runtime.getRuntime().totalMemory(),10,10,0);
g.drawString(frees=+temp,10,10+lineHeight,0);
g.drawString(hiegh=+tempMin,10,10+2*lineHeight,0);
把这些东西画到你的画布上,在真机上运行的时候方便你查看内存的使用率。(每种机器的内存最大值是不同的)。
而且可以查看内存峰值。当然你也可以使用wtk自带的内存和方法查看器来判断。
既然转换vector不能解决问题,那就继续优化。内存的主要使用看来是在每次重画的时候产生的,这样就要减少每次重画的内容,我采取的方式是把所有固定要重画的东西用双缓冲的方式画到一张图片上,这样只有初始化的时候才去遍历数组,经过一次的遍历把所有内容画到一张图片上,然后每一次的重画都是在重画一张图片。经过这样的处理性能上有了很大的提升。高兴还为时尚早,nokia的低端机器没有问题了,结果在moto的机器上出了问题,根本就不能初始化,也就是说创建那张图片的时候就应用程序错误了。继续查找原因,结果发现是因为moto的机器不支持创建一张那么大图片。也就是说你创建一张大图的时候,在moto的机器上根本就不能申请到内存。找到原因后,把大图分割为两个比较小的图,ok没有问题了。当然至于moto支持创建多大的图片可能每种机器不同吧,只能在需要的时候自己测试了。对于浏览器,一张分割为两张需要做些代码的处理。实际上为了性能的更加优化可以把一张大图分割为多张小图,每张小图的大小可以根据屏幕的几倍大小确定也可以根据机型固定。初始化的时候也可以先初始化一部分图。因人而异。
至此,程序的主要瓶颈已经找到,并且解决。正应了一句话,程序是花80%的时间在执行20%的代码。也就是说我们要把主要精力放在那20%的代码的优化上,但实际中我发现对另外80%代码的优化也很重要,主要是一些编程细节上的处理。在细节的代码书写上多注意些也有利于程序性能的提升。关于细节上觉得以下几个方面对程序性能的提升很有帮助。
首先系统垃圾回收的利用:关于堆内存(heap)与栈内存(stack)我们知道,heap存放的是对象实例与变量;而stack存放的是静态方法。堆内存在JVM启动的时候被创建,堆内存中所存储的对象可以被JVM自动回收。在这里,要手动把不用对象置为null,特别是较大的对象,如果不用一定要记得置为空。比如说较大的数组,vector或者是image对象。(切忌)在这里,浏览器中页面图片的读取我是采用的是后台读取,即先显示文字部分,而后后台读取页面中的图片,读取完成后再一起重新显示。重新显示的时候要重新构建那个双缓冲图片,而我当时就忘记了把原来创建的那个双缓冲图片置为null了,走了很多弯路才解决问题。所以要切忌至少把大的对象置空,不要指望垃圾回收。
其次是static的使用:静态变量在程序运行期间内存空间对所有该类的对象实例而言是共享的,即只在内存中保存一份拷贝,这样节约了不比要的内存开销。但是static生命周期较长,而且不容易被垃圾回收机制所回收,所以要合理运用,不要适得其反。建议在全部具备下列条件的情况下尽量使用静态变量:
1),变量所包含的对象体积较大,占用内存较多。
2),变量所包含的对象生命周期较长。
3),变量所包含的对象数据稳定。
4),该类的对象实例有对该变量所包含的对象的共享需求。
在我的程序中对静态变量的优化后,使程序占用内存量至少提升了5k-10k。所以也不容忽视。
还有就是String类相关的东西:1。字符串累加的时候一定要用StringBuffer的append方法,不要使用+操作符连接两个字符串。差别很大。而且在循环或某些重复执行的动作中不要去创建String对象,因为String对象是要用StringBuffer对象来处理的,一个String对象应该是产生了3个对象(大概是这样:))。
2,字符串length()方法来取得字符串长度的时候不要把length放到循环中,可以在循环外面对其取值。(包括vector的size方法)。特别是循环次数多的时候,尽量把length放到循环外面。
intsize=xmlVector.size();
for(inti=2;i
。。。
}
在程序中我曾经误写了这样一句:if(i=5){。。。},编译器没有报错,而且结果好像是把i的值改变了,没有记清,大家有兴趣可以测验下。书写上尽量认真,否则查找bug的时候可能会折腾死人的。
对代码进行优化的最简单办法就是首先不要调用这些代码。这并不是说要删除这些代码,也许可以用其他办法来调用它们(后者事实减少对它的调用)。游戏的主循环 是游戏运行的最主要做的事情,应该更多地考虑是否可以不用或减少对属于这个区域内的代码的调用。在前一片文章中介绍了方法和内存评测工具的使用,但它们只 能帮助我们找出究竟是哪段代码降低了程序的运行速度,下面的内容是参考了其他资料整理出来的优化代码的方法。
代码优化的技术大致分为两个主要方面:高级优化,从使用的整体算法和结构出发进行的优化;低级优化,集中于孤立的代码片断(通常为方法中的代码)的优化。下面分别讨论两方面的优化:
1, 感觉到就是真实
对于电影来说,我们通过摄像头看到的都是完美的,而在拍摄现场我们看到的却是木头,泡沫和胶带。所以对于电影来说,感觉到就是真实。
游戏也一样,只需要处理游戏需要的东西。在游戏开发的各个方面这都是实用的。把精力集中在使游戏有趣和完美运行的问题上,始终只做需要做的而丢弃其他的部分。
2, 不要创建对象
减少对象创建的总数量和频率,结果能够大大地提高游戏的性能。还必须小心在不经意的情况下产生 String对象。
例如 :graphics.drawString( 0,0,”Score:” +score );
这一句代码会在每次被调用的时候产生一个新的 String对象,在这里就是每一桢画面显示时都会产生新的 String对象。因此最好是只是在分数改变的时候才构造这个 String。
3, 绘制屏幕
通常,在对游戏完成大量的优化工作以后,收获的将是一个大量时间耗费在屏幕绘图上的游戏。这是因为一个游戏的主要时耗大都集中在绘制图像的工作上(或其他的一些基本的绘图调用)。因此,如果一开始就可以避免绘制工作,那将是对游戏的很好的优化。
还有就是要减少屏幕绘制,循环检测屏幕图像是否在某个部分发生了改变,如果没有,就不要对那部分的屏幕进行更新。另一个方法就是增加绘制图像的尺寸来减少单独的绘制调用的次数。
4, 算法
最好的,也是使用最多的高级优化是对游戏的算法方面。
1, 提前绘制复杂图像
我们已经知道,使用 LCDUI绘制图像是很慢的,因此最好是能够避免这种绘制。其中的一个方法就是用一个预生成图像来减少复杂图像的绘制。进一步来讲,举例:将所有的游戏状态信息整合到一个面板中(得分,生命数,能量值等),然后对这些信息进行一次性同时更新。
2, 保持类和内存之间的平衡
产生新的类会增加 JAR包文件的大小,因此应该尽量避免。有的时候增加了额外类的开销可能节省了额外的内存开销,这也是值得的。
3, 复杂值的预计算
节省运算的一个好方法就是对数值进行预运算,从而无需再调用大开销的计算方法。一个很好的例子就是:主窗口画布的高度和宽度就是很好的缓存对象。例如:可以调用 getHeight方法和 getWidth方法一次,然后将它们的结果缓存起来,而不是在每一次绘图中都调用这两种方法。
4, 使用数组
在任何时候,只要可能,都应该使用数组而不是 Vector,因为数组的运行速度更快。通常面临的唯一问题是,如果最初分配的数组空间不够大,将需要对数组的大小进行扩充。这可以做到,但它需要对整个数组进行重建。例如:
Public final static int[ ] expandArray(int [] oldArray, int expandBy)
{
int [ ] newArray = new int [oldArray.length + expandBy];
System.arraycopy(oldArray, 0, newArray, 0, oldArray.length);
Return newArray;
}
任何时候,都应该尽量使用一维数组。访问二维数组变量的速度只有访问一维数组变量的一半。当然,仍然可以访问二维数组的对象,只是需要加入一点点计算。例如,与其使用这条语句:
world[y][x] = 0;
不如下面这条语句运行的快:
world[y*tilesWide + x] = 0;
这条语句通过行列的位置将数值转换成一维值,实现了对数组同一元素的访问。
5, 不要使用数组
尽管数组的访问比 Vector快,但仍然比直接访问变量要慢,因此如果可能就应该删除对数组的访问,或者为一些常用方法中的数组寻求其他能提高性能的办法。
6, 使用快速方法
并不是所有 Java调用的方法在性能上都是相同的,方法声明方式的不同对性能会会产生很多的影响。可以使用的最快的方法类型是静态方法,因此应该尽可能多地将代码置于静态调用方法中。运行速度仅次于静态方法的是声明为 final的方法。运行最慢的两种方法是在接口中定义的方法和用关键字 synchronized声明的方法,必须尽可能地避免使用这些类型的方法。
7, 其他优化
1)异常处理非常缓慢,不要为一半的游戏逻辑使用异常,只用它们来报告真正的错误状态。
2)使用 switch表达式比使用 if条件语句块的速度要快。
3)尽可能避免使用 String对象进行运算,使用 StringBuffer。
4)内嵌类的运行很慢,尽可能避免使用。
5)在完成一个引用的使用后将它设为 null。
6)不要浪费时间来将一个对象初始化为 null或 0, java虚拟机会替我们完成这样的初始化
7)多思考新方法,这会使我们的大脑运转的更快。
8)如果可能,尽量使用 static,它们运行都很快。它同时适用于方法和域,这条规则就是,如果它可以是静态的,那么就把它声明为静态的。
9)避免类型转换。
10)程序优化的时候要有所取舍,要多捉摸了。大家有什么好的建议?一起来壮大吧。
共同探讨: zxhwolfe@hotmail.com
附:关于j2me程序的调试
使用eclipseME+WTK2.1进行J2ME应用程序调试(debug)-- 引用mingjava的帖子。
选择windows-》preferences-》java-》debug 不要选择suspend execution的前面两个选项,在debuger timeout选项中 第一个时间至少设置为15000ms 这样就可以调试了。
《script src=“http://partner.googleadservices.com/gampad/google_service.js” type=“text/javascript”》《/script》 《script type=“text/javascript”》《!-- try { GS_googleAddAdSenseService(“ca-pub-4210569241504288”); GS_googleEnableAllServices(); } catch (e) { } // --》《/script》 《script src=“http://partner.googleadservices.com/gampad/google_ads.js”》《/script》 《script type=“text/javascript”》《!-- try { GA_googleAddSlot(“ca-pub-4210569241504288”, “cnblogs_commentbox_up”); GA_googleAddSlot(“ca-pub-4210569241504288”, “cnblogs_blogpost_bottom”); } catch (e) { } // --》《/script》 《script type=“text/javascript”》《!-- try { GA_googleFetchAds(); } catch (e) { } // --》《/script》
全部0条评论
快来发表一下你的评论吧 !