Effective J2ME(2)
本文节选于笔者在数年前开发手机游戏时总结的一份文档。一家之言,贻笑大方。2 J2ME开发技巧
2.1 性能
??? 在模拟器上可以使用JProbe Profiler等分析工具来分析性能瓶颈。但是需要注意的是模拟器的瓶颈与实机中的瓶颈可能存在差别。2.1.1 设计可重用的对象
??? 在Java程序中,对象的创建是个不小的开销。同时过多的无用对象也会导致耗时的垃圾回收。因此在程序中,每次编码类似 mBullet=null;的语句的时候,都要考虑一下这个对象是否可以被重用。推荐使用设计模式之一的工厂模式来控制对象的创建。例如在雷鸟号游戏的子弹类(在整个战斗场景中,子弹需要不停地被创建和销毁)的设计中,其构造函数被定义为私有,同时提供一个公共的静态方法public static Bullet newInstance(…);来创建子弹的实例。这样就可以对子弹实例的创建进行有效的控制。子弹类也定义了方法public void destroy();来控制子弹实例的销毁。destroy();方法并不是简单的将实例引用赋值成null,而是将这个实例保存到一个缓冲(程序中称之为回收站)里,当程序需要创建新的子弹实例的时候,newInstance方法首先检查回收站是否保存有使用过的子弹实例,如果没有就调用子弹类的构造函数创建一个新的实例;如果有,就给这个实例的成员重新赋值。如果需要,也可以将类设计为不可变的,这样做的好处之一就是该类的实例是线程安全的。总之,不要在程序里不必要的丢弃任何对象。2.1.2 减少线程间的切换次数
??? 在J2ME游戏的开发中,推荐使用单线程驱动的结构。由于Java在语言层面上提供了线程级的支持,所以相对而言,使用Java语言可以更容易地开发出多线程应用。但是由于线程切换通常是一个代价高昂地操作,所以在设计阶段要尽量避免不必要的线程切换。例如线程间的同步通信,如果一个线程以同步的方式与另外一个线程通信,那么第一个线程在发出消息之后,调度程序就必须让第一个线程进入休眠状态(在单处理器的情况下),同时唤醒第二个线程处理消息。因此大量的线程间同步通信对程序的性能有很大的冲击。在J2ME游戏的开发中,单线程驱动的结构通常可以达到良好的效果,这样的结构既减少了线程切换的次数,又避免的棘手的线程间同步的问题。如果需要实现特殊的功能,可以通过定制特定的定时器(Timer)的方法解决。2.1.3 减少基本数据类型间的强制类型转换
??? 由于J2ME设备的运行时内存有限,所以J2ME应用都倾向于的使用更精确的数据类型来描述数据。例如如果可以用byte型来定义变量就不使用int型。但是这样做通常会导致程序中有大量的基本数据类型之间的强制转换。就像永远不要过早优化一样,这种对更精确的数据类型的使用也不是灵丹妙药,应该分析程序的具体特点。通常应该对那些在程序中使用比较频繁而且使用量较大的对象进行优化。例如如果在程序里需要创建500个Building对象,那么就可能需要对Building类进行优化,将Building的成员变量定义为更准确的数据类型。在一般情况下,还是应该使用缺省int型。这样会大大减少基本数据类型间的强制类型转换对程序性能的冲击。2.1.4 尽量减少重画的次数,如果可能,采用局部重画技术
??? 就我个人的经验而言,在J2ME游戏中,背景图的刷新占用了很大一部分CPU资源。特别是在那种用图块(Tile)拼接出整个背景的程序(比如BomberMan和荒岛探宝游戏)中,这个现象比较明显。例如在128×128的屏幕上,如果用12×12的图块拼接出整个背景,那么需要做11×11=121次绘图操作。如果采用全屏刷新的策略,那么代价就比较可观了。常用的解决方案是:1、如果背景图不可变,例如雷鸟号游戏(背景图只是循环向下移动,其内容不变),那么强烈推荐采用双缓冲(只是双缓冲背景图)技术。具体做法就是将图块绘制到一张内存图片中。在每次重画背景时,将这张内存图片绘制到屏幕上,而不是用每次都用图块拼接出背景。如果内存情况允许的话,采用这种做法会显著地提升性能;2、如果背景图可变,仍然可以采用双缓冲技术,但是会比较复杂。程序必须能够识别脏图块(即已经改变而需要重画的图块),如果不能的话,就失去了双缓冲的意义。