十一月's profileNOVEMBREBlogListsGuestbookMore Tools Help

Blog


    March 25

    JAVA汉化教程

    一:JAVA游戏分析
          大家从网上下载的一般都是JAR格式的游戏.下载后用WINRAR打开.解压后JAVA游戏一般包括几个文件.例如我汉化的超级玛丽豪华汉化版


       COM文件夹里主要存放各公司的文件.网上流传用COM包移植NOKIA的游戏.就是说这个COM包.
       icons文件夹里面存放的就是图标文件.图标文件用PNG格式存放.
       META-INF文件夹主要存放MANIFEST.MF文件.要改游戏文件名为中文名就是修改MANIFEST.MF文件.
        level2.map等文件就是地图文件.一般不用进行修改.不必理会.
       lang文件就是语言包.一般游戏文字资源主要存放于lang.×.class.*.dat还有*.txt文件
        其中如果文字资源包含在lang.和*.txt最方便汉化.因为汉化lang用工具可以直接修改.*.txt可以用记事本打开修改.
       还有就是游戏的图片文件了一般以PNG图片文件为主.



    二:汉化工具
       我常用的JAVA汉化工具有四个.分别是hhclass.UEDIT32.Hex2Text.CrPNG-2这四个工具.
       一.hhclass主要是用来汉化class.语言包和文件名的.这款软件一般的文字资源都能找到.但是有一个缺陷.大部分比较长的文字资源.
    大于250文字都不能找到.例如有游戏的帮助文字
       二.UEDIT32.这个大家都熟悉.经典的十六进制编辑工具.用来完美汉化JAVA最合适不过了.我平时主要用来修改一些hhclass不能搜索到的文字资源.
       三.第三个是Hex2Text.这是小海开发的十六进制辅助工具.主要是用来转换编码.由于JAVA是采用UTF-8编码的.要汉化就必须文本转换到UTF8再转换到16进制
       四.第四个是CrPNG-2.主要用来提取PNG格式图像的.由于JAVA体积小.游戏中使用的图片格式都是PNG格式.要实现完美汉化就必须连一些图片资源也汉化.这 个工具就能快速的对资源文件搜索出PNG图片.不过这软件我还未成功搜索一次图片.最后一个软件CrPNG-2也是小海开发的.主要是用于内部交流.我跟小海要了个免注册版.汉化组有需要的兄弟可以跟我要.其他兄弟如果对汉化JAVA有兴趣可以用GOOGLE搜索一下


    三.使用工具汉化.
       一.进行汉化前先装好模拟器.不然一变一变从手机上测试就很麻烦了.我使用的模拟器是midpx_java..这个模拟器能模拟95%的JAVA游戏.安装后开始汉化.
       二.从下载英文版游戏下载英文版游戏.下载后用WINRAR解压
    然后使用hhclass工具汉化.打开hhclass后选择打开语言包.打开超级玛丽豪华版文件夹的lang文件.打开后会看到界面上显示



    这些文件就是游戏中的文字资源.装好了JAVA模拟器系统会自动关联jar和jad格式.双击超级玛丽豪华版的jar文件就会弹出模拟器的窗口.


    这就是电脑上模拟出来的效果.看到了游戏的英文文字了吗?我们一起把它消灭掉...


    接下来切换窗框到hhclass.看看这些英文单词就是游戏中的文字.只要简单的把它改为中文就可以了.例如点击"sound off"在下面的白色对话框
    输入“关闭声音"然后点击替换.再点击保存文件.



    然后我们用右键点击超级玛丽豪华版.JAR.弹出菜单”打开为“选择WINRAR打开方式.用WINRAR打开.打开后.在WINRAR软件界面中.点击”添加-选择刚汉化的LANG文件-点击确定.这样我们刚刚汉化的LANG文件也就添加进超级玛丽豪华版.JAR文件去了.或者你可以用WINRAR打开超级玛丽豪华版.然后直接把刚才汉化的LANG文件拖进压缩包里.具体步骤我就不再多说了.

    接下来把全部英文资源汉化完.我们要测试一下汉化了没有.直接双击打开超级玛丽豪华版.JAR.弹出模拟器窗口.看一下效果.

    对于中文名的汉化.我们只要用hhclass工具.选择“打开MANIFEST”打开刚超级玛丽豪华版文件夹中的META-INF文件夹.再打开MANIFEST.MF文件你就会看到:

    手机SIM内部资料介绍

    什么是SIM卡

    移动电话机与SIM卡共同构成移动通信终端设备。无论是GSM系统还是CDMA系统,数字移动电话机用户在“入网”时会得到一张SIM卡。SIM卡 是(Subscriber Identity Model客户识别模块)的缩写 ,也称为智能卡、用户身份识别卡, GSM数字移动电话机必须装上此卡方能使用。

    SIM卡就是一个在内部包含有大规模集成电路的卡片,卡片内部存储了数字移动电话客户的信息、加密密钥等内容,它可供GSM网络对客户身份进行鉴别,并对客户通话时的语音信息进行加密。SIM卡的使用,完全防止了并机和通话被窃听行为,并且SIM卡的制作是严格按照GSM国际标准和规范来完成的,它使客户的正常通信得到了可靠的保障。现在的数字电话都是必须要安装SIM卡之后才可以使用,如果不安装的话,那么后果相信也就也不用我多说了。在没有安装SIM卡的情况下,我们仅仅只能拨打像119、112这种紧急电话的号码。

    SIM卡在GSM系统中的应用,使得卡和手机分离,一张SIM卡唯一标识一个客户。一张SIM卡可以插入任何一部GSM手机中使用,而使用手机所产生的通信费用则自动记录在该SIM卡所唯一标识的客户的帐户上

    PIN、PUK码介绍
    我们在使用手机时,会接触到5种密码 :SIM卡的PIN、PIN2、PUK、PUK2和手机密码。前四种初始密码都是SIM卡供应商移动、联通提供的,手机密码是手机生产商提供的。它们之间的关系如下:

    1、PIN码(即PIN1码)就是SIM卡的个人识别密码 ,一般在修改前原始密码是1234,如果不是就不要再试了,打1860/1001咨询。打开开机PIN码,刚每次开机后就要输入PIN码!如果输入三次错误,需要用PUK码 解锁,PUK码 由移动、联通提供,如果输入十次错误会导致SIM卡烧毁,所以有问题不要自己随便猜测密码 ,马上找移动、联通。

       2、PIN2码是设定手机计费时使用的,如果输入三次错误需要用 PUK 2码解锁。目前移动、联通都不提供此项功能支持,即使PIN2密码锁死也不会影响手机正常使用。

       3、PIN码连续输入10次都是错误的话就会锁卡要求用PUK码 来解开,而PUK码的输入机会只有3次,3次都输错的话,SIM卡将会给永久锁死,即报废了。

       4、PUK码,不管你使用的是全球通还是神州行,网络服务商那里都有资料保存,一旦需要输入时,可以致电相应的服务热线来查询,先核对用户资料就行了。这些密码设定及更改都在菜单-其他设定-安全设定中。

        忘记PIN码可以用PUK码来解密,PUK密码一般不向用户提供,但某些SIM卡除外,比如神州行的用户就随卡提供PUK。如果你的SIM卡的PUK没有随卡提供,你可以到当地的网络运营商营业厅要求解锁,一般是免费的。

    SIM外观
    在实际使用中有两种功能相同而形式不同的SIM卡:卡片式(俗称大卡)SIM卡,这种形式的SIM卡符合有关IC卡的ISO7816标准,类似IC卡;嵌入式(俗称小卡)SIM卡,其大小只有25mm×15mm,是半永久性地装入到移动台设备中的卡。

    “大卡”上真正起作用的是它上面的那张“小卡”,而“小卡”上起作用的部分则是卡面上的铜制接口及其内部胶封的卡内逻辑电路。目前国内流行样式是“小卡”,小卡也可以换成“大卡”(需加装一卡托)。“大卡”和“小卡”分别适用于不同类型的GSM移动电话,早期机型如摩托罗拉GC87C、308C等手机用的是“大卡”,而目前新出的机型基本上都使用“小卡”

    在SIM卡的背面有以五个一排,被排成四排的一组数字,在这组数字最前面的六位数字所代表的是中国的代号,就像从国外打电话到国内都需要先拨打86一样。第七位数字则代表的是接入号码,如果是5的话,那么这张SIM卡的电话号码前三位就是135的,而如果是6的话,则代表其前三位数字为136,其它的也都以此类推。第八位数字代表的是该SIM卡的功能位,一般情况下显示的数字为0。第九和第十位数字代表了该SIM卡所处的省份。至于第十一和第十二位数字则代表的是该SIM卡的年号,而第十三位数字则是SIM卡供应商的代码。从第十四位开始至第十九位数字则代表了该SIM卡的用户识别码。最后一个数字是校验位。

    什么是Ki、IMEI、IMSI
    国际移动设备识别码(IMEI:International Mobile Equipment Identification Number)是区别移动设备的标志,储存在移动设备中,可用于监控被窃或无效的移动设备。IMEI组成如下图所示,移动终端设备通过键入“*#06#”即可查得。其总长为15位,每位数字仅使用0~9的数字。其中TAC代表型号装配码,由欧洲型号标准中心分配;FAC代表装配厂家号码;SNR为产品序号,用于区别同一个TAC和FAC中的每台移动设备;SP是备用编码。

    国际移动用户识别码(IMSI:International Mobile Subscriber Identification Number)是区别移动用户的标志,储存在SIM卡中,可用于区别移动用户的有效信息。IMSI组成如下图所示,其总长度不超过15位,同样使用0~9的数字。 其中MCC是移动用户所属国家代号,占3位数字,中国的MCC规定为460;MNC是移动网号码,最多由两位数字组成,用于识别移动用户所归属的移动通信网;MSIN是移动用户识别码,用以识别某一移动通信网中的移动用户。

    Ki (Key identifier)是SIM卡与运营商之间加密数据传递的密钥。GSM的加密方式是一种称为comp-128的数字加密运算,当系统进行验证时会同时使用Ki及IMSI,经过一连串系统安全认证讯息后产生随机变量,并以A3算法进行加密运算与手机内存资料进行比对,当身份确认无误后始可入网。目前GSM使用的Ki长度是16 bytes,相当于128bits,若非经过特殊译码程序,使用者无法读取Ki,安全性极高,使用者无须担心有被盗打电话的顾虑。

    由此看来,只要知道SIM卡的Ki、IMSI值,我们就可以通过软件仿真出SIM卡的功能,甚至可以利用多组Ki、IMSI值,用一张微处理器卡片来同时仿真本来需要多张SIM所完成的功能,这就是“一卡多号”技术。

    SIM卡的软件特性
    SIM卡采用新式单片机及存储器管理结构,因此处理功能大大增强。其智能特性的逻辑结构是树型结构。全部特性参数信息都是用数据字段方式表达,SIM卡中存有3类数据信息:

    1. 与持卡者相关的信息以及SIM卡将来准备提供的所有业务信息,这种类型的数据存储在根目录下;

    2. GSM应用中特有的信息,这种类型的数据存储在GSM目录下;

    3.   GSM应用所使用的信息,此信息可与其他电信应用或业务共享,位于电信目录下。

    在SIM卡根目录下有3个应用目录,一个属于行政主管部门应用目录,另外两个属于技术管理的应用目录,分别是GSM应用目录和电信应用目录。所有的目录下均为数据字段,有二进制的和格式化的数据字段。数据字段中的信息有的是永存性的即不能更新的,有的是暂存的需要更新的。每个数据字段都要表达出它的用途、更新程度、数据字段的特性。

    SIM卡内部的数据
    了解完SIM卡的大概之后,我们再来看看SIM卡具体都能存储哪些类型的数据。以目前的情况来看,SIM卡能够存储的数据类型主要被分为以下四种:

    1. 由SIM卡生产厂商存入的系统原始数据

    2. 存储手机的固定信息,手机在出售之前都会被SIM卡中心记录到SIM卡当中,主要包括鉴权和加密信息、国际移动用户识别码(IMSI)、IMSI认证算法、加密密匙生成算法、密匙生成前,用户密匙的生成算法(这三种算法均为128位)

    3. 用户自己存入的数据,如短消息、固定拨号、缩位拨号、性能参数、话费记数等;能够存储有关的电话号码,也就是具备电话簿功能。

    4. 有关于网络方面的数据,用户在用卡过程中自动存入和更新的网络接续和用户信息类数据,包括最近一次位置登记时手机所在位置识别号、设置的周期性位置更新间隔时间、临时移动用户号等。不过这种数据的存放是暂时性的,也就是说它并不是永久的存放于SIM卡之中。

      5.相关的业务代码,这一点相信也是大家很熟悉的,那就是非常重要的个人识别码(也就使我们平常所说的PIN码),还有就  是解开锁定用的解锁码(PUK)等等。

    以上四种类型的数据都是存储在SIM卡当中的,而我们通常也是可以利用这些数据来进行手机的设置,每张SIM卡个人密码(PIN)都是可以由用户设置,利用加密的功能可以实现防止手机被其它人所盗用甚至被窃听,由此看来SIM卡不仅仅可以为我们提供打电话的功能,而且还为我们保护自己的隐私而提供了安全的保障。

    SIM卡内部的数据都存放在各自的目录项内,第一类数据放在根目录,当电源开启后首先进入根目录,再根据指令进入相关的子目录,每种目录及其内部的数据域均有各自的识别码保护,只有经过核对判别以后才能对数据域中的数据进行查询、读出和更新。上面第一类数据通常属于永久性数据,由SIM卡生产厂商注入以后无法更改,第二类数据只有网络运行部门的专门机构才允许查阅和更新,第三、四类数据中的大部分允许用户利用手机对其进行读写操作。

    SIM卡的类型
    SIM卡的存储容量有3kB、8kB、16kB、32kB、64kB等。STK卡(SIM application Tool Kit)是SIM卡的一种,它能为手机提供增值服务,如移动梦网业务等。SIM卡能够储存多少电话号码和短信取决于卡内数据存储器EEPROM的容量(有2KB、3KB、8KB容量),假设一张EEPROM容量为8KB的SIM卡,可储存以下容量的数据:100组电话号码及其对应姓名、15组短信息、25组最近拨出的号码、4位SIM卡密码(PIN)。目前中国移动/中国联通实际对普通用户提供的多数是普通8K的SIM卡。

    SIM卡的接口
         SIM卡是通过卡面上铜制接口来连接卡内逻辑电路与移动终端的,SIM卡芯片有8个触点,通常与移动设备连接需要6个触点。

    SIM卡是一个装有微处理器(CPU)的芯片卡,它的内部有5个模块,并且每个模块都对应一个功能:微处理器CPU(8位)、程序存储器ROM(3~8kbit)、工作存储器RAM(6~16kbit)数据存储器EEPROM(16~256kbit)和串行通信单元。这5个模块被胶封在SIM卡铜制接口后与普通IC卡封装方式相同。这5个模块必须集成在一块集成电路中,否则其安全性会受到威胁,因为芯片间的连线可能成为非法存取和盗用SIM卡的重要线索。

    SIM卡的供电分为5V(1998年前发行)、5V与3V兼容、3V、1.8V等,当然这些卡必须与相应的手机配合使用,即手机产生的SIM卡供电电压与该SIM卡所需的电压相匹配。SIM卡插入手机后,电源端口提供电源给SIM卡内各模块。

    手机开发平台指南、教程和资料介绍

    近来无事,把手机开发平台的学习指南、教程和资料总结一下,方便大家参考。
    手机客户端软件开发最大的困难就是平台不统一,手机开发平台太多。
    手机可分为智能手机开发和feather phone手机。开发平台可分为开放式平台和封闭式平台,开放式平台包括symbian、windows mobile、linux、Android、BlackBerry、j2me、brew等,支持手机应用程序通过OTA下载和安装;封闭式平台包括MTK、展讯、TI、飞利浦等。下面分别介绍。
    1. Symbian
    Symbian平台为目前智能手机市场的老大,智能手机全球市场占70%以上,在欧洲和亚洲占绝对优势,只是在美国市场份额少得可怜,希望今年nokia在美国发力,赶上其它智能手机。根据UI风格的不同,Symbian分为s60和UIQ两个平台,其中nokia使用s60平台,索爱和moto采用UIQ平台。Symbian平台由于发展时间较长,又是市场老大,目前中文资料也较多,不像几年前刚接触时只有英文资料。以下书籍值得推荐:
    SYMBIAN OS软件开发开发--应用C++开发智能手机应用程序入门》(其中UI部分为UIQ平台),
    《Series 60 应用程序开发》(以s60平台为主,重点推荐)
    Symbian OS C++手机应用开发 (第2卷)》
    另外今年将出来一本有关uiq3.0的新书,值得期待。
    关于symbian的开发网站和论坛:
    诺基亚论坛:最好的symbian论坛,
    http://discussion.forum.nokia.com/forum/forumdisplay.php?f=6,其中还有中文论坛。
    UIQ官方论坛:http://developer.uiq.com/
    Symbian公司中文论坛:http://developer.symbian.com/forum/forum.jspa?forumID=37
    NewLC网站:http://www.newlc.com/
    索爱uiq官方论坛:http://developer.sonyericsson.com/category.jspa?categoryID=3
    索爱uiq中文论坛http://developer.sonyericsson.com/forum.jspa?forumID=133

    2.windows mobile:
    智能手机全球市场中windows mobile占12%左右市场份额。开发与windows平台类似,所以熟悉windows开发的能很快上手。目前没有较好的针对windows mobile的书,经典书籍《Windows 程序设计(第5版)》对于开发win32程序依然是最好的,经典的书就是牛啊!虽然pocket pc支持MFC,但smartphone不支持,所以为了更好的移植,用win32开发较好。由本书做基础,再参考windows mobile的sdk以及示例代码,应该能很快上手。
    关于windows mobile的开发网站和论坛:
    Windows mobile中文社区:http://www.winbile.net/BBS/
    Pocket pc forum:http://www.windowsmobiledn.com/forum/
    微软官方网站:http://www.microsoft.com/windowsmobile/developers/default.mspx

    3. linux:
    智能手机全球市场中linux手机仅占可怜的4.4%市场份额。原来moto还出过不少linux手机,如A768、A1200、E680、E2、E6、V8等,07年8月份还信誓旦旦预测60%手机采用Linux OS,但moto后来回购了UIQ的股份,又与微软合作,还加入了google的开放手机联盟,看来要抛弃自己的linux平台了。另外,moto的策略是linux+j2me的模式,虽然采用linux os,但并不对外开放linux的sdk,只有和moto合作的厂家才能获得native linux sdk,所以导致目前linux手机开发的资料和论坛较少。虽然有民间linux高手破解了moto的linux的sdk,可以开发native linux的程序,但native linux的程序不能直接安装在moto的linux手机上,还必须安装一个插件,这对普通用户又是难以跨越的一个门槛。所以个人觉得native linux手机的开发将走向末路。大部分linux手机的ui开发都是基于QT,关于QT开发,可参考《C++ GUI Qt3编程》、《精通Qt4编程》,如果有moto官方的sdk,参考API文档以及一些示例代码,将可以较快上手,如果没有官方的sdk,那就要话很长时间hack了。目前没有很好的关于linux手机开发的论坛,个人推荐陈罡的博客http://www.cublog.cn/u/26691/,内有moto a1200开发随笔,对于熟悉和了解手机native linux开发有很大帮助。

    4. MTK:
    MTK最近几年异军突起,近70%的国产手机采用MTK的芯片和平台,黑手机更几乎是MTK的代名词。国内厂家只有夏新没有采用MTK的方案。MTK的模式名为“Turn-key”的全面解决方案,厂商采用了这个方案,只需要加一个手机外壳即可成品——这能大大降低了出货时间,一般厂家只修改界面、铃声以及增加一些应用软件。有关MTK平台的介绍,见环球企业家杂志的文章“国产手机操纵者联发科的秘密”http://www.cnbeta.com/articles/45580.htm
    MTK是私有平台,目前没有书籍介绍MTK平台,所以只能从网上查找MTK的资料。以下资料大家可以从网上获得,对于了解MTK很有帮助:《mtk 训练课程.pdf》、《MMI Platform Source Code Training.pdf》主要是MMI界面开发介绍、《MMI Resource & Customization Tool.pdf》MMI资源工具介绍。
    最近,MTK平台MMI设计牛人FUGUI自己编写了一本有关mtk平台MMI开发的实例教程,书名为《MMI实例培训教程》(本书大家通过Google下载到),此书深入浅出,全面的介绍了MTK平台MMI开发的各个方面,通过本书,开发人员将能很快进入MTK平台开发领域,实为MTK平台MMI开发的圣经!MTK平台的操作系统为nucleus,有关nucleus的介绍网上有一篇文章《Nucleus实时操作系统分析报告》。MTK平台主要用C语言开发,所以要熟悉MTK开发,首先必须熟悉掌握C语言,MTK的sdk与vc6集成,MMI的各控件和窗口之间通过回调函数实现通讯。
    关于MTK的开发网站和论坛:
    我爱研发网:http://www.52rd.com/ 上面几篇文章和书籍都能从本网站找到。
    手机研发论坛:http://www.1mp.cc/bbs/index.asp


    5. 展讯:
    展讯平台07年也发展很快,利用MTK下半年PA放大器缺货的机会,趁势而入,抢走mtk很多市场份额。展讯的开发模式和MTK的很类似,基本也是给厂家提供整体解决方案,与MTK的差别见此链接http://www.chinabyte.com/telecom/267/2507267.shtml。国内主要是夏新、联想、文泰等采用展讯平台。展讯平台采用的嵌入式操作系统是threadx,关于threadx的有一本中文书《嵌入式实时操作系统的多线程计算:基于ThreadX和ARM》。同MTK平台一样,为封闭平台,开发语言为C,开发环境为vc6, MMI的各控件和窗口之间与windows类似通过消息进制实现通讯。另外也没有书籍介绍展讯平台,以下资料值得推荐:
    《Spreadtrum_SAP.ppt》
    《MMK_Kernel软件API接口说明书.doc》
    《MMK_Window_Table开发说明书.doc》
    《展讯平台MMI窗口开发说明书.pdf》
    《展讯手机平台软件简介.pdf》
    关于展讯的开发网站和论坛:
    我爱研发网:http://www.52rd.com/ 上面几篇文章都能从本网站找到。
    手机研发论坛:http://www.1mp.cc/bbs/index.asp

    6. J2ME:
    J2ME平台为手机上运用最广泛的开放式平台,绝大部分手机均已经支持J2ME了。关于J2ME的书籍数不胜数,个人推荐以下书籍:
    《j2me技术手册》
    《J2ME开发大全》
    《J2ME移动应用程序开发》
    关于J2ME的开发网站和论坛:
    J2ME开发网:http://www.j2medev.com/Index.html
    中国Java手机网:http://www.cnjm.net/
    以及各手机厂商的官方java论坛

    7. Brew
    Brew 的全称是无线二进制运行时环境。Brew平台是高通公司开发的,从无线应用程序开发、设备配置、应用程序分发以及计费和支付的完整端到端解决方案中的无线应用程序开发部分。目前绝大部分CDMA手机都支持Brew平台。学习Brew平台,首先需要熟悉c语言。学习步骤和方法可参考” BREW高手之路-解析BREW学习过程”。
    学习书籍:
    《深入BREW手机游戏开发》
    《BREW 技术开发与应用》
    这些书虽然不够深入,但对于入门还是可以参考的。
    关于Brew的开发网站和论坛:
    Brew官方论坛:http://brewforums.qualcomm.com/index.php

    8. Blackberry,Android,iPhone
    黑莓公司BlackBerry手机和Google的Android手机均只支持java开发。苹果的iPhone不支持第3方开发,只能开发网页,据说将来开放sdk,值得期待。
    BlackBerry的开发网站和论坛:
    BlackBerry开发者指南:http://www.cnblogs.com/confach/category/36451.htmlBlackBerry官方网站:http://na.blackberry.com/eng/developers/community.jsphttp://bbs.maxpda.com/forum-184-1.html
    Android的开发网站和论坛:
    http://www.androiddev.net.cn/bbs/
    http://www.androidin.com/http://www.androidcn.net/
    iPhone的开发网站和论坛:
    一群苹果fans成立的网站:http://apple4.us/
    http://bbs.maxpda.com/forum-190-1.html
    http://lordhong.javaeye.com/blog/153315
    http://lordhong.javaeye.com/blog/152774

    9. 其它Feather phone平台
    其它feather phone平台,如TI、飞利浦、英飞凌(Infineon)、飞思卡尔(Freescale)、Broadcom、Skyworks等平台,由于平台私有,并且不提供第三方sdk,所以也没有相关资料和文档,希望有了解的朋友介绍一下。
    March 23

    J2ME 2D小游戏入门之周边工具类


    完善周边工具类(图象、GameObject、Font)

      虽然我们有了midp2.0的支持,但是有时还是需要一些辅助工具,方便我们使用。这怕是在进行真正的游戏设计之前最有趣的了。

      1、首先是一个ImageTools工具类,提供一个方法帮助调用Image



    public class ImageTools {
     protected ImageTools() {}

     public static Image getImage(String str){
      Image img=null;
      try {
       img = Image.createImage(str);
      }
      catch (Exception ex) {
       System.out.println(ex);
      }
      finally{
       return img;
      }
     }
    }




      2.GameObject,提供一个通用的游戏对象。

      有了Sprite类,为什么还要GameObject呢?其实我们一般是将Sprite,看作成一个高级的Image,往往一个Sprite要被多个 游戏对象调用,GameObject其实就是Sprite的状态类。GameObject提供简单的生命周期概念,动画更新速度;



    public class GameObject {
     public Sprite sprite;//内置的Sprite
     public boolean alive;//存活标记
     private int lifecount=0;//生命周期计数器
     public int lifetime=0;//生命周期,以桢为单位
     public int speed=0;//动画桢更新速度,(0至无穷,0代表每一桢跟新一个画面)
     private int animcount=0;// /动画桢更新计数器
     public GameObject(Image img,int width,int height){
      sprite=new Sprite(img,width,height);
      reset();
     }
     public void move(int dx,int dy){//相对移动
      sprite.move(dx,dy);
     }

     public void moveto(int x,int y){//绝对移动
      sprite.setPosition(x,y);
     }

     public void update(){//更新状态,动画桢更新,生命周期更新
      if(!alive)
       return;
      if(++animcount>speed){
       animcount=0;
       sprite.nextFrame();
       if(lifetime!=0 && ++lifecount>lifetime)
        alive=false;
      }
     }

     public void paint(Graphics g){//Paint
      if(!alive)
       return;
       sprite.paint(g);
     }

     public void reset(){//复位
      alive=true;
      lifecount=0;
      animcount=0;
      sprite.setFrame(0);
     }
    }





      3.封装字体类,你需要漂亮的字体吗?

      我们经常需要用图片来输出文字,一个方便的字体类是必须的。我们希望仅仅提供一个图片,一个图片所描述的字符的数组,来初始化一个字体类。字体类提供一个类似Textout的方法,方便得在一个位置输出信息。先封装一个简单的版本,只支持英文和数字,并且输出不能自动换行。 可能你有一个简单的思路,就是简单的保存字符数组,当打印的时候遍历数组,来查找每个字符在sprite的frameseq中的index,但当我们打印 一个字符串的时候就会发现,太多的遍历操作耽误了宝贵时间,这里我们使用一个小技巧用容量换取速度,我们知道Character. hashCode()可以返回字符的ascii编码,常用字符将返回1-127;利用这一点,我们开辟一个128的数组charhash,将输入的字符c所在图片index存入charhash[c. hashCode()]中。以后也用这种映射方法来读取字符。charhash的元素初值为-1,以后只要数值大于0就是有效值。



    public class Font {
     Sprite sprite; //Sprite
     int width,height; //每个char的尺寸
     int[] charhash; //储存1-127个常见字符在sprite的frameseq中的位置
     Graphics g;
     public Font(Graphics g,Image img, int width, int height, char[] chars) {
      this.g=g;
      sprite=new Sprite(img,width,height);
      this.width=width;
      this.height=height;
      charhash=new int[128];
      for (int i = 0; i < charhash.length; i++) {
       charhash=-1;//没有代表此字符的图片
      }
      Character c;
      for (int i = 0; i < chars.length; i++) {
       c=new Character(chars);
       charhash[c.hashCode()]=i;
      }
     }
    public void drawChar(char ch, int x, int y){
     Character c=new Character(ch);
     int hashcode=c.hashCode();
     sprite.setPosition(x,y);
     if(hashcode>=0){
      sprite.setFrame(charhash[hashcode]);
      sprite.paint(g);
     }
    }

    public void drawString(String str, int x, int y){
     int length=str.length();
     for (int i = 0; i < length; i++) {
      drawChar(str.charAt(i),x+width*i,y);
     }
    }

    }




      这样只要有一个实例font,就可以调用font.drawString(“hello”,0,0);

      在0,0位置输出漂亮的图片字符串。怎么样还挺好使的吧。

    备份 资源


    J2MEGAME网站上有很多资源 包括 图片 声音 工具

    以下是其中的游戏制作工具区的网址 里面有很多编辑器 好东西呀 大笑
    http://www.j2megame.cn/forumdisplay.php?fid=36

    一个音乐播放的工具类

    //引入媒体包
    import javax.microedition.media.*;
    //引入控制包
    import javax.microedition.media.control.*;
    //读取资源使用
    import java.io.*;

    public class LPAudioPlayer {
            /** 媒体接口 */
            private Player player;

            /** 播放的媒体文件名 */
            private String filename;

            /** 播放的媒体格式 */
            private String format;

            /**
             * 媒体播放类构造方法
             *
             * @param filename传入媒体的文件名
             * @param format文件格式
             * @param isLoad是否立刻装载媒体
             */
            public LPAudioPlayer(String filename, String format, boolean isLoad) {
                    this.format = format;
                    this.filename = filename;
                    if (isLoad) {
                            loadResource();
                    }
            }

            /**
             * 媒体构造方法
             *
             * @param filename文件名称
             * @param format文件格式
             */
            public LPAudioPlayer(String filename, String format) {
                    this.format = format;
                    this.filename = filename;
            }

            /**
             * 装载媒体资源
             *
             */
            public void loadResource() {
                    try {
                            // 根据文件名称创建流对象
                            InputStream is = getClass().getResourceAsStream("/" + filename);
                            // 创建播放器接口
                            player = Manager.createPlayer(is, format);
                    } catch (IOException e) {
                            System.out.println("can't load " + filename);
                            System.out.println(e);
                    } catch (MediaException e) {
                            System.out.println("can't create audio");
                            System.out.println(e);
                    }
            }

            /**
             * 设置循环播放
             *
             */
            public void setLoop() {
                    if (player != null) {
                            player.setLoopCount(-1);
                    }
            }

            /**
             * 设置音量
             *
             * @param level音量大小
             */
            public void setVolume(int level) {
                    if (player != null) {
                            VolumeControl control = (VolumeControl) player
                                            .getControl("VolumeControl");
                            control.setLevel(level);
                    }
            }

            /**
             * 停止播放
             *
             */
            public void stop() {
                    if (player != null) {
                            try {
                                    player.stop();
                            } catch (MediaException e) {
                                    System.out.println("can't stop audio");
                                    System.out.println(e);
                            }
                    }
            }

            /**
             * 播放方法
             *
             */
            public void play() {
                    if (player != null) {
                            try {
                                    // 获得播放信息
                                    player.realize();
                                    // 获取稀有资源
                                    player.prefetch();
                                    // 开始播放
                                    player.start();
                            } catch (MediaException e) {
                                    System.out.println("can't play audio");
                                    System.out.println(e);
                            }
                    }
            }

            /**
             * 重新播放声音文件
             *
             */
            public void replay() {
                    // 关闭当前声音接口
                    close();
                    // 进行垃圾回收
                    System.gc();
                    loadResource();
                    play();
            }

            public void close() {
                    if (player != null) {
                            player.close();
                            player = null;
                    }
            }
    }


    使用方法

    // 创建播放声音对象
    LPAudioPlayer mid = new LPAudioPlayer("back.mid", "audio/midi", true);
    // 设置循环播放
    mid.setLoop();
    // 播放音乐
    mid.play();

    转]查询手机支持的所有声音、视频文件格式的小代码


    网上抓来的,也许有些朋友能用得到。

    import javax.microedition.lcdui.*;
    import javax.microedition.midlet.*;
    import javax.microedition.media.*;

    public class MediaInformationMIDlet extends MIDlet implements CommandListener {
    private Form mInformationForm;
    public void startApp() {
    if (mInformationForm == null) {
    mInformationForm =
    new Form("Content types and protocols");
    String[] contentTypes =
    Manager.getSupportedContentTypes(null);
    for (int i = 0; i < contentTypes.length; i++) {
    String[] protocols =
    Manager.getSupportedProtocols(contentTypes[i]);
    for (int j = 0; j < protocols.length; j++) {
    StringItem si = new StringItem(contentTypes[i] + ": ",
    protocols[j]);
    //si.setLayout(Item.LAYOUT_NEWLINE_AFTER);
    mInformationForm.append(si);
    }
    }
    Command exitCommand = new Command("Exit", Command.EXIT, 0);
    mInformationForm.addCommand(exitCommand);
    mInformationForm.setCommandListener(this);
    }
    Display.getDisplay(this).setCurrent(mInformationForm);
    }
    public void pauseApp() {}

    public void destroyApp(boolean unconditional) {}
    public void commandAction(Command c, Displayable s) {
    notifyDestroyed();
    }
    }


    基于J2ME的SVGT移动应用(二)

    使用SVG 不可避免的要涉及XML的解析和使用。JSR-172是J2ME下访问WEB服务的标准API,提供了一个标准的轻量级XML解析器。JSR-172实现 了JAXP1.2(Java API for XML Processing)的一个子集,支持SAX2.0。在JSR-172中使用SAX方式处理XML数据,相比DOM的处理方式更加节省内存,因此在同样 的硬件条件下,SAX能够处理更大的XML文档或数据。JSR-172中包括3个包,其中javax.xml.parsers 包括SAX解析器,工厂和异常类;org.xml.sax中包括核心SAX API;org.xml.sax.helpers中包括多种辅助类。
    此外 ,用户还可以使用KXML等XML解析器在J2ME应用程序中处理XML数据,尤其是在平台不支持JSR-172的时候。
    以下是使用SAX解析XML的具体实例,其中省略了一些代码。首先创建SAX处理器类。
    class BasicHandler extends DefaultHandler
    {
    private Stack tagStack = new Stack();
    public BasicHandler (){}
    public void startDocument() throws SAXException {}
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException  {
        if(qName.equals("AAA")) { /*数据处理*/}
        tagStack.push(qName);
      }
    public void characters(char[] ch, int start, int length) throws SAXException {
        String chars = new String(ch, start, length).trim();
        if(chars.length() > 0){
          String qName = (String)tagStack.peek();
          if (qName.equals("BBB")) { /*数据处理*/}
    }
    }
    public void endElement(String uri, String localName, String qName, Attributes attributes) throws SAXException  {
        tagStack.pop();
      }
      public void endDocument() throws SAXException   { }
    }
    之后装入一个XML文件并指定为此前定义的SAX处理器。
    try
    {
      SAXParserFactory factory = SAXParserFactory.newInstance();
      SAXParser saxParser = factory.newSAXParser();
      FileConnection fc = (FileConnection) Connector.open("file:///root1/phones.xml");
      InputStream is = fc.openInputStream();
      InputSource inputSource = new InputSource(is);
      saxParser.parse(is,new BasicHandler(this));
    }
    catch(Exception ex) {}
         我们也可以从一个输入流中加载XML,例如从一个XML字符串:
          InputStream is = new ByteArrayInputStream(xmlString.getBytes(), 0, xmlString.length());
          InputSource inputSource = new InputSource(is);
          saxParser.parse(is,new BasicHandler(svgCanvas));
     
    一个简单的SVG MIDP应用
        现在我们可以将上面提到的技术组合起来,形成一个完整基于手机短信的网络监控应用,功能是根据短信数据显示当前的网络状态。类图如下:
     
    其中MySamplePushRegistry为一个MIDP应用,启动 后显示界面为一个SvgCanvas类对象,并在在50001端口启动SMS侦听。SvgCanvas为SVG窗口类,负责显示SVG内容,这些内容来自 一个定义好的SVG文件,这里为一个简单的网络图,包括一个PC机,一台打印机以及之间的网络连接。BasicHandler为SAX解析处理器, Status类为短信数据类。
    应用程序的基本流程为:MIDP启动后显示SVG文件,并启动在SMS特 定端口侦听;在接收到XML短信数据后,MIDP使用BasicHandler解析XML,生成状态对象并存入对象列表;在解析结束后,通知 SvgCanvas将根据状态对象更新显示界面,在故障情况下将相应部件颜色改为红色,正常情况则为绿色。
    应用程序在模拟器中运行的情况如下:
    1, 启动画面
     
    2, 发送XML数据短信
    3,PC正常,与打印机连接故障和打印机故障的情况显示
     
    结论
    Mobile SVG已开始在MMS上显露身手,但实际上除了MMS,Mobile SVG还有丰富的应用,其中包括:
    l         娱乐教育等,包括各种手机游戏和在线教程等。
    l         基于位置的服务,在地图上显示运动物体及相关链接,同时允许用户缩放地图、切换不同图层的可见性、选择特定的区域等。具体应用如汽车导航、汽车调度、电子导游、移动广告等。
    l         现场服务:包括技术制图,设备监控等,借助于Mobile SVG,就可以绘制出前后一致的、高品质的图像,可以看全景,也可以看局部细节。
    l         其它任何JPG、GIF或其它位图格式不能满足要求的无线应用场合,如要求提供内容丰富、可伸缩、互动的图形或动画的地方。
     
    对于下一代无线系统(3G)来 说,是否能够成功取决于用户的需求。对于无线用户来说,如果存在更加吸引他们的应用,他们就会更换现有的手机。经由像 Mobile SVG这样以用户为中心的技术增强的MMS,将会产生更多促使手机升级的充分的理由。所以,对于业界来说,其面对的挑战是实现和应用Mobile SVG或其它矢量应用,例如FlashLite等。总的来说,在实现Mobile SVG的过程中,需要运营商、终端制造商、内容提供商共同努力,才能使Mobile SVG的无限魅力在资源受限的移动设备上大放光彩。

    基于J2ME的SVGT移动应用(一)

    本文首先介绍了矢量图形在移动领域中具有的优势,然后对其相关技术,包括Mobile SVG、J2ME以及JSR-226和JSR-172进行简单的介绍。最后,在以上技术的支持下,创建一个简单基于J2ME的SVGT网络监控应用。
     
    矢量图形在移动应用上的优势
    矢量图形与光栅格式图像相比,在对动画、地图和互动图形进行编码和显示方面的优势是明显的。矢量图形是动态的、可以缩放的,能够描述非常高级的图形特性,如复杂形状、动画、分层图形和特殊效果等。
    利用矢量图形的缩放性,图形可以调整大小,以适应任何显示设备而不会导致品质损失。这 在移动设备上是一个优势,因为移动设备的显示屏的形状、尺寸和分辨率差别很大。矢量图形还可以很方便的进行平移、缩放、旋转以及与用户交互的操作,因此用 户可以在不影响图形质量的情况下对图形进行放大,这在使用手机的小屏幕看图时特别有用。
    矢量图形文件通常小于光栅图像文件,从而可以缩短无线下载时间,这点对于非常计较带宽 的移动应用来说尤其重要。另外,将当前屏幕上的图形放大时,对于光栅图像会出现使图像模糊的马赛克效应,此时若要获得高质量的放大图像,则需重新从服务器 获取放大后的图像,从而增加了网络的流量,而矢量图形在客户端进行放大就可以得到没有质量损失的放大图形。
    矢量图形的另一个强大功能是可以存储图形中各元素的相关信息。例如,通过对建筑矢量图形的附加数据,就可以知道房间的面积,售价等信息。
    最后,矢量图形可以方便的利用搜索引擎对图形中的属性进行搜索,实现基于图形的数据搜索。
    目前在移动领域的矢量应用主要包括FlashLite和Mobile SVG等,本文主要讨论Mobile SVG。
     
    Mobile SVG标准
       SVG(Scalable Vector Graphics,可缩放矢量图像)是互联网联盟(W3C)的正式推荐标准,它是一种使用XML来描述二维图像的语言。由于SVG的大部分特性非常适合于 无线领域的图形应用,为了满足移动业界的需求, W3C的SVG工作小组制订了适合于移动应用领域的Mobile SVG标准。
       Mobile SVG主要用于各种资源非常有限的移动设备,所以在实现Mobile SVG时,性能指标成为最主要的指标。由于移动设备在CPU速度、内存大小、支持的显示颜色等各个参数上有很大的不同,单一的专业标准很难满足所有移动设 备的要求。因此,为了覆盖不同移动设备家族的需求,SVG工作小组最终制订了两个级别的Mobile SVG专业标准。一种专业标准是SVG Tiny (SVGT),适用于资源高度受限的移动设备,如手机;另一种专业标准是SVG Basic (SVGB),适用于高端的移动设备,如PDA等。由于移动设备硬件条件的限制,相对于标准的SVG,Mobile SVG在支持的内容、属性、功能等方面作了限制。SVGB是标准SVG的子集,而SVGT又是SVGB的子集,SVGT标准中删除了透明、渐变、裁剪、图 案、符号和蒙板等复杂功能,而且没有对脚本支持。具体的不同请参考W3C SVG网址。
    以下是一个SVGT的例子:
    <?xml version="1.0" encoding="utf-8"?>
    <svg xmlns="http://www.w3.org/2000/svg"
         xmlns:xlink="http://www.w3.org/1999/xlink"
         xmlns:demo="http://www.sun.com/svg/demo"
         id="root"
         width="100%" height="100%" viewBox="0 0 240 320"
         xml:space="preserve"
         preserveAspectRatio="none">
        <rect fill="#35556B" width="240" height="320"/>
        <g id="processingIndicator" transform="translate(120, 97) scale(0.5)" >
           <g id="processingIndicator.outerCircle">
                  <path transform="translate(-120, -97)" fill="#C1CBD1" d="M73.426,97.169c0,25.681,20.894,46.575,46.575,46.575c25.681,0,46.574-20.894,46.574-46.575c0-9.251-2.708-18.192-7.83-25.854c-1.688-2.525-5.104-3.204-7.629-1.516s-3.204,5.104-1.516,7.629c3.908,5.847,5.975,12.673,5.975,19.741c0,19.616-15.959,35.575-35.574,35.575c-19.616,0-35.575-15.958-35.575-35.575c0-19.616,15.959-35.575,35.575-35.575c6.063,0,12.048,1.551,17.307,4.485c2.652,1.48,6.002,0.53,7.482-2.123c1.48-2.653,0.529-6.003-2.123-7.483c-6.893-3.846-14.73-5.879-22.666-5.879C94.319,50.594,73.426,71.488,73.426,97.169z"/>
           </g>
           <g id="processingIndicator.innerCircle">
                  <path transform="translate(-120, -97)" fill="#738999" d="M120.001,69.772c-5.441,0-10.701,1.592-15.21,4.607c-2.525,1.688-3.204,5.104-1.516,7.629c1.688,2.525,5.104,3.204,7.629,1.516c2.693-1.8,5.838-2.752,9.096-2.751c4.38,0,8.498,1.705,11.594,4.802c3.098,3.097,4.803,7.214,4.803,11.594c0,9.042-7.355,16.397-16.397,16.397c-9.041,0-16.396-7.355-16.396-16.397l0.065-1.479c0.134-3.034-2.201-5.848-5.236-5.981c-3.034-0.134-5.587,1.972-5.721,5.007l-0.108,2.454c0,15.107,12.29,27.397,27.396,27.397c15.107,0,27.397-12.291,27.397-27.397c0-7.318-2.85-14.198-8.024-19.373S127.318,69.772,120.001,69.772z"/>
           </g>
           <g id="processingIndicator.center" >
                  <circle fill="white" transform="translate(-120, -97)" cx="120" cy="97.169" r="9.349"/>
                  <circle display="none" fill="#35556B" transform="translate(-120, -97)" cx="122.321" cy="92.551" r="2.078"/>
           </g>
        </g>
        <defs>
                <animateTransform id="processingIndicatorAnim" xlink:href="#processingIndicator.innerCircle" attributeName="transform" type="rotate" values="0;360" begin="0s" dur="3s" repeatDur="indefinite"/>
                <animateTransform xlink:href="#processingIndicator.outerCircle" attributeName="transform" type="rotate" values="0;-360" begin="0s" dur="2s" repeatDur="indefinite"/>
                <animateTransform xlink:href="#processingIndicator.center" attributeName="transform" type="rotate" values="0;-360" begin="0s" dur="1s" repeatDur="indefinite"/>
        </defs>
    <g id="loadProgressBar">
            <rect id="loadProgressBar.bkg" fill="#A3B8CB" x="45" y="250" width="150" height="10" />
            <rect id="loadProgressBar.progress" fill="white" x="45" y="250" width="20" height="10" />
            <text transform="translate(120,230)" fill="#BDBEC0" font-size="12" text-anchor="middle">loading application</text>   
            <text id="loadProgressBar.text" text-anchor="middle" transform="translate(120,275)" fill="#FFFFFF" font-size="12">0%</text>
    </g>
    </svg>
    Mobile SVG最近已被3GPP组织所采纳,用于多媒体短信服务 (MMS)。Mobile SVG将很快应用在许多2.5G和3G MMS服务中。Mobile SVG的增强MMS短信功能对于手机制造商具有明显的吸引力。与运营商一样,OEM厂商也可以在不对设计作重大变动或不显著增加成本的情况下,增加 Mobile SVG功能。由于Mobile SVG是内容丰富的通用平台,最适合于帮助设备制造商提供差别化服务。
    为了实现SVGT在手机上的应用,开发人员需要了解手机开发平台及其对SVGT的支持情况,其中最常见的是基于JAVA语言的J2ME移动开发平台。
     
    J2ME应用及其规范扩展
    随 着移动技术的发展,手机已经从最初的一种单纯的通信工具转变成如今集通信、工作、娱乐等功能为一体的综合设备。因此,在移动终端上开发通用的、丰富的应用 已成为必然的趋势。这些应用能够提供与桌面应用相媲美的功能,并可以按用户的意愿随时安装和删除。然而手机平台在硬件,操作系统等系统底层有很大的不同, 给应用程序的开发带来了很大的困难,开发人员迫切需要一种通用的开发平台。
    J2ME(JAVA2 Micro Edition)正是这样一种JAVA应用开发平台。实际上,JAVA语言从其诞生起就以其运行的平台无关性这一强大的优势而成为网络应用的宠儿。 J2ME是JAVA2标准版本的微型版本,专门为小型移动设备所设计。这些设备处理器的处理能力都不强,可使用的资源也有限。因此,J2ME只包含了 J2SE中在移动通信设备上所必需的功能和组件,使其能够在移动设备及其有限的资源上开发出丰富多彩且平台无关的应用。J2ME在结构上分为CDC (Connected Device Configuration)和相应的Foundation Profile规范,以及CLDC(Connected Limited Device Configuration)和相应的MIDP规范。
    MIDP(Mobile Information Device Profile)是移动信息设备规范的简称,规范具体定义了J2ME适用的硬件和软件框架,并提供了这个框架要实现的基本功能及其标准接口,应用开发者可 以基于这个框架开发出各种应用。2000年9月,SUN公司发布了MIDP的第一个正式版本MIDP1.0。它将J2ME适用的设备定位在至少拥有数百 KB RAM和ROM,并具有基本网络和显示功能的移动通信设备上。在该基础上定义了一系列软件接口,其中包括基本输入输出、图形化用户接口(GUI)、网络、 事件机制、文件系统、应用管理系统(AMS)等。之后,随着Java技术的 不断发展和用户需求的不断提高,SUN公司又于2002年11月发布了MIDP2.0。它对设备的内存资源和处理能力的要求较1.0要高,但也为应用开发 者提供了更方便、更丰富多彩的软件包。MIDP2.0中主要增加了游戏接口的实现、声音输出接口的实现安全网络机制的实现。MIDP2.0的这些特性将使 基于移动设备的JAVA应用具有更加广阔的前景,也必将使新一代的移动设备发生革命性的变化并领导时尚潮流。
    另外,为了支持不同的应用方向,SUN还联合其它一些公司共同制定了J2ME扩展技术规范,包括图形应用、网络服务、多媒体和安全等。其中本文中主要使用到移动2D SVG图形方面的JSR-226和WEB服务方面的JSR-172。
    所有MIDP应用从MIDlet类中派生, MIDlet类管理和控制着应用程序的生命周期,包括装载、激活、暂停和销毁四个阶段。当设备装入MIDlet并调用其构造函数时为装载期,直到程序管理 器调用应用程序的startApp()方法。在startApp()调用后,应用程序处于激活期,直到调用了pauseApp() 或 destroyApp() 。pauseApp() 暂停应用程序,而destroyApp()销毁应用程序。一个典型的MIDlet代码如下:
    import javax.microedition.midlet.*;
    import javax.microedition.lcdui.*;
    public class Hello extends MIDlet
    {
    private Display display;
    public Hello()
    {
    display = Display.getDisplay(this);
    }
    public void startApp()
    {
    Form f = new Form("Test");
    f.append("hello world!");
    display.setCurrent(f);
    }
    public void pauseApp()
    {
    }
    public void destroyApp(boolean unconditional)
    {
    }
    }
     
    J2ME开发平台
    开发人员可以在SUN的J2ME网址上下载CLDC移动开发工具,目前的最新版本为2.5。Sun Java™ Wireless Toolkit 2.5X新增了一个新的平台,移动服务架构 (JSR 248以及9个新的可选API包,其中包括:)
    l         Security and Trust Services APIs for J2ME (JSR 177支持智能卡访问和数据加密功能,包括创建和签署证书。)
    l         Location API for J2ME (JSR 179提供与GPS等位置服务硬件交互功能。)
    l         Scalable 2D Vector Graphics API for J2ME (JSR 226提供了显示和渲染SVG内容的API。 )
    l         Mobile Internationalization API (JSR 238为多语言应用程序定义了资源文件机制。)
     
    开发工具中带有丰富的示例,便于开发人员的了解和学习。
     
    JSR-226技术规范和应用
    JSR-226的定义了SVG 图形(Mobile 2D Graphics,M2G)API。其中SVG矢量图形是ScalableImage的实例,你可以通过静态的createImage()方法获得它们。 SVGImage是ScalableImage的一个子类,它提供事件处理和底层DOM文档访问之间的联系。大多数情况下我们会使用这个类。例如如何从一 个SVGT文件中创建一个SVGImage实例
    try{
    InputStream imageStream = getClass().getResourceAsStream("/loadScreen.svg");
    SVGImage svgImage = (SVGImage)SVGImage.createImage(imageStream, null);
    } catch(Exception e) {
        e.printStackTrace();
    }
    然后,创建ScalableGraphics的一个实例:
    ScalableGraphics sg = ScalableGraphics.createInstance();
    最后,在paint事件中显示SVG图形:
    public void paint(Graphics g) {
           // *** clear the display
           g.setColor(255, 255, 255);
           g.fillRect(0, 0, getWidth(), getHeight());
           // *** render the SVG image
          sg.bindTarget(g);
           sg.setTransparency(1f);
            svgImage.setViewportWidth(getWidth());
            svgImage.setViewportHeight(getHeight());
            sg.render(0, 0, svgImage);
            sg.releaseTarget();
    }
    为了使渲染动画SVG 内容的普通情况更加方便,JSR-226还提供了SVGAnimator类。通过SVGAnimator可以创建和控制一个Canvas对象,这个对象自 动处理屏幕更新,以播放动画事件和相应用户的操作。因此使用SVGAnimator不需要显式创建ScalableGraphics对象。例如:
    try{
                 InputStream imageStream = SVGMobile.class.getResourceAsStream("/Halloween.svg");
                 svgImage = (SVGImage)SVGImage.createImage(imageStream, null);
                 svgAnimator = SVGAnimator.createAnimator(svgImage);
         }
         catch(Exception e) {
             e.printStackTrace();
         }
            
           // Set to 10 fps (frames per second)
           svgAnimator.setTimeIncrement(0.01f);
     
           svgCanvas = (Canvas)svgAnimator.getTargetComponent();
           svgImage.setViewportWidth(svgCanvas.getWidth());
           svgImage.setViewportHeight(svgCanvas.getHeight());
           
           // The SVG root element is used to reset the time on a stop operation.
           doc = svgImage.getDocument();
           svg = (SVGSVGElement)doc.getDocumentElement();
           
           svgCanvas.setCommandListener(this);
           // Hook-in key listeners to play, pause and stop the animation.
           svgAnimator.setSVGEventListener(this);
           
           svgAnimator.play();
           state = STATE_PLAYING;
           System.err.println("PLAYING...");
                   
    // *** grab a reference to the display
           display = Display.getDisplay(this);
           display.setCurrent(svgCanvas);
           
           // *** set up the midlet menu
           int hotKey = 0;
           svgCanvas.addCommand(exitCommand);
    在应用程序中,不仅可以从SVG文档装载静态的SVG数据,而且可以创建空的SVGImage,然后通过操作其DOM数据形成需要的图形,下例中创建了一个空SVGImage,然后在DOM树中追加了一个SVG text元素
    import javax.microedition.midlet.*;
    import javax.microedition.lcdui.*;
    import javax.microedition.m2g.ScalableGraphics;
    import javax.microedition.m2g.SVGImage;
    import org.w3c.dom.Document;
    import org.w3c.dom.svg.SVGElement;
    import org.w3c.dom.svg.SVGSVGElement;
    import org.w3c.dom.svg.SVGRGBColor;

    public class HelloSVG extends MIDlet  {
     
      protected SVGImageCanvas svgCanvas = null;
      public HelloSVG() {
     
      }
      public void startApp() {
        SVGImage svgImage = SVGImage.createEmptyImage(null);
        Document doc = svgImage.getDocument(); 
        SVGSVGElement svg = (SVGSVGElement) doc.getDocumentElement();
        SVGElement textElement = (SVGElement) doc.createElementNS("http://www.w3.org/2000/svg", "text");  
        textElement.setTrait("#text", "Hello JSR-226 !");
        textElement.setFloatTrait("x", 50.0f);
        textElement.setFloatTrait("y", 50.0f);
        SVGRGBColor textColor = svg.createSVGRGBColor(0, 0, 0);
        textElement.setRGBColorTrait( "stroke", textColor);
        svg.appendChild(textElement);
        svgCanvas = new SVGImageCanvas(svgImage);
        Display.getDisplay(this).setCurrent(svgCanvas);
      }
       public void pauseApp() {
       }
        public void destroyApp(boolean unconditional) {
        }
    }
    class SVGImageCanvas extends Canvas {
        protected SVGImage svgImage;
        protected ScalableGraphics sg = ScalableGraphics.createInstance();

        protected SVGImageCanvas(final SVGImage svgImage) {
            this.svgImage = svgImage;
        }
    public void paint(Graphics g) { 
            g.setColor(255, 255, 255);
            g.fillRect(0, 0, getWidth(), getHeight());
            sg.bindTarget(g);
            svgImage.setViewportWidth(getWidth());
            svgImage.setViewportHeight(getHeight());
            sg.render(0, 0, svgImage);
            sg.releaseTarget();
        }
    }
    这些类都定义在javax.miroedition.m2g和org.w3c.dom.svg包中,更多内容请参考JSR-226的技术规范文档。SVG DOM是在org.w3c.dom和org.w3c.dom.events包中定义的。

    SVG(JSR 266)开发入门指南

    SonyEricsson的W950 M600和P990是基于Symbian平台的手机,并且手机上实现的是Symbian Java Platform 3。在SJP-3中提供了对Scalable Vector Graphics的支持,本文说明如何从文件中装载生成SVG Image以及如何在代码中创建SVG Image。

    下面是用于创建SVG Image的文件svgImage.svg。

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG Tiny 1.1//EN"
             "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd">

    <svg width="240" height="220" version="1.1" xmlns="http://www.w3.org/2000/svg" baseProfile="tiny">
         <circle id="circle" cx="75" cy="75" r="25" fill="#C8C800" fill-opacity="0.5" stroke="#000000" stroke-width="5.0"/>
         <rect id="rect" x="75" y="75" width="50" height="50" fill="#640000" fill-opacity="0.5" stroke="#000000" stroke-width="5.0" />

    </svg>

    在这个文件中我们会创建一个圆和一个矩形,信息如下。
    Circle:
    Id = Circle // id用来标记这个圆,这样可以在
    cx = 75 // 原点 X
    cy = 75 // 原点 Y
    r = 25 // 半径
    fill = C8C800 // 填充色
    fill-opacity = 0.5 // 50% 透明度
    stroke = black // border的颜色
    stroke-witdh = 5 // Border的宽度

    下面的代码说明了如何在MIDlet中载入上面的文件

    private SVGImage svgImage = null;
    private ScalableGraphics gc;
    ...
    InputStream is = getClass().getResourceAsStream("/svgImage.svg");
    svgImage = (SVGImage) SVGImage.createImage(is, null);
    public void paint(Graphics g){
        gc.bindTarget(g);
        gc.render(0, 0, svgImage);
        gc.releaseTarget();
    }

    当Image创建后,你可以对它进行删改等操作,例如下面就修改了圆的半径。

    Document document = svgImage.getDocument();
    SVGElement circle = (SVGElement) document.getElementById("circle");
    circle.setFloatTrait("r", 40.0f);

    上面介绍了如何从文件创建SVG Image,下面的代码说明了如何在代码中创建。
    ScalableGraphics gc = ScalableGraphics.createInstance();

    SVGImage svgImage = SVGImage.createEmptyImage(null);
    svgImage.setViewportWidth(getWidth());
    svgImage.setViewportHeight(getHeight());
    Document document = svgImage.getDocument();
    SVGSVGElement root = (SVGSVGElement) document.getDocumentElement();
    String namespace = root.getNamespaceURI();

    SVGElement element = null;
    try{
        element = (SVGElement) document.createElementNS(namespace, "circle");
    }catch(Exception e){
        SVGDynamic.addInfo(e.toString());
        e.printStackTrace();
    }
    SVGRGBColor fillColor = root.createSVGRGBColor(0xC8, 0xC8, 0);
    SVGRGBColor strokeColor = root.createSVGRGBColor(0, 0, 0);

    element.setId("circle");
    element.setFloatTrait("cx", 75.0f);
    element.setFloatTrait("cy", 75.0f);
    element.setFloatTrait("r", 25.0f);
    element.setFloatTrait("fill-opacity", 0.5f);
    element.setFloatTrait("stroke-width", 5.0f);
    element.setRGBColorTrait("fill", fillColor);
    element.setRGBColorTrait("stroke", strokeColor);

    root.appendChild(element);

    public void paint(Graphics g) {
        gc.bindTarget(g);
        gc.render(0, 0, svgImage);
        gc.releaseTarget();
    }

    WTK 2.5提供了JSR 226的实现环境,并且里面还提供了例子可供开发者参考。

    import javax.microedition.midlet.*;
    import javax.microedition.lcdui.*;

    import javax.microedition.m2g.ScalableGraphics;
    import javax.microedition.m2g.SVGImage;

    import org.w3c.dom.Document;
    import org.w3c.dom.svg.SVGElement;
    import org.w3c.dom.svg.SVGSVGElement;
    import org.w3c.dom.svg.SVGRGBColor;


    public class HelloSVG extends MIDlet  {
     
      protected SVGImageCanvas svgCanvas = null;

      public HelloSVG() {
     
      }

      public void startApp() {
        SVGImage svgImage = SVGImage.createEmptyImage(null);
        Document doc = svgImage.getDocument(); 
        SVGSVGElement svg = (SVGSVGElement) doc.getDocumentElement();
        SVGElement textElement = (SVGElement) doc.createElementNS("http://www.w3.org/2000/svg", "text");  
        textElement.setTrait("#text", "Hello JSR-226 !");
        textElement.setFloatTrait("x", 50.0f);
        textElement.setFloatTrait("y", 50.0f);
        SVGRGBColor textColor = svg.createSVGRGBColor(0, 0, 0);
        textElement.setRGBColorTrait( "stroke", textColor);

        svg.appendChild(textElement);

        svgCanvas = new SVGImageCanvas(svgImage);
        Display.getDisplay(this).setCurrent(svgCanvas);
      }

       public void pauseApp() {
       }

        public void destroyApp(boolean unconditional) {
        }
    }

    class SVGImageCanvas extends Canvas {

        protected SVGImage svgImage;

        protected ScalableGraphics sg = ScalableGraphics.createInstance();


        protected SVGImageCanvas(final SVGImage svgImage) {
            this.svgImage = svgImage;
        }

        public void paint(Graphics g) { 
            g.setColor(255, 255, 255);
            g.fillRect(0, 0, getWidth(), getHeight());
            sg.bindTarget(g);
            svgImage.setViewportWidth(getWidth());
            svgImage.setViewportHeight(getHeight());
            sg.render(0, 0, svgImage);
            sg.releaseTarget();
        }
    }


     

     

     

    常见J2ME系统属性及其作用列表

    在J2ME开发中,我们经常需要和手机系统进行交互,获得一些和系统相关的信息,在J2ME API设计中,提供了一系列的系统属性,可以让我们来进行获得,下面就一一进行介绍。
    表1   CLDC、MIDP和JTWI属性
    属性名称
    属性作用
    microedition.profiles
    代表手机支持的MIDP版本,返回格式值为“MIDP-1.0”或“MIDP-2.0”
    microedition.configuration
    代表手机支持的CLDC版本,返回格式值为“CLDC-1.0”或“CLDC-2.0”
    microedition.locale
    代表手机所在的国家或地区,返回值格式为“en-US”
    microedition.platform
    代表手机的品牌和型号,Nokia手机的返回值格式为“Nokia6310i/4.42
    microedition.encoding
    代表手机默认的字符集名称,返回值格式为“ISO-8859-1”
    microedition.commports
    代表手机可以使用的串口列表,返回值中各个串口之间使用逗号分隔
    microedition.hostname
    MIDP2.0定义,代表本地主机名称,需要手机支持。
    microedition.jtwi.version
    代表手机支持的JTWI版本,值必须是“1.0”
     
    表2 可选包属性
    属性名称
    属性作用
    microedition.media.version
    代表手机支持的MMAPI版本,如果不支持则返回null
    microedition.pim.version
    代表手机支持的PIM API版本,如果不支持则返回null
    microedition.m3g.version
    代表手机支持的M3G API版本,如果不支持则返回null
    microedition.location.version
    代表手机支持的Location API版本,如果不支持则返回null
    Bluetooth.api.version
    代表手机支持的BT API版本,如果不支持则返回null
    microedition.io.file.
    FileConnection.version
    代表手机支持的FC API版本,如果不支持则返回null
    microedition.global.version
    代表手机支持的Mobile Internationalization API(JSR-238)版本,如果不支持则返回null
    microedition.chapi.version
    代表手机支持的CH(Content Handler) API(JSR211)版本,如果不支持则返回null
    microedition.sip.version
    代表手机支持的SIP API版本,如果不支持则返回null
     
    表3 MMAPI属性
    属性名称
    属性作用
    supports.mixing
    代表手机是否支持混音(同时播放多个Player),返回值为“true”或“false”
    supports.audio.capture
    代表手机是否支持声音捕获(录音),返回值为“true”或“false”
    supports.video.capture
    代表手机是否支持视频捕获(录像),返回值为“true”或“false”
    supports.recording
    代表手机是否支持记录(record),返回值为“true”或“false”
    audio.encodings
    代表手机支持的声音格式,返回值格式为“encoding=audio/wav”,多个格式之间使用至少一个空格进行间隔
    video.encodings
    代表手机支持的视频格式,返回值格式为“encoding=video/3gpp”,多个格式之间使用至少一个空格进行间隔
    video.snapshot.encodings
    代表手机使用getSnapshot方法获得的视频快照格式,返回值格式为“encoding=png”,多个格式之间使用至少一个空格进行间隔
    streamable.contents
    代表手机支持的流媒体格式,返回null代表不支持
     
    表4 Wireless Messaging API属性
    属性名称
    属性作用
    wireless.messaging.sms.smsc
    代表手机发送短信时的短信服务中心号码
     
    表5 FileConnection API
    属性名称
    属性作用
    fileconn.dir.photos
    代表手机中存储照片和其它图片的目录,例如“file:///c:/My files/ Images /”
    fileconn.dir.videos
    代表手机中存储视频的目录,例如“file:///c:/My files/Video clips/”
    fileconn.dir.tones
    代表手机中存储声音的目录,例如“file:///c:/My files/Tones/”
    fileconn.dir.memorycard
    代表手机中存储卡的根目录。例如“file:///d:/”
    fileconn.dir.private
    (Nokia S40不支持) 
    代表手机中MIDlet的私有工作目录,例如“file:///c:/System/MIDlets/[1015f294]/scratch”
    fileconn.dir.photos.name
    代表手机中图片目录的名称,例如“Images”
    fileconn.dir.videos.name
    代表手机中视频目录的名称,例如“Video clips”
    fileconn.dir.tones.name
    代表手机中声音目录的名称,例如“Sound clips”
    file.separator
    代表手机中的文件分隔符,例如“/”
    fileconn.dir.memorycard.name
    代表手机中存储卡的名称,例如“Memory card”
     
             使用这些属性,可以获得在程序运行过程中需要的很多和系统相关的信息,也可以使用表2中的属性来获得手机是否支持对应的可选包等信息。
             实际使用示例:
                       String name = System.getProperty(“microedition.platform”);
     
             注意:如果需要获得JVM或jad文件中的信息,需要使用MIDlet类中的getAppProperty方法,其属性名则需要查阅jad文件的设定,和本文所述的属性名无关。

    J2ME游戏优化秘密

    本文章描述了代码优化在为移动设备写运行起来速度快的游戏中扮演的角色。我会用例子说明如何、什么时候和为什么要优化你的代

    码,来榨干兼容MIDP的手机的每一滴性能。我们将要讨论为什么优化是必要的和为什么有时候最好不要优化。我将解释高级优化和低

    级优化的差别,然后我们会知道如何使用J2ME无线开发包(WTK)自带的Profile程序来发现到哪里去优化你的代码。这篇文章最后揭

    示了很多让你的MIDlet运行的技术。
    为什么优化?
     计算机游戏可以分为两大类: 实时的和输入驱动的. 输入驱动的游戏显示游戏的当前运行状态,并在继续之前无限地等待用户的输入.

    扑克牌游戏属于这一类,同样,大多数的猜谜游戏、过关游戏和文字冒险游戏都属于这一类。实时游戏,有时候被称为技能或动作游戏

    ,不等待用户,他们不停地运行直到游戏结束。
     技能和动作游戏经常以大量的屏幕上运东为特征(想想Galaga游戏和Robotron游戏)。刷新率必须至少有10fps(每秒的帧数)并且要

    有足够的动作来保持玩家的挑战性。它们需要玩家快速的反应和好的手眼配合,所以就强迫S&A(技能和动作)游戏必须对玩家的输入

    有很强的响应能力。在快速响应玩家案件的同时提供高帧数的图形动作,这是实时游戏的代码必须运行起来快的原因。在用J2ME开发

    的时候,挑战性就更大了。
     Java 2 Micro Edition(J2ME)是java的一个分解版本。 适用于有限功能的小型设备,比如手机和PDA。J2ME设备有:
     *有限的输入能力(没有键盘!)(译者注:这里键盘特指个人电脑的键盘)
     *小的显示尺寸
     *有限的内存容量和堆大小
     *慢速的CPU
     在J2ME平台上写出快的游戏-------写出在比桌面电脑里的慢得多的CPU上运行的代码更是挑战了开发者。
    什么时候不优化
     如果你不是在写一个技能或者动作游戏,那么可能不需要优化。如果玩家已经为自己的下一步考虑了几秒钟抑或几分钟,她可能不会

    介意如果你的游戏响应花掉了几百微秒。这个规则的一个例外是,如果这个游戏在决定下一步如何运行的时候有大量的工作要处理,比

    如搜索一百万个可能的象棋片组合。这种情况下,你可能想要优化你的代码,从而在几秒钟内计算出电脑的下一步,而不是几分钟。
     就算你正在写这种类型的游戏,优化也可能是危险的。许多这样的技术伴随着一个代价--他们表示着好”的程序设计这个通常概念飞

    过来的时候,同时使你的代码更难读懂。有些是一个权衡,需要开发者大大增加程序的大小来得到性能上一点点的改进。J2ME开发者

    们对于保持他们的JAR尽可能的小这个挑战再熟悉不过了。这里是一些不优化的理由:
            *优化是一个增加bug的好手
     *有些技术会降低你的代码的移植性
     *你可能要花费大量的努力来得到微小的或者没有改进
     *优化是困难的
     最后一点需要一些阐述。优化是一个活动目标,在Java平台上更是这样,而且在J2ME上就更加突出,因为其运行环境是那样的多变。

    你优化后的代码可能在一个模拟器上运行得更快,但却在实际设备上更慢,或者相反。为一部手机优化可能会降低其在另一部上的性能


     不过还是有希望。有两条路径你可以做优化,高层的和底层的。第一条基本上会在所有的平台上增加执行性能,甚至会改进你代码的

    整个质量。第二条是可能会让你头疼的,但是那些底层技术是很容易创造的,而且更加容易消去如果你不想使用它们。最起码,他们看

    起来很有趣。
     
     我们将用系统的timer在实际设备上剖析你的代码,这可以帮助你测量出那些技术在你所开发的硬件上到底有多有效。
      最后一点:
      *优化是有趣的
     
     一个反面例子:
     让我们来看一看这个包含两个类的简单的应用程序,首先,是Midlet...
      import javax.microedition.midlet.*;
    import javax.microedition.lcdui.*;
    public class OptimizeMe extends MIDlet implements CommandListener {
      private static final boolean debug = false;
      private Display display;
      private OCanvas oCanvas;
      private Form form;
      private StringItem timeItem = new StringItem( "Time: ", "Unknown" );
      private StringItem resultItem =
                                new StringItem( "Result: ", "No results" );
      private Command cmdStart = new Command( "Start", Command.SCREEN, 1 );
      private Command cmdExit = new Command( "Exit", Command.EXIT, 2 );
      public boolean running = true;
      public OptimizeMe() {
        display = Display.getDisplay(this);
        form = new Form( "Optimize" );
        form.append( timeItem );
        form.append( resultItem );
        form.addCommand( cmdStart );
        form.addCommand( cmdExit );
        form.setCommandListener( this );
        oCanvas = new OCanvas( this );
      }
      public void startApp() throws MIDletStateChangeException {
        running = true;
        display.setCurrent( form );
      }
      public void pauseApp() {
        running = false;
      }
      public void exitCanvas(int status) {
        debug( "exitCanvas - status = " + status );
        switch (status) {
          case OCanvas.USER_EXIT:
            timeItem.setText( "Aborted" );
            resultItem.setText( "Unknown" );
          break;
          case OCanvas.EXIT_DONE:
            timeItem.setText( oCanvas.elapsed+"ms" );
            resultItem.setText( String.valueOf( oCanvas.result ) );
          break;
        }
        display.setCurrent( form );
      }
      public void destroyApp(boolean unconditional)
                              throws MIDletStateChangeException {
        oCanvas = null;
        display.setCurrent ( null );
        display = null;
      }
      public void commandAction(Command c, Displayable d) {
        if ( c == cmdExit ) {
          oCanvas = null;
          display.setCurrent ( null );
          display = null;
          notifyDestroyed();
        }
        else {
          running = true;
          display.setCurrent( oCanvas );
          oCanvas.start();
        }
      }
      public static final void debug( String s ) {
        if (debug) System.out.println( s );
      }
    }
    Second, the OCanvas class that does most of the work in this example...
    import javax.microedition.midlet.*;
    import javax.microedition.lcdui.*;
    import java.util.Random;
    public class OCanvas extends Canvas implements Runnable {
      public static final int USER_EXIT = 1;
      public static final int EXIT_DONE = 2;
      public static final int LOOP_COUNT = 100;
      public static final int DRAW_COUNT = 16;
      public static final int NUMBER_COUNT = 64;
      public static final int DIVISOR_COUNT = 8;
      public static final int WAIT_TIME = 50;
      public static final int COLOR_BG = 0x00FFFFFF;
      public static final int COLOR_FG = 0x00000000;
      public long elapsed = 0l;
      public int exitStatus;
      public int result;
      private Thread animationThread;
      private OptimizeMe midlet;
      private boolean finished;
      private long started;
      private long frameStarted;
      private long frameTime;
      private int[] numbers;
      private int loopCounter;
      private Random random = new Random( System.currentTimeMillis() );
      public OCanvas( OptimizeMe _o ) {
        midlet = _o;
        numbers = new int[ NUMBER_COUNT ];
        for ( int i = 0 ; i < numbers.length ; i++ ) {
          numbers[i] = i+1;
        }
      }
      public synchronized void start() {
        started = frameStarted = System.currentTimeMillis();
        loopCounter = result = 0;
        finished = false;
        exitStatus = EXIT_DONE;
        animationThread = new Thread( this );
        animationThread.start();
      }
      public void run() {
        Thread currentThread = Thread.currentThread();
        try {
          while ( animationThread == currentThread && midlet.running
                                                   && !finished ) {
            frameTime = System.currentTimeMillis() - frameStarted;
            frameStarted = System.currentTimeMillis();
            result += work( numbers );
            repaint();
            synchronized(this) {
              wait( WAIT_TIME );
            }
            loopCounter++;
            finished = ( loopCounter > LOOP_COUNT );
          }
        }
        catch ( InterruptedException ie ) {
          OptimizeMe.debug( "interrupted" );
        }
        elapsed = System.currentTimeMillis() - started;
        midlet.exitCanvas( exitStatus );
      }
      public void paint(Graphics g) {
        g.setColor( COLOR_BG );
        g.fillRect( 0, 0, getWidth(), getHeight() );
        g.setColor( COLOR_FG );
        g.setFont( Font.getFont( Font.FACE_PROPORTIONAL,
             Font.STYLE_BOLD | Font.STYLE_ITALIC, Font.SIZE_SMALL ) );
        for ( int i  = 0 ; i < DRAW_COUNT ; i ++ ) {
          g.drawString( frameTime + " ms per frame",
                        getRandom( getWidth() ),
                        getRandom( getHeight() ),
                        Graphics.TOP | Graphics.HCENTER );
        }
      }
      private int divisor;
      private int r;
      public synchronized int work( int[] n ) {
        r = 0;
        for ( int j = 0 ; j < DIVISOR_COUNT ; j++ ) {
          for ( int i = 0 ; i < n.length ; i++ ) {
            divisor = getDivisor(j);
            r += workMore( n, i, divisor );
          }
        }
        return r;
      }
      private int a;
      public synchronized int getDivisor( int n ) {
        if ( n == 0 ) return 1;
        a = 1;
        for ( int i = 0 ; i < n ; i++ ) {
          a *= 2;
        }
        return a;
      }
      public synchronized int workMore( int[] n, int _i, int _d ) {
        return n[_i] * n[_i] / _d + n[_i];
      }
      public void keyReleased(int keyCode) {
        if ( System.currentTimeMillis() - started > 1000l ) {
          exitStatus = USER_EXIT;
          midlet.running = false;
        }
      }
      private int getRandom( int bound )
      {  // return a random, positive integer less than bound
        return Math.abs( random.nextInt() % bound );
      }
    }
     这个程序是一个模拟一个简单游戏循环的MIDlet:
      *work          执行
      *draw          绘制
      *poll for user input        等待用户输入
      *repeat          重复
      对于快速游戏,这个循环一定要尽可能的紧凑和快速。我们的循环持续一个有限的次数(LOOP_COUNT=100),并且用系统timer来计

    算整个作业花费了多少毫秒,我们就可以测量并改善它的性能。时间和执行的结果会显示在一个简单的窗口上。用Start命令来开启测

    试。按任意键会提前退出循环,退出按钮用来结束程序。-
      在大多数游戏里面,主游戏循环中的作业会更新整个游戏状态-----移动所有的角色,检测并处理冲突,更新分数,等等。在这个例

    子里面,我们并没有做什么特别有用的事。程序仅仅是在一个数组之间做一些算数运算,然后
      把这些结果加起来。
      run()函数计算了每次循环所花费的时间。每一帧,OCanvas.paint()方法都在屏幕上的16个随机的地方显示这个数。一般的,

    你可以用这个方法在你的游戏里面画出你的图像元素,我们的代码在该过程中作了一些有用的摹写。
      不管这些代码看起来有多么的无意义,它给了我们足够的机会去优化它的性能。
     
    ******************第二页
    哪里去优化 -- 90/10规则
    在苛求性能的游戏里面,有90%的时间是在执行其中%10的代码。我们的优化努力就应该针对这10%的代码。我们用一个Profier来定位

    这 10%. 要运行J2ME无线开发包中的profier工具,选择edit菜单下的preferences选项. 这将会显示preferences窗口.选择

    Monitoring这一栏,将"Enable Profiling"悬赏,然后点ok按钮。什么也没有出现。这是对的,在Profier窗口显示之前,我们需要

    在模拟器中运行我们的程序然后退出。现在就做.
    图1显示了如何打开Profiler工具。

     
     
     

    我的模拟器(运行在Windows XP下,Inter P4 2.4GHz的CPU)报告我100次这个循环用了6,407毫秒,或者说6又1/2秒。这个程序报

    告说62或者63毫秒每帧。在硬件(一个 motorola的i85s)上运行会慢得多。 一帧的时间大约是500毫秒,整个循环用了52460毫秒。

    在本文这一课中,我们将试着改善这个数据。
    当你退出这个程序时,profiler窗口就会出现,然后你会看见一个文件夹浏览器中有一些东西,在左边的面板上会有一个熟悉的树形

    部件。方法间的联系会在这个结构列表中显示。每一个文件夹是一个方法,打开一个文件夹会显示它所调用过的方法。在该树中选择一

    个方法会显示那个方法的profiling信息并在右边的面板显示所有被它调用过的方法。注意在每一个元素旁边显示了一个百分数。这就

    是该方法在整个执行过程中所占的执行时间的百分比。我们必须翻遍这棵树,来寻找时间都到哪里去了,并对占用百分比最高的方法进

    行优化,如果可能的话。
     
    图2 -- Profiler程序调用的图

     
     
     

    对这个profiler,有几点需要说明。首先你的百分比多半会和我的不一样,但是他们的比例会比较相似--总是在最大的数之后。我的

    数据在被次运行的时候都会改变。为了保持情况一致,你可能希望关掉所有的后台程序,像Email客户端,并在你测试的时候保持你正

    在进行的任务最少。还有,不要在用profiler之前混淆(obfuscate)你的代码,不然你的方法会被神秘的标示为b或者a或者ff。最

    后profiler不会因为你运行模拟器的设备的差别而改变,它和硬件是完全独立的。
    打开最高百分比的那个文件夹,我们看到有66.8%的时间在执行一个被称为

    "com.sun.kvem.midp.lcdui.EmulEventHandler$EventLoop.run"的方法,这个对我们并没有什么帮助。用类似的方法,再往下寻

    找更深层次的方法,持续下去,你就会找到一个大的百分比停留在serviceRepaints()上,最后到了我们的 OCanvas.paint()方法.另

    外有30%的时间在OCanvas.run()方法里.这两个方法都在我们的主程序循环中,这并不奇怪.我们不会在我们的MIDlet类中花任何时间

    做优化,同样地我们不会对游戏的主循环外的任何代码做优化.
    在我们的例子程序中的百分比的划分在真实的游戏中并不是完全的没有特性. 你多半会在一个真实的视觉游戏中发现这个大的执行时

    间的比例是在paint()方法中. 相比于非图形化程序,图形化程序总是要花很长的时间. 不幸的是,我们的图形程序已经被写在了J2ME

    API这一层下,对于改善它们的性能,我们没有多少可以做的.我们可以做的是在用哪个和如何用它们之间做出聪明的决定.

    高级vs低级优化

    我们在该文章随后的地方会看到一些低级代码优化的技术.你会看见它们很容易被嵌入到现有代码中,并且在改善性能的同时相应的降

    低其可读性. 在我们使用那些技术之前,最好还是继续在我们的代码和算法的设计上下功夫.这是高级优化.
    Michael Abrash,"Quake"的一位开发者,一次写道,"the best optimizer is between your ears"(最好的游戏器就在你的两耳之

    间).这有不只一种方法而且如果如果实现花更多的时间来思考正确的做事的方式,你会得到极大的回报. 使用正确的算法所带来的性能

    提升,会比用低级优化技术在普通算法上作优化得到的提升大很多. 你用低级技术可能会得到几点百分比的提升,但是请首先从最上层

    开始并且使用你的大脑(你可以在你的两耳之间找到它).
    现在让我们来看一看我们在paint()方法中作了什么.每次在屏幕上打印消息"n ms per frame"时,我们调用了

    Graphics.drawString() 16次. 我们不知道drawString的任何内部作业,但是我们知道它用掉了大量时间,所以让我们试试其它的方

    式.让我们直接将这个字符串画到一个图片实例上, 然后再画16次这个图片.
    public void paint(Graphics g) {
        g.setColor( COLOR_BG );
        g.fillRect( 0, 0, getWidth(), getHeight() );
        Font font = Font.getFont( Font.FACE_PROPORTIONAL,
                                  Font.STYLE_BOLD | Font.STYLE_ITALIC,
                                  Font.SIZE_SMALL );
        String msMessage = frameTime + "ms per frame";
        Image stringImage =
             Image.createImage( font.stringWidth( msMessage ),
                                font.getBaselinePosition() );
        Graphics imageGraphics = stringImage.getGraphics();
        imageGraphics.setColor( COLOR_BG );
        imageGraphics.fillRect( 0, 0, stringImage.getWidth(),
                                stringImage.getHeight() );
        imageGraphics.setColor( COLOR_FG );
        imageGraphics.setFont( font );
        imageGraphics.drawString( msMessage, 0, 0,
                                  Graphics.TOP | Graphics.LEFT );
        for ( int i  = 0 ; i < DRAW_COUNT ; i ++ ) {
          g.drawImage( stringImage, getRandom( getWidth() ),
                       getRandom( getHeight() ),
                       Graphics.VCENTER | Graphics.HCENTER );
        }
      }
    当我们运行这个版本的软件时,我们看到我们的paint()方法占用的时间百分比减少了一点点.往里看,我们看到drawString()方法只被

    调用了101次,而且现在是敌人啊我Image方法执的次数最多,被调用了1616次。虽然我们做了更多的工作,,但是程序运行得快了一点

    ,因为我们所用的graphics调用要快一点。
    你或许发现了吧一个字符串画到一个图片上会影响显示,因为J2MEbing不支持图片的透明,所以大量的背景被重写了。这是一个

    weruhe优化可能导致你重新审核程序需求的例子。如果你真的需要与文字重合,你可能被迫要用更少的时间来处理。
    这个代码或许好了一点点,但是它仍然有很大的可改进空间。让我们来看一看我们的第一个低级优化技术。
     

    第三页
    循环之外?
    循环多少次,在for()内部的代码就会执行多少次。要改善性能,那么,我们想要尽可能的把循环中的代码移动到循环外。我们可以在

    profiler中看到paint()被调用了101次,并且在它之中的循环又循环了16次。在这两个循环中有哪些我们可以移出来呢?让我们从他

    们的定义说明开始,每当调用paint()时,我们声明了一个字体,一个字符串,一个图片对象和一个图形对象.我们将要把它们移出到该

    类的最前面.
    public static final Font font =
      Font.getFont( Font.FACE_PROPORTIONAL,
                    Font.STYLE_BOLD | Font.STYLE_ITALIC,
                    Font.SIZE_SMALL);
    public static final int graphicAnchor =
                       Graphics.VCENTER | Graphics.HCENTER;
    public static final int textAnchor =
          Graphics.TOP | Graphics.LEFT;
    private static final String MESSAGE = " ms per frame";
    private String msMessage = "000" + MESSAGE;
    private Image stringImage;
    private Graphics imageGraphics;
    private long oldFrameTime;

    你会发现,我把Font对象变成了一个公共的常量.这一点在你的程序中通常是有用的,你可以把你所要用到的字体声明都集中到一个地方

    .我发现anchor也一样,所以我也把文本和图像坐标放到了一起.对这些的预处理,保持了这些运算--虽然不怎么重要--在循环之外了.
    我把MESSAGE也变成了一个常量.那是因为Java喜欢到处创建字符串对象.字符串如果没有被控制,它们可能导致大量的内存消耗.不要

    把它们留给自动回收,否则你很可能会遇到内存泄露,那最终会影响你的程序性能,特别是当垃圾回收器被调用得过于频繁时.字符串创

    造垃圾,而垃圾不好.用一个字符串常量减少了这类问题.稍后我们会看到如何运用一个StringBuffer来完全的阻止字符串滥用带来的

    内存流失.
    既然我们把那些变成了实例变量,我们需要在构造函数里面添加这些代码:
    stringImage = Image.createImage( font.stringWidth( msMessage ),
                                     font.getBaselinePosition() );
    imageGraphics = stringImage.getGraphics();
    imageGraphics.setFont( font );
    另一个很酷的对于图形对象的大写字符的事是,我们可以设置一次字体然后就可以忘掉它了,不用每次在循环中都设置一次. 每次我们

    还需要用fillRect()擦去图片对象. 热情的编码者可能会发现这里有一个机会从同一个图片创建两个图形对象,然后为fillRect()的

    调用预设其中一个的颜色为COLOR_BG,并为 drawString()的调用预设另一个的颜色为COLOR_FG.不幸地,对同一个图片的多次调用

    getGraphics()没有被定义,在不同的平台上不一样,于是你的技巧可能在Motorola上有效但在NOKIA上不行.如果不确定,就不做.
    还有另一种改进我们的paint()的方法.再次使用我们的大脑我们认识到,如果从上次调用以来frameTime的值改变了,那么我们只需要

    重画这个字符串.那是我们的新变量oldFrameTime到来的地方,下面是新的方法:
    public void paint(Graphics g) {
      g.setColor( COLOR_BG );
      g.fillRect( 0, 0, getWidth(), getHeight() );
      if ( frameTime != oldFrameTime ) {
        msMessage = frameTime + MESSAGE;
        imageGraphics.setColor( COLOR_BG );
        imageGraphics.fillRect( 0, 0, stringImage.getWidth(),
                                stringImage.getHeight() );
        imageGraphics.setColor( COLOR_FG );
        imageGraphics.drawString( msMessage, 0, 0, textAnchor );
      }
      for ( int i  = 0 ; i < DRAW_COUNT ; i ++ ) {
        g.drawImage( stringImage, getRandom( getWidth() ),
                     getRandom( getHeight() ), graphicAnchor );
      }
      oldFrameTime = frameTime;
    }
    现在Profiler显示OCanvas的paint总共所花费的时间百分比已经降低为42.01%了.对比结果frameTime在 paint()中的调用,对

    drawString()和fillRect()的调用次数已经从101变为69了.那时一个不错的节约,没有多少可以做的了,现在是该认真的时候了.你优

    化得越多,它就变得越困难.现在我们要去挖掉最后几块循环中的代码.我们现在正在剃去非常小的百分比或者说百分比的碎片了,但是

    我们比较幸运,他们加起来还是比较可观的.
    让我们从一些简单的开始.让我们调用那些函数一次并且把结果暂存在循环之外,而不是每次都调用getHeight()和getWidth(). 下一

    步,我们将停止使用字符串并手动使用StringBuffer来做所有事.依靠在Graphics.setClip()的调用中限制绘画区域,我们将剃掉一些

    对drawImage()的调用.最后,我们将避免在循环中对java.util.Random.nextInt()的调用.
    这是些新的变量...
      private static final String MESSAGE = "ms per frame:";
      private int iw, ih, dw, dh;
      private StringBuffer stringBuffer;
      private int messageLength;
      private int stringLength;
      private char[] stringChars;
      private static final int RANDOMCOUNT = 256;
      private int[] randomNumbersX = new int[RANDOMCOUNT];
      private int[] randomNumbersY = new int[RANDOMCOUNT];
      private int ri;
    ...这里是我们构造函数里的新代码:
    iw = stringImage.getWidth();
    ih = stringImage.getHeight();
    dw = getWidth();
    dh = getHeight();
    for ( int i = 0 ; i < RANDOMCOUNT ; i++ ) {
      randomNumbersX[i] = getRandom( dw );
      randomNumbersY[i] = getRandom( dh );
    }
    ri = 0;
    stringBuffer = new StringBuffer( MESSAGE+"000" );
    messageLength = MESSAGE.length();
    stringLength = stringBuffer.length();
    stringChars = new char[stringLength];
    stringBuffer.getChars( 0, stringLength, stringChars, 0 );

    你现在可以看到我们在预处理显示(Display)和图片(Image).我们也在暂存512次getRandom()的调用的结果,有了StringBuffer也不

    再需要msMessage这个字符串.当然,肉依然在paint()方法中:
      public void paint(Graphics g) {
        g.setColor( COLOR_BG );
        g.fillRect( 0, 0, dw, dh );
        if ( frameTime != oldFrameTime ) {
          stringBuffer.delete( messageLength, stringLength );
          stringBuffer.append( (int)frameTime );
          stringLength = stringBuffer.length();
          stringBuffer.getChars( messageLength,
                                 stringLength,
                                 stringChars,
                                 messageLength );
          iw = font.charsWidth( stringChars, 0, stringLength );
          imageGraphics.setColor( COLOR_BG );
          imageGraphics.fillRect( 0, 0, iw, ih );
          imageGraphics.setColor( COLOR_FG );
          imageGraphics.drawChars( stringChars, 0,
                                   stringLength, 0, 0, textAnchor );
        }
        for ( int i  = 0 ; i < DRAW_COUNT ; i ++ ) {
          g.setClip( randomNumbersX[ri], randomNumbersY[ri], iw, ih );
          g.drawImage( stringImage, randomNumbersX[ri],
                       randomNumbersY[ri], textAnchor );
          ri = (ri+1) % RANDOMCOUNT;
        }
        oldFrameTime = frameTime;
      }
    我们现在正在用一个StringBuffer来写我们的消息中的字符.相比于在开头插入一个字符,在StringBuffer的后面添加要容易得多,所

    以我把字符显示的顺序调换了,现在frameTime在消息的最后了,比如:"ms per frame:120".我们每次重写最后的几位frameTime字符

    ,保持消息的一部分不变. 像这样明白的运用StringBuffer会节约paint()方法内系统从创建到销毁字符串的时间.它是额外的工作,但

    值得做.注意,我在把 frameTimer强制转换为一个整数.我发现用append(long) 导致了一个内存泄露.我不知道为什么,但这是一个为

    什么你需要用软件注意事情的例子.
    我们用font.charsWidth()来计算消息图片的宽度,以让我们可以画得最少.我们使用均衡字符,来使"ms per frame:1"的宽度比这绘

    制它图片小,我们用Graphics.setClip(),所以我们就不需要画更多的. 这同样意味着我们只需要填充一个足够遮掩我们需要的区域那

    么大的一个矩形.我们希望绘图省下的时间比调用font.charWidth()花去的时间多.
    在这里这可能不会带来多大区别,但它确实是一个绘制玩家的分数到屏幕上的不错的技术.那种情况下,在绘制0分和150,000,000分之

    间有着巨大的差别. 这多少是因为font.getBaselinePosition()的不正确的返回值,这个值好像和font.getHeight()的返回值一样,

    啊! (叹气)
    最后,我们刚刚搞定了我们的两个数组中预先计算的"随机"数,这节约了我们产生随机数的一些调用.注意用取模运算来实现一个循环数

    组的用法.注意我们用同一个TextAnchor绘制图片和字符串,所以现在setClip()工作正常.
    我们已经在一个灰色地带,怀着对这个版本的代码产生的数据。Profiler高速我们这个代码比没有这些改变的代码在paint()方法里

    多花了大概7%多一点的时间。对font.charsWidth()的调用可能是个原因,它占了4.6%。(这并不多,但它可以被减少。注意我们每

    次都会获取 MESSAGE字符串的宽度。我们可以简单的在循环体之间计算它,并简单的把它加到frameTime的宽度上。)同样,新的对

    setClip()的调用被标识为0.85%,而且看起来大大的增加了drawImage所占用的时间百分比(从27.58%到33.94%)。
    到这一点了,看起来所有额外的代码肯定会使执行慢下来,但是程序产生的书记和这个假设矛盾了。在模拟器上的数据上下波动,看起

    来好像没有长时间的测试,是不能下决定了,但是我的i85s报告说额外的代码比不加要快一点点,在没有对setClip()或者

    charsWidth()时数据是 37130毫秒,而两个都有的时候是36540。我做了我的耐心所能忍受的那么多次,结果都一致。这使执行环境

    差异这一点的影响突出起来。一旦你到了一个你不能确定会不会有进展的地方,你可能会被迫继续所有在硬件上的测试,这需要大量的

    对JAR文件的安装和卸载。
    看起来我们已经从我们的图形程序段压榨出了大量的性能。现在是对我们的work()方法进行同样的高级和低级优化的时候了。让我们

    来回顾一下那个方法:
      public synchronized int work( int[] n ) {
        r = 0;
        for ( int j = 0 ; j < DIVISOR_COUNT ; j++ ) {
          for ( int i = 0 ; i < n.length ; i++ ) {
            divisor = getDivisor(j);
            r += workMore( n, i, divisor );
          }
        }
        return r;
      }
    每次在run()中的循环,我们都传递一个数组参数。在work()中的循环外计算了我们的除数,然后调用workMore()来做这个除法。这

    里所有事都错了,你可能也发现了。因为一开始,程序员已经把getDivisor()的调用放到了循环内。如果j的值在循环内部没有改变,

    那么除数是不变的,真的属于内循环外面。
    但是让我们多想一想,这个调用本身就是完全不必要的。下面的代码做了同样的事情...
      public synchronized int work( int[] n ) {
        r = 0;
        divisor = 1;
        for ( int j = 0 ; j < DIVISOR_COUNT ; j++ ) {
          for ( int i = 0 ; i < n.length ; i++ ) {
            r += workMore( n, i, divisor );
          }
          divisor *= 2;
        }
        return r;
      }
    ...没有对getDivisor()的调用。现在我们的profiler告诉我们run()方法花了23.72%的时间,对应于我们做这些改进以前的38.78%

    。请总是在使用低级优化技术之前首先使用你的大脑来优化。接下来,让我们看一看它们中的一些技术。
     
    第四页
    低级优化
    所有的程序员都对子程序和函数---为了避免在多个地方重复,把程序中共用的代码从应用程序中提出来--的概念很熟悉。不幸地,这

    个通常的 “好”变成习惯会影响性能,因为方法的调用会带来一定量的开销。最简单的减少对一个方法的调用所耗费的时间的方法是

    ,仔细地挑选他们的声明修饰语。我们的程序员已经很小心了,已经把他的work()和workMore()方法同步了,以防万一其它的线程同

    时调用了它们。这很不错,但是如果我们对性能很看重,我们常常要做出一些牺牲,今天我们的牺牲是安全的。
    好了,我们知道不会有其它人会调用这些方法,那么我们可以不怎么担心地把他们同步起来。还有什么其它的可以做?让我们来看一看

    这个方法类型的列表:
    *synchronized 该方法是最慢的,因为需要获取一个对象锁
    *interface 该方法是次慢的
    *instance 这个方法居中
    *final 该方法比较快
    *static 该方法是最快的
    所以我们不应该让所有的都是synchronized,而且看起来我们甚至可以把work()和workMore标示为final static方法。这样做会减

    少1%模拟器中run()方法所花的时间。
    另一个影响方法调用性能的因素是,传递给该方法的参数的个数。我们调用了workMore()51712次,每次都传递一个整形数组和两个

    整形变量给这个参数并返回一个整形变量。在这个有点微不足道的例子中,可以很容易地把workMore()方法拆散到work()的方法体中

    来完全地避免这个调用。在真实世界中,这是个很难的决定,特别是当这意味着需要把代码复制到你程序周围的时候。在profiler上

    测试一下来看和没做这一步之前到底有多大的差别。如果你不能把所有的方法去掉,试着减少你传递的参数的个数。参数越多,开销就

    越大。
      public final static int work( int[] n ) {
        divisor = 1;
        r = 0;
        for ( int j = 0 ; j < DIVISOR_COUNT ; j++ ) {
          for ( int i = 0 ; i < n.length ; i++ ) {
            r += n[i] * n[i] / divisor + n[i];
          }
          divisor *= 2;
        }
        return r;
      }
    哇哦!去掉workMore()的调用把run中的时间开销砍到了9.96%。现在开始,一路上都会上升。让我们来看一看两个最基本的优化技术

    ---降低强度(strength reduction)和解开循环。
    降低强度就是将一个慢一点的操作用一个相对快一点的完成同样的工作的去替换。最普通的就是使用位移运算符,它和对2的乘除运算

    是相等的。比如说,x>>2和x/4是相等的(2的2次幂),x<<10和x*1024是相等的(2的10次幂)。一个令人惊讶的巧合,我们的除数

    总是2的幂方(是不是很幸运!),所以我们可以用位移来替换那些除法。
    解开循环减少了代码控制流的开销,但在循环中做更多的操作,少执行几次循环,或者完全把循环去掉。由于我们的DIVISOR——

    COUNT只是8,解开我们的循环变得简单起来。
    public final static int work( int[] n ) {
      r = 0;
      for ( int i = 0 ; i < n.length ; i++ ) { r +=  n[i] * n[i]  + n[i]; }
      for ( int i = 0 ; i < n.length ; i++ ) { r +=  (n[i] * n[i] >> 1) + n[i]; }
      for ( int i = 0 ; i < n.length ; i++ ) { r +=  (n[i] * n[i] >> 2) + n[i]; }
      for ( int i = 0 ; i < n.length ; i++ ) { r +=  (n[i] * n[i] >> 3) + n[i]; }
      for ( int i = 0 ; i < n.length ; i++ ) { r +=  (n[i] * n[i] >> 4) + n[i]; }
      for ( int i = 0 ; i < n.length ; i++ ) { r +=  (n[i] * n[i] >> 5) + n[i]; }
      for ( int i = 0 ; i < n.length ; i++ ) { r +=  (n[i] * n[i] >> 6) + n[i]; }
      for ( int i = 0 ; i < n.length ; i++ ) { r +=  (n[i] * n[i] >> 7) + n[i]; }
      return r;
    }
    有两个重点,第一,你会发现解开我们的循环需要我们复制一些代码。这是你在J2ME中想要的最后一件事,程序员们总是在与JAR作战

    ,但要记得 JARing(打包)过程包括了压缩,而压缩工作对重复的代码最有效,所以上面的代码可能不会像你所想的那样对你的jar

    文件大小产生大的影响。再者,这都是代价交换。你的代码可以很小,很快,易读,任意选择其中的两个。第二点是位移操作符合乘除

    运算的优先级不一样,所以你常常需要在表达式周围放置括号,而乘除运算符则不需要。
    解开循环和使用位移操作符提升了1%多一点,不算坏。现在让我们把注意力集中到数组访问上。数组在C中是快速的数据结构,但因为

    那个原因他们也很危险---如果你的代码访问了超过数组尾部的地址,那么你就重写了你不应该访问的内存区域,而且结果通常都是可

    怕的。
    相比之下,java是一个很安全的语言---像那样执行到数组尾部以外会简单地抛出一个 ArrayIndexOutOfBoundsException(一个数组

    地址越界异常)。每次访问数组的时候系统都检查数组的下标是否有效,这使得数组的访问比C中要慢。再者,对于java内部对于数组

    的处理我们没有什么可以做的,但是我们可以在它周围作一些聪明的决定。在上面的代码中,举例来说,我们访问了n[i]24次。我们

    可以通过把n[i]的值存储于一个变量来省略掉很多那样的数组访问。稍微高级一点的想法同样揭示了我们可以用聪明的多的方式重新

    安排他们,像这样...
      private static int divisor;
      private static int r;
      private static int ni;
      public final static int work( int[] n ) {
        r = 0;
        for ( int i = 0 ; i < n.length ; i++ )  {
          ni = n[i];
          r +=  ni * ni + ni;
          r +=  (ni * ni >> 1) + ni;
          r +=  (ni * ni >> 2) + ni;
          r +=  (ni * ni >> 3) + ni;
          r +=  (ni * ni >> 4) + ni;
          r +=  (ni * ni >> 5) + ni;
          r +=  (ni * ni >> 6) + ni;
          r +=  (ni * ni >> 7) + ni;
        }
        return r;
      }
    ...把run()中耗费的时间减少到了6.18%,一点也不坏。在我们继续之前,让我多说一点关于数组的事。一个稍微高级的优化(也就是

    “thought”)可能揭示了数组可能不是这里必要的正确的数据结构。想想一个链表或者一些其他的结构,如果他们将提升性能。第二

    点,如果你将要使用一个数组而且也需要复制它的内容到另一个数组,请总是使用System.arraycopy()。完成同样的工作,它会比自

    己写的函数要快一点。最后,数组的性能比java.util.Vector对象的性能要好一点。如果你需要一种Vectors提供的功能,想想自己

    写代码并测试一下,确保你的代码要快一点。
    好了,我们真的把优化的事情做完了。我们刚刚介绍了暂存数组变量和该变量的平房的另一对变量。你可能在想为什么那些变量在方法

    体的声明之外就被声明了。他们在循环外是因为每次定义一个整形数都有一点开销,而且我们需要保持他们在循环外也有效,对么?错


    什么都不假设,我们确实为整形变量的声明节约了时间,但是如果那些变量在方法内部的定义为局部的,代码实际上可能会更慢。这是

    因为局部变量表现得更好,因为JVM解释一个在方法外声明的变量会花更长的时间。所以让我们把他们变为局部变量。
    最后,我们可以微微改变一下for()循环。计算机处理和零比较比处理和其他的非零数比较要快。那意味着我们可以改变我们的循环的

    顺序并像这样重写方法,我们就可以和零比较:
    public final static int work( int[] n ) {
        int r = 0;
        int ni;
        int nis;
        int i;
        for ( i = n.length ; --i >= 0 ; ) {
          ni = n[i];
          nis = ni * ni;
          r +=  nis  + ni;
          r +=  (nis >> 1) + ni;
          r +=  (nis >> 2) + ni;
          r +=  (nis >> 3) + ni;
          r +=  (nis >> 4) + ni;
          r +=  (nis >> 5) + ni;
          r +=  (nis >> 6) + ni;
          r +=  (nis >> 7) + ni;
        }
        return r;
      }
    就是它了!这个代码可能会快一点,但是 profiler的结果不是那么的明显,清楚地是这个方法变得难懂了。或许这里还有更多可改进

    的空间,但是让我们再看一下paint()方法,看看我们所学的里面有没有什么在这儿可以介绍的。
    记住我们所学的关于局部变量的么?如果你被迫要用一个实例变量,而且你在一个方法中引用了那个变量多次,它可能值得你创建一个

    局部变量来让 JVM只处理那个引用一次。你将引入一个声明和一个赋值,这会让程序变慢,但根据经验,如果一个变量被引用了超过

    两次,我们将会使用这个技术。
    我们同样可以在我们的paint()方法中运用降低强度,用一个循环移位计数器来代替取模运算符。这只有在我们的随机数暂存数组的长

    度是2的倍数(令人惊讶地)的时候是可能的。最后,我们可以把我们的比较运算改成总是和零比较。这里是新的改进过的paint()方

    法:
      public void paint(Graphics g) {
        StringBuffer sb = stringBuffer;
        Graphics ig = imageGraphics;
        char[] sc = stringChars;
        int sl;
        int ml = messageLength;
        int ril = ri;
        int iw = 0;
        g.setColor( COLOR_BG );
        g.fillRect( 0, 0, dw, dh );
        if ( frameTime - oldFrameTime != 0  ) {
          sb.delete( ml, stringLength );
          sb.append( (int)frameTime );
          sl = stringLength = sb.length();
          sb.getChars( ml, sl, sc, ml );
          iw = font.charsWidth( sc, 0, sl );
          ig.setColor( COLOR_BG );
          ig.fillRect( 0, 0, iw, ih );
          ig.setColor( COLOR_FG );
          ig.drawChars( sc, 0, sl, 0, 0, textAnchor );
        }
        for ( int i  = DRAW_COUNT ; --i >=0  ; ) {
          g.setClip( randomNumbersX[ril], randomNumbersY[ril], iw, ih );
          g.drawImage( stringImage, randomNumbersX[ril],
                       randomNumbersY[ril], textAnchor );
          ril++;
          ril &= 255;
        }
        ri = ril;
        oldFrameTime = frameTime;
      }
    此外,怀着对profiler结果的敬意,我们已经在路的尽头了。这些改变并不影响图形函数被调用的次数,所以最好情况下这个差别也

    会很小。但是当我们把所有对我们的work()方法的改变组合起来并且装在新的JAR到设备上时,这个差别是很大的。我的motorola

    i85s现在在14030毫秒内完成了测试----快了两倍多!
    这个代码还有最后一点需要改变。我把它放到最后是因为它没有书写得特别好,并且我的经验是它的表现在不同的实现间不一样。看着

    OCanvas中的start()和run()方法,你可以看到我已经用了一个单独的动画线程。这是Java中处理动画的传统方法。在游戏中用这个

    技术的一个问题是,每当重复循环时,我们被迫等待系统事件,比如说按键或者一个命令被传输了。毕竟我们在一个同步块中调用

    wait()方法等待。这是艰辛的优化代码。毕竟我们的困难工作优化了其他所有事情,但我们在最激烈的时候实际上什么正确的事情也

    没能做。更坏的是,为WAIT_TIME得到一个好的数据并不简单。如果我们 wait()太长,游戏就变慢了。如果我们没有wait()足够的时

    间,按键可能被错过然后游戏停止了对用户输入的响应。
    J2ME提供了一个这个问题的解决方案,用Display.callSerially()方法。API声明callSerially (Runnable r)"导致了在repaint

    周期完成不久之后,为了和事件流同步,Runnable对象r让其run()被推迟调用“[原文是:"causes the Runnable object r to

    have its run() method called later, serialized with the event stream, soon after completion of the repaint

    cycle"]。通过使用callSerially(),我们可以完全的取消对wait()的调用。系统会保证我们的work()和paint()方法和用户输入程

    序同步地被调用,那样游戏就会保持可响应性。这里是一些新的方法...
      public void start() {
        started = frameStarted = System.currentTimeMillis();
        loopCounter = result = 0;
        finished = false;
        exitStatus = EXIT_DONE;
        run();
      }
      public void run() {
        frameTime = System.currentTimeMillis() - frameStarted;
        frameStarted = System.currentTimeMillis();
        if ( midlet.running && !finished ) {
          result += work( numbers );
          repaint();
          display.callSerially(this);
          loopCounter++;
          finished = ( loopCounter > LOOP_COUNT );
        }
        else {
          elapsed = System.currentTimeMillis() - started;
          midlet.exitCanvas( exitStatus );
        }
      }
    ...此外我们还需要声明并得到一个Display的句柄:
       Display display = Display.getDisplay( midlet );
    没有了对wait()的调用,现在我的i85s在10180毫秒内完成了代码的运行---差不多省了40%。你可能希望更大的性能提升,在得知我

    们刚刚消除了100次50毫秒的对wait()的调用,但是请记住这个技术也是关于用户输入响应的。
    再次,让我强调一下这个动画方法需要小心使用。在使他在NOKIA上工作的时候我遇到的麻烦,而且他们所有的示例代码(即使是游戏

    代码)都使用了wait()技术。即使是在Motorola上,我在动画Canvas上添加了命令对象的时候,使用callSerially()的时候也遇到

    了问题。在你在家尝试这个之前请小心测试。
     
    第五页

    其他的技术
    一个我不能在我的示例程序中包含的技术是,最佳的使用switch()。Switch非常普遍的用于实现有限状态自动机(Finite State

    Machines),在为非玩家角色的行为控制做人工智能的代码时。在你使用switch的时候,像这样写代码是一个好的编程习惯:
      public static final int STATE_RUNNING = 1000;
      public static final int STATE_JUMPING = 2000;
      public static final int STATE_SHOOTING = 3000;
      switch ( n ) {
        case STATE_RUNNING:
          doRun();
        case STATE_JUMPING:
          doJump();
        case STATE_SHOOTING:
          doShoot();
      }
    这没有什么不对的,这些变量很不错而且离得很远,万一我们想要加一个在RUNNING和JUMPING之间的变量,像 STATE_DUCKING =

    2500。但是显然switch选项可以被编译为一个两字节的代码,如果所用的整数紧靠在一起那么这个两字节的代码会更快,所以这会更

    好:
      public static final int STATE_RUNNING = 1;
      public static final int STATE_JUMPING = 2;
      public static final int STATE_SHOOTING = 3;
    在使用定点数学库(Fixed Point math library)的时候,有一些优化你可以做。首先,如果你除了一个相同的数很多次,你应该计

    算出那个数的倒数然后把运算改变为执行一个乘法。乘法要比除法快一点。所以不是...
      int fpP = FP.Div( fpX, fpD );
      int fpQ = FP.Div( fpY, fpD );
      int fpR = FP.Div( fpZ, fpD );
    ...你应该把它重新写成这样:
      int fpID = FP.Div( 1, fpD );
      int fpP = FP.Mul( fpX, fpID );
      int fpQ = FP.Mul( fpY, fpID );
      int fpR = FP.Mul( fpZ, fpID );
    如果你在每一帧要做数百次的除法,这会有所帮助。第二点,不要默认你的FP数学函数库不错。don't take your FP math library

    for granted.  如果你有它的源代码,打开它然后看一下里面发生了什么。保证所有的方法都被声明为final static并看看有没有机

    会优化它的代码。比如,你可能发现这个乘法方法需要把int强制转换为long然后再转换回来:
    public static final int Mul (int x, int y) {
      long z = (long) x * (long) y;
      return ((int) (z >> 16));
    }
    那些转换要花时间。冲突检测使用边界圆球或者半球(bounding circles or spheres)包括将int的平方相加。那会产生一些大的

    可能会溢出你的int数据类型的最大值的数字。要避免这个,你可以写下自己的返回一个long型数的平方函数:
        public static final long Sqr (int x) {
          long z = (long) x;
          z *= z;
          return (z >> 16);
        }
    这个优化的方法避免了两个转换。如果你要做大量的定点计算,你可能要考虑把所有的主游戏循环中的调用替换为long型的。那会节

    约大量的方法调用和参数传递。你可能也发现当这个计算被手动写出的时候,你可以减少类型转换所需要的次数。如果你嵌套一些对你

    的库的调用,这是尤其正确的。比如:
        int fpA = FP.Mul( FP.toInt(5),
                        FP.Mul( FP.Div( 1 / fpB ),
                        FP.Mul( FP.Div( fpC, fpD ),
                        FP.toInt( 13 ) ) ) );
    花一些时间Take the time来拆开这些像这样的嵌套调用,然后看你是否能减少类型转换的次数。另一个方式是避免到long类型的转

    换,如果你知道涉及到的数字足够小以至于他们肯定不会导致溢出。
    在高级优化上,你应该看一些游戏设计上的文章。大量的已知的游戏设计中问题,比如3D几何和碰撞检测已经被非常优雅和有效地解

    决了。如果你找不到java源代码,你很可能会找到C源代码或者伪代码来转换。例如,边界检查是一个普通的技术,我们已经在paint

    ()方法中用到了。我们只需要清除帧到帧之间所改变的那部分屏幕,而不是每次都把整个屏幕清除。因为图形程序相对来说较慢,你

    会发现这额外的管理的代价---需要明了屏幕上哪些部分需要被清除--值得付出。
    一些手机制造商提供了一些私有的API来帮助程序员们处理一些J2ME表现出来的的限制,比如声音的欠缺,图片透明度的欠缺,等等。

    例如, Motorola提供了一个浮点数学库来在芯片上做浮点运算。这个库比最快的定点数学库快很多,精度也高很多。使用这个函数会

    完全破坏你代码的可移植性,当然,如果在许多不同的手机上运行不是关键,他们是一个可选项。
    结论
    *只优化需要的代码
    *只在有价值的地方优化
    *用profiler来找要优化的地方
    *在具体的设备上profiler无能为力,在硬件上使用System timer
    *在于用低级技术之前,总是先研究你的代码并且试着改进算法
    *绘图是慢的,所以尽量节俭地使用图形调用
    *在可以减少绘制区域的地方使用setClip()
    *尽可能的把东西放到循环之外
    *拼命地预先计算和暂存
    *字符串带来垃圾,垃圾不好,所以使用StringBuffers来代替
    *什么都不假设
    *可能就使用static final方法,避免synchronized修饰符
    *传递尽可能少的参数到经常调用的方法
    *如果可能,完全地去掉函数调用
    *解开循环
    *对2的幂的乘除运算用位移运算代替
    *你可以使用位运算符代替取模运算来实现循环
    *试着用零来代替和其他数的比较
    *数组访问比C要慢,所以暂存数组元素
    *消去公共的子表达式
    *局部变量要比引用变量快
    *如果可以callSerially()就不要wait()
    *在switch()中使用小的变量作选项
    *检查定点数学库并且优化它
    *拆开嵌套的FP调用来减少类型转换
    *除法比乘法慢,所以用乘于倒数来代替除法
    *用使用过和测试过的算法
    *为了保护可移植性,小心地使用私有高性能API

    下一步去哪里?
    优化是魔法。任何的计算机的心脏都是CPU,java的心脏在虚拟CPU,JVM(java虚拟机)。要榨干虚拟机的每点性能,你需要了解大

    量的表层以下的事情是如何工作的。特别地,你需要知道哪些事情JVM可以做得快,哪些慢。寻找有java里层工作的可靠信息的网站。

    你不必要学习如何按字节来写程序,但是你懂得越多,就越容易跟上优化你的程序性能的新方式。
    没有什么能够代替经验。你会及时地发现关于J2ME的性能特性和所开发的手机的你个人的秘密。即使你不能编出有独特特性的代码,

    你可以用它设计你的下一个游戏。在开发我的游戏的时候,我发现调用5次drawImage()来分别绘制5个有25像素的图片要比调用它一

    次来绘制一个五倍大小的图片慢得多。这个只是肯定会帮助我设计我的下一个游戏。
    祝你好运,玩得开心!
    资源:
    1. J2ME's official web site contains the latest on what's happening on this front.
    2. Like wireless games? Read the Wireless Gaming Review.
    3. Discuss J2ME Game Development at j2me.org
    4. A great site on many aspects of Java Optimization
    5. Another great site on Optimization
    6. Many articles on J2ME performance tuning
    7. The amazing Graphics Programming Black Book by Michael Abrash
    8. The Art of Computer Game Design by Chris Crawford
    关于作者:
    Mike Shivas has been playing video games since before the advent of the 8-bit home microcomputer. He has been

    programming in Java since 1996, has consulted for MasterCard on wireless solutions and is the published

    author of several J2ME video games. Readers may contact Mike at mailto:mshivas@hotmail.com?subject=FastCode.

    J2ME性能优化之—优化方法探讨

     

            对 代码进行优化的最简单办法就是首先不要调用这些代码。这并不是说要删除这些代码,也许可以用其他办法来调用它们(后者事实减少对它的调用)。游戏的主循环 是游戏运行的最主要做的事情,应该更多地考虑是否可以不用或减少对属于这个区域内的代码的调用。在前一片文章中介绍了方法和内存评测工具的使用,但它们只 能帮助我们找出究竟是哪段代码降低了程序的运行速度,下面的内容是参考了其他资料整理出来的优化代码的方法。

     

        代码优化的技术大致分为两个主要方面:高级优化,从使用的整体算法和结构出发进行的优化;低级优化,集中于孤立的代码片断(通常为方法中的代码)的优化。下面分别讨论两方面的优化:

    一,高级优化

    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)不要浪费时间来将一个对象初始化为null0java虚拟机会替我们完成这样的初始化

    7)多思考新方法,这会使我们的大脑运转的更快。

    8)如果可能,尽量使用static,它们运行都很快。它同时适用于方法和域,这条规则就是,如果它可以是静态的,那么就把它声明为静态的。

    9)避免类型转换。

    10)程序优化的时候要有所取舍,要多捉摸了。大家有什么好的建议?一起来壮大吧。共同探讨:zxhwolfe@hotmail.com

     

    参考资料:

    J2ME游戏编程》

    www.j2medev.com

     

     

     

    附:关于j2me程序的调试

    使用eclipseME+WTK2.1进行J2ME应用程序调试(debug)-- 引用mingjava的帖子。

    选择windows->preferences->java->debug  不要选择suspend execution的前面两个选项,在debuger timeout选项中  第一个时间至少设置为15000ms  这样就可以调试了。

    J2ME性能优化之--方法和内存的评测

     

        本文是对J2ME游戏性能优化的初步探讨,有不足和错误之处还请及时指出。(请联系zxhwolfe@hotmail.com.

    本文目的是使我们的游戏能够更快更稳定的运行,J2ME的有限处理能力和存储能力,使得节省设备资源就显得尤为重要。对代码进行优化和提速我个人觉得是一件恐怖的事情,嘿嘿。但当你解决代码的性能瓶颈,使游戏能够流畅运行又是一件很有成就感的事情。

        本文共分两部分:第一部分介绍我们如何使用工具对方法和内存进行评测;第二部分和大家探讨些优化程序的方法。

     

        性能评测所要做的就是,在游戏运行的时候,记录代码每一部分对内存和处理器的使用情况。在评测的过程中,使我们知道什么地方内存使用过大,什么原因导致游戏运行变慢。

        进行评测我们使用的是Sun公司J2ME Wireless ToolkitWTK2.2)。

    J2ME Wireless Toolkit 提供了若干用于监视应用程序性能的工具。这些工具能够帮助您调试和优化代码。

    • 事件探查器列出应用程序中每个方法的使用频率和执行时间。
    • 内存监视器显示应用程序运行时内存的使用情况。
    • 网络监视器显示应用程序传送和接收的网络数据。网络监视器支持多种网络协议,包括 HTTPHTTPSSMS CBS
    • 跟踪将低级信息输出到 KToolbar 控制台。

    提示:监视功能可能会降低应用程序的执行速度。

     

    方法性能评测:

    事件探查器跟踪应用程序中的每个方法。对某次特定的应用程序运行,事件探查器都计算出每个方法占用的时间,以及调用每个方法的次数。当应用程序结束运行并关闭仿真器后,将弹出事件探查器,您可以从中浏览所有的方法调用信息。

    要打开事件探查器,请从 KToolbar 菜单中选择编辑”>“偏好...”。单击监视选项卡。(即Preferences如果您想查看所有系统实现方法的配置处理信息,请选择显示系统类。否则,事件探查器将只显示包含对应用程序方法调用的系统方法。

    要打开时间探查器也可以这样:%WTK安装路径%--binà prefs.exe来启动工具。

    启动工具并选择“监视”,再勾选上事件探查器显示应该是这样:

     

    确定后,即可以。下次启动手机模拟器运行游戏结束后就会弹出事件探查器,并显示有关应用程序中所有方法调用的信息。

    提示:只要勾选了事件探查器每次模拟器结束的时候都会弹出,所以记得不用及时取消该功能,因为它影响模拟器的速度,内存监视器尤为明显。不论模拟器是在什么IDE中打开都会弹出方法事件探查器。

     

     

    事件探查器显示两种类型的信息:

    • 方法关系,显示在名为调用图的层次结构列表中。
    • 事件探查器的右侧显示每个方法及其子代的执行时间和调用次数。

    注-从仿真器中获得的配置处理值反映不出真实设备上的实际值。

    调用图显示方法调用的层次结构。调用其他方法的方法显示为文件夹。双击某个方法将其打开,并查看该方法所调用的方法。没有调用任何其他方法的方法显示为灰色圆圈。

    您可以搜索某个特定类或方法名。单击查找...”,并填写名称。搜索从调用图中的当前选择开始执行,直到最后。如果您想搜索完整的调用图,请在单击查找按钮之前选中环绕

    在调用图中单击不同的节点时,事件探查器的右侧就会显示该节点方法的详细信息。

    事件探查器窗口的右侧显示有关方法的详细信息。您可以看到方法名、方法调用次数以及仿真器使用该方法所用的时间。执行时间有四种不同的描述方法:

    • 周期表示方法本身所用的处理器时间。
    • %周期表示方法本身所用总执行时间的百分比。
    • 周期(包括子类)表示某方法及其调用的方法所用的时间。
    • %周期(包括子类)表示某方法及其调用的方法所用的时间占总执行时间的百分比。

    单击任一列,按照该列进行排序。再次单击该列,可以在升序和降序之间切换。

    右窗格显示调用图中当前所选节点中包含的方法。如果您想查看每个方法,请在调用图中单击 节点。

    关于保存就不多说了。

     

    内存性能评测

    许多 MIDP 设备上的内存都不够用。J2ME Wireless Toolkit 中的内存监视器使您可以方便地检查应用程序内存的使用情况。您可以查看应用程序使用的内存总量,以及每个对象的内存使用情况详细列表。

    要打开内存监视器,请从 KToolbar 菜单中选择编辑”>“偏好...”,单击监视选项卡。选择启用内存监视器

    下次运行模拟器时,就会弹出内存监视器窗口,显示一幅随时间变化的应用程序内存使用情况图。由于创建的每个对象都被记录下来,因此内存监视器会使应用程序的启动速度变慢。最好不要同时勾选两种监视器。 

     

     

    您将看到包含以下列的一个表:

    • 名字。对象类名称。
    • 存活。实例数目。有些可以进行资源回收。
    • 总共。应用程序开始运行以来所分配的对象总数。
    • 总计。对象使用的内存总量。
    • 平均。对象的平均大小,由总计大小除以活动实例数目得到。

    单击任一列标题,按照该列进行排序。

    通过从内存监视器窗口菜单中选择查看”>“查找...”,可以搜索某个特定的类名。

    特别注意内存使用超出当前分配的堆(图像中红色虚线表示)的情况。

    关于方法和内存的评测就到这里,希望大家在使用中的经验和心得能够共同交流。WTK中还有其他好用的功能哦。下一部分讨论下j2me的调试方法和优化方法。

     

    本文参考资料:

     

    J2ME Wireless Toolkit 用户指南》

    J2ME游戏编程》  www.j2medev.com

    J2me性能优化,避免内存溢出小结

      

    最 近在做一个手机浏览器的客户端,最初以为一个浏览器的客户端不用考虑太多的内存使用,结果做完才发现在高端的手机上运行没有任何问题,在低端的手机上不是 响应速度过慢,就是内存溢出。所以就开始对代码进行优化,在这个过程总结一些自己的经验,希望对有困惑的朋友有所帮助,有好的办法也希望大家共同探讨,zxhwolfe@hotmail.com

     

    以下内容有些是自己实践所得,有些是参考高人经验或书本上的经验,如有不正确请立即指正;如果侵犯了他人版权请高台贵手,借鉴您的经验不是用于商业,只是希望大家共同进步请不要追究呵呵。

     

           在写程序初期由于是面向midp2.0的手机而且不是游戏的开发,所以没有对性能方面做太多的考虑,大胆的使用了很多vector数组(使用起来方便:))等等,而且程序写的也是随心所欲,似乎不是在手机上开发j2me的东西。结果让我大跌眼镜,没办法只能重新优化,这期间可想而知浪费了很多时间,也使开发效率降低到了最低。说到这,只有一句话奉劝大家,不管你是开发什么样手机程序,一定要把性能放在第一位,不要重蹈我的覆辙。

     

           我的几个vector存放的是链接的内容,文本框的内容,图片的内容和普通文本的内容,每个vector中包含的其他信息还有它所占的行数,它的位置和它的标志。我用线程控制paint(只要目的是实现持续按键的机制),这样的话,每次重绘都要把所有内容重新画到屏幕上,而且还要对当前焦点进行重绘。在重绘的过程中每一次都要遍历一遍几乎所有的vector然后重绘,这样肯定是浪费资源。结果也一样,在稍微低端一点手机上响应明显的慢。优化这样程序,我首先想到的就是把vector用别的方法替换掉,正常的性能使用是字符串-〉一维数组-〉二维数组->vector。一维数组肯定是替代不了我的vector了,那只能是使用二维数组了。费了好大劲把vector转换位二维数组,发现性能上并没有提升多少。大概只节省了20k左右的内存,只能再想别的办法。这里简单说下在手机上获得当前内存和剩余内存的方式:

    long tempMin = 1000000

    long temp = 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自带的内存和方法查看器来判断。相关内容参考我的另外2篇文章:

       J2ME性能优化之--方法和内存的评测:

    /Article/Class2/200603/1650.html

    J2ME性能优化之—优化方法探讨:

    /Article/Class2/200603/1658.html

     

    既然转换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。字符串累加的时候一定要用StringBufferappend方法,不要使用+操作符连接两个字符串。差别很大。而且在循环或某些重复执行的动作中不要去创建String对象,因为String对象是要用StringBuffer对象来处理的,一个String对象应该是产生了3个对象(大概是这样:))。

       2,字符串length()方法来取得字符串长度的时候不要把length放到循环中,可以在循环外面对其取值。(包括vectorsize方法)。特别是循环次数多的时候,尽量把length放到循环外面。

    int size = xmlVector.size();

    for (int i = 2; i < size; i++) {

    。。。

        }

     

    在程序中我曾经误写了这样一句:if(i=5){...},编译器没有报错,而且结果好像是把i的值改变了,没有记清,大家有兴趣可以测验下。书写上尽量认真,否则查找bug的时候可能会折腾死人的。

     

    关于优化方面也就能想起这些来了,如果还有对大家有用的东西我再补充吧,希望大家多提宝贵意见。共同进步嘛。

    March 22

    厂商SDK的下载地址

     

    现在支持Java的手机厂商很多,现将常用的手机厂商SDK和下载地址说明一下:

    1、  Nokia
    Nokia不愧为手机行业的老大,对于j2me的支持也是一流的,有专门的网站提供SDK和各种文档说明。
    网址是:http://forum.nokia.com.cn/sch/index.html

    2、  Siemens
    Siemens对于J2ME的支持也不错,它提供了SDK,模拟器需要独立安装。下载地址如下:
    https://communication-market.siemens.de/portal/main.aspx?LangID=0&MainMenuID=2&LeftID=2&pid=1&cid=0&tid=3000&xid=0

    3、  SonyEricsson
    SonyEricsson SDK以及自己的模拟器,下载地址为:
    http://developer.sonyericsson.com/site/global/docstools/java/p_java.jsp
    http://mobilityworld.ericsson.com.cn/development/download_hit.asp

    4、  Motorola
    Motorola提供了专门的SDK,内部包含模拟器,下载地址为:
    http://www.motocoder.com/motorola/pcsHome.jsp

    5、  SamSung
    SamSung也提供了专门的SDK和模拟器,下载地址为:
    http://developer.samsungmobile.com/eng/front_zone/bbs/bbs_main.jsp?p_menu_id=1500

    6、  NEC:
    NEC也提供了集成模拟器的SDK,下载地址为:
    http://www.nec-mfriend.com/cn/


    J2ME内存占用详解及优化方法

      我想做过J2ME的人,特别是像我这样做手机游 戏的,肯定会对OutOfMemoryError这个异常深恶痛绝,尤其是在老40这样变态的机型上,甚至对这个异常都产生了恐惧。还好我现在总算不做这 个机型了,对那些仍然在为这个机型移植游戏的同志们感到同情。为了能够稍微缓解一下他们的痛苦,也为了广大J2ME的从业者和爱好者能尽量减少与该异常的 见面次数,CoCoMo将把自己的经验分享一下。

      首先了解一下分析内存占用的方法,一般有两种:模拟器自带工具和Runtime类方法。

      模拟器自带工具:WTK貌似带了一个Memory Monitor,而且许多学者人士也夸夸其谈他的使用方法,但我不知道有多少人真正在用。就我对他的了解,首先运行他你的程序会 慢的一塌糊涂,这对游戏开发者来说简直是无法忍受的。但我出于研究目的仍然让他跑了半个小时才发现原来他根本无法显示正确的内存占用量,我载入一张很大的 图片后他的内存线好像只出现了微微的波动又停留在原位,呵,看来的确是拿出来秀的。我一般使用的是7210模拟器自带的内存监视器,模拟的很准,但唯一的 缺点是内存太少,才200K。我也见某些人使用3220的模拟器监视内存,好像内存稍微大一点,我还没来得及尝试就再也不用为老40写程序了,庆幸。
    Runtime类方法:我经常用这个语句System.out.println(Runtime.getRuntime().freeMemory());后来集成进了我的引擎,他能够显示当前剩余内存。不记得有多少次我用它在老40上来寻找内存占用峰值。

      了解了分析内存的方法,来看看内存占用的罪魁祸首:程序和资源。

       程序:类会被编译成class字节码文件随MIDlet的启动加载进内存,而且是一次性全部加入。也就是说MIDlet里类个数越多、单个类程序越长、 类内字符串常量及数据越多,编译后的class文件就越大,载入后占用的内存也越多。我经常在MIDlet类的构造函数里用Runtime方法来查看 MIDlet启动后整个程序占用内存量。

      优化方法:

      1.某些同志将MIDlet程序写成两个类来减少内存占用 量,但是以牺牲Java的OOP特性为代价的。在程序比较大时这种弊端将尤为显见。而且CoCoMo曾经遇到过单个类过大,载入时间过长而违反百宝箱有关 Logo 6秒时间限制的情形。因而我现在的程序加带引擎一般都是6-7个类。

      2.尽量编写优雅的代码,减少函数数量,在程序发布时去掉try catch,最大限度的减少程序行数,这一般都是在老40上没有办法的办法,现在CoCoMo已经不靠这个来省内存了。

      3.将数据及字符串写进文件,在用时方载入内存,不用时设为null。

       4.I/O操作getClass().getResourceAsStream(file);、数据库操做 RecordStore.openRecordStore(name, true);、声音创建Manager.createPlayer();、图像创建Image.createImage(file);会在短时间内占用大 量内存且过后释放,如果MIDlet程序内存剩余量不足则会在这些函数频繁调用时发生内存溢出,产生所谓的内存峰值,尤其在老40上比较普遍。当你再次与 讨厌的OutOfMemoryError碰面时,多用Runtime查找内存峰值发生位置并尽量将这些语句分开调用,并灵活运用System.gc()来 及时回收。

      资源:

      图片:是占用内存的大户,尤其是手机游戏图片资源众多。对图片资源在内存中占用量的计算成为J2ME游戏开发者的经常性工作,CoCoMo来解释一下如何计算图片在内存中的占用量:

      内存占用量=宽*高*像素字节数,其中像素字节数因机型而异。

       例如一张64*64的图片在7210上的内存占用量=64*64*1.5=6144(字节)=6K、在S60上的内存占用量=64*64*2=8192 (字节)=8K。像素字节数因机型而异,例如7210是4096色机型,也就是说用12位来表示一个像素,所以乘上1.5,而S60是65536色的机 型,用16位来表示一个像素,所以乘上2。

      优化方法:

      有些人认为压缩图片可以节省内存,这种想法是错误的。根据上面的解释图片载入内存后只和宽高有关系,和图片数据量大小没有任何关系,压缩图片只能减少jar大小而不能见少内存占用量。

      1.静态法:减小图片大小,宽高小了结果当然小了。根据这个思路出现了动画编辑器之类的工具,像gameloft的波斯王子,人物被分割后使人体的部位可以重用,各部位紧凑放置都是为了较少图片大小,充分利用图片中的每一寸空间。

       2.动态法:减少同一时刻载入内存的图片数。CoCoMo曾经在火影武士项目中遇到过这种情况,当时有6种怪物,如果同时载入内存在老40上肯定爆掉 了,但是每关只出现两到三种怪物,所以每一关只需要载入该关出现的怪物图片即可。现在想起来当时做这个项目在老40上溢出频出,真把我搞死了。

        声音:声音也是比较耗用内存的资源,声音中音轨所占的byte会转化成字节流被载入到内存中。因而减少音轨所占byte即可减少内存耗用量。目前 gameloft的做法是用声音转化工具将mid转化为ott,然后变为ByteArrayInputStream字节流来创建Player。

    j2me内存管理工具类(转贴)


    开发J2AVA ME 程序最需要关注的是内存的占用,开发性能高,适配多种机型的软件不是件容易的事情,我在国庆时间整理下我的开发思路写了这个类,给大家参考下

    package com.gggeye.util;

    import java.util.Hashtable;
    import java.util.Stack;
    import java.util.TimerTask;
    import java.util.Vector;

    import com.gggeye.demo.Logger;

    /**
      *  内存管理,工具类,主要是用于项目的内存控制,
      *  此类相对重要点,因为涉及到各个方面的内存回收
                                                                                      *  此类除了提供一些常用的方法进行回收外,还还提供一个自动回收的机制,不过不建议采用,自动回收机制是是当
      *  空闲内存小于指定内存大小时候,释放内存,回收的时间是5秒回收一次<br/>
      * 创建日期 2007-10-1
      * @author wuhua
      * <p><b>MSN </b> gggeye@hotmail.com
      * <p><b>EMAIL</b> gooogledev@gmail.com </p>
      * <p>网站支持 <a href="http://www.3geye.net">http://www.3geye.net</a></p>
      * <p>网站论坛 <a href="http://www.3geye.net/bbs">http://www.3geye.net/bbs&lt;/a></p>
      * <p>wuhua的博客 <a href="http://wuhua.3geye.net">http://wuhua.3geye.net</a></p>
      */

      public   class MemoryManager implements Runnable {
       
        /**
         * 释放内存,当内存小于addtionMemory时候,进行垃圾回收,主动回收
         * @param addtionMemory
         */
        public final static void release(long addtionMemory) {
            long freeMemory = Runtime.getRuntime().freeMemory();
            //如果空闲的内存小于指定的内存,则回收
            if(freeMemory<=addtionMemory){
                Logger.println("回收前的内存==>" +MemoryManager.freeMemoryKByte());
                System.gc();
                Logger.println("回收后的内存==>" +MemoryManager.freeMemoryKByte());
            }
        }
       
        /**
         * 释放指定缓存对象
         * @param addtionMemory
         * @param cache
         */
        public final static void release(long addtionMemory, Object cache){
            release(addtionMemory);
            if(cache != null){
                if(cache instanceof java.util.Hashtable){
                    Hashtable i = (Hashtable)cache;
                    i.clear();         
                }else if(cache instanceof java.util.Vector){
                    Vector i = (Vector)cache;
                    i.removeAllElements();
                }else if(cache instanceof java.util.Stack){
                    Stack i = (Stack)cache;
                    i.removeAllElements();
                }         
                cache = null;
                System.gc();
            }   
        }
       
        /**
         * 得到系统空闲内存,单位是k
         * @return 返回空闲内存的大小
         */
        public final static String freeMemoryKByte(){
            return freeMemoryByte()/1024 + "k";
        }
       
       
        public final static long  freeMemoryByte(){
            return Runtime.getRuntime().freeMemory();
        }

       
    private static MemoryManager instance ;
        private static TimerTask task;
        /**
         * 自动回收内存机制,此方法,应该在系统第一次调用的时候调用,如果重复调用则会抛出异常
         * @param time
         */
        public static void autoGC(long time) throws java.lang.RuntimeException{
            if(instance == null){
                instance  = new MemoryManager();
                task = TimerTaskManager.getInstace().create(instance, time);
            }else
                throw new RuntimeException("GC is starting...");
        }
       
        public static void colse(){
            if(instance != null) instance = null;
            if(task != null){
                task.cancel();
                task = null;
            }
            
        }

        /**
         * 实现对内存的自动化管理
         */
        public void run() {
            //小于12k内存的时候释放内存
            release(1200000);
             
        }

    }

    j2me内存优化


    out momory 一阵天旋地转内存又溢出了。在手机上这种痛苦经常都有,套一句俗话在手机上用内存必须勒紧裤腰带。虽然现在pc内存上G都不奇怪,可是在手机上却只能以K 来记,可能某位同志会马上跳出来说也有上M的,记住中国还不富大多数手机都是低端手机。写手机程序让我仿佛回到了dos时代(自我安慰一下那个时代也炼出 了不少高手说不定我是下一个)。言归正传做内存优化可以归结为以下几种方法。代码优化,图片优化,第三方工具优化等…
    一.代码优化
    内存会溢出肯定和代码逃不了关系,99.99%学java的人都知道垃圾回收器是java的一大优点并据此来嘲笑C++。显然这个特性为代码编写者省了不 少事,但这个特性却带来了不少隐患。举个例子在游戏当中经常有不同场景的切换,如从游戏逻辑退到主菜单逻辑,对游戏逻辑对象的态度很多人会选择忘记等待垃 圾回收器来收尸^_^。乍看之下似乎并无不妥垃圾回收器会来善后。实际上垃圾回收器并非实时的,它不像C++的Delete语句马上释放不用的内存。当从 游戏逻辑切换到主菜单逻辑这时两个对象同时存在很可能这时内存就不够用了。读到这里很多人会发现实际上垃圾回收器在j2me上并不怎么好用,从一个角度上来讲在j2me上 所有垃圾必须由手工释放,除简单类型以外所有对象都必须显式地置空例如 imgs=null;  实际上java提供了一个不错的工具用来查找内存溢出,java.lang.Runtime.freeMemory() 。它可以返回当前的剩余内存数,将它适当的安放在代码中可以有效的监测内存使用状况。很大一部份的j2me程序员之前都是从事pc软件开发工作,充裕的内存掩盖了许多写代码的不良习惯。如下所示:
         //a 不为空
         a=new Logic();
    很多人可能对此有异议,他们会认为新的对象会把旧的对象冲掉并且释放内存。这里面包含两个问题:1. 该段代码是先创建对象然后再进行赋值操作的,也就是说在这期间有两个对象同时存在这就很可能会产生溢出。2. 这样做也会妨碍垃圾回收器的工作
    较好的写法如下:
                a=null;
                a=new Logic();
    虽然麻烦了点但在j2me中还是必要的。接着看下例。
      drawString("游戏时间:" + time ,50,50,Graphics.LEFT|Graphics.TOP);
    "游戏时间:" + time  很完美在paint()方法当中每次都被刷一遍显示在屏幕上。危机往往隐藏在美丽的外表,该语句会引起新的内存重新分配来存储 "游戏时间:" + time   而显示完以后又必须由垃圾回收器释放,用了双倍时间,并且容易发生内存溢出。依此类推在重复执行的方法里应尽量避免重复定义对象。与paint()方法 类似在循环里也有类似的情况存在。
    把所有对象的初始化放在构造函数里想必是再正当不过了,大多数人通常的做法是把当前逻辑所要用到的资源通通初始化完毕。
    很大一部份的内存溢出都是发生在构造函数中。内存使用的高峰期都是在构造函数中所以避开这个高峰能有效的防止溢出。建议最好的办法是第一次使用时初始化。如下所示
        if (img==null){
            //初始化
        }
    现在做游戏很多时候都需要地图数组,声音数组,还有一些其它资源这些资源很多可以放在代码中也有的可以放在文件当中。
    强烈建议将这些资源放在文件中需要时在load进来。这些资源文件如果放在代码中则会占用不小的代码段空间,而代码一般是程序一运行就装载到内存当中。
    除上面列举的方法外还有一些大家所熟知的顺便一提, 比如关闭没用的rms ,关闭没用的网络连接,关闭没用的流。正确地停止线程。良好的程序架构减少代码偶合性也是一个不错的方法,无论在代码调式,内存释放都可以做到非常清析。
    二. 图片优化
    j2me的内存杀手无疑非图片莫属,一张3k的图片可以占用20多k的内存不信大家把load前后的内存剩余打印出来对比看看。所以防止内存溢出最直接的办法就是从图片入手。
    1.图片压缩: 多数人马上会想到这个办法。不错这个办法是最有效的。在photoshop里图片制作完成后不要选择 "存储为",而是选择 "存储为 web 所用格式"  可以根据里面的选项进行压缩,特别是颜色这一项越小越好不过相应的图像会有所失真。不要认为这样就完了。
    实际上该图片还可以再次压缩,在网上有许多类似的工具。推荐一款可以压缩png格式的软件 xat.com Image Optimizer 效果不错。经常都有 70% 的压缩率且图像不会失真。
         假如你有多张规格一样的图片,那么建议你把它做成一张长条图片。有两个原因:
    1、 这样节省存储空间和内存空间。大家可做个试验将10张图片的内容放在一张当中对比看看文件大小有没有变化。
    2 、10张图片需要10个image 对象需要进行10次io操作浪费时间不说还浪费内存。当笔者发现这个好处时兴奋地把所有图片都存成一张,吱地一声内存又溢出了...原因想必大家也知道!!图片太大了不要把不同界面的图片整合在一起否则经常会得不偿失。
    作图时还有一些细节需要注意,颜色数量,分辩率,图像模式(最好是索引颜色),画布大小都会影响到图片大小。
    三. 工具优化
    谁都知道混淆器是用来保护代码的以加大反编译的难度(个人认为这是在嘲笑程序员的智商)。实际上用它来优化程序也是不错的选择,至少有两点好处:1、 压缩程序大小。一个60k的程序经常可以压掉10k左右。10k的空间对于写低端手机的程序员简直是雪中送碳,多少超过64k限制的游戏都受过它的恩惠; 2、节省内存空间。用脚去想也想得出来代码少了内存里的代码段自然就短了。
    根据经验很多人都会用jb自带的混淆器RetroGuard,实际上它效果并不怎么好。推荐使用proguard 在 http://sf.net 可以免费下载,它可以比retroguard 多压缩3至4 k 以上而且安全性更好。


    了解Java多线程编程之不提倡的方法

        不提倡使用的方法是为支持向后兼容性而保留的那些方法,它们在以后的版本中可能出现,也可能不出现。Java 多线程支持在版本 1.1 和版本 1.2 中做了重大修订,stop()、suspend() 和 resume() 函数已不提倡使用。这些函数在 JVM 中可能引入微妙的错误。虽然函数名可能听起来很诱人,但请抵制诱惑不要使用它们。

      调试线程化的程序

      在线程化的程序中,可能发生的某些常见而讨厌的情况是死锁、活锁、内存损坏和资源耗尽。

      死锁

      死锁可能是多线程程序最常见的问题。当一个线程需要一个资源而另一个线程持有该资源的锁时,就会发生死锁。这种情况通常很难检测。但是,解决方 案却相当好:在所有的线程中按相同的次序获取所有资源锁。例如,如果有四个资源 —A、B、C 和 D — 并且一个线程可能要获取四个资源中任何一个资源的锁,则请确保在获取对 B 的锁之前首先获取对 A 的锁,依此类推。如果“线程 1”希望获取对 B 和 C 的锁,而“线程 2”获取了 A、C 和 D 的锁,则这一技术可能导致阻塞,但它永远不会在这四个锁上造成死锁。

      活锁

      当一个线程忙于接受新任务以致它永远没有机会完成任何任务时,就会发生活锁。这个线程最终将超出缓冲区并导致程序崩溃。试想一个秘书需要录入一封信,但她一直在忙于接电话,所以这封信永远不会被录入。

      内存损坏

      如果明智地使用 synchronized 关键字,则完全可以避免内存错误这种气死人的问题。

      资源耗尽

      某些系统资源是有限的,如文件描述符。多线程程序可能耗尽资源,因为每个线程都可能希望有一个这样的资源。如果线程数相当大,或者某个资源的侯 选线程数远远超过了可用的资源数,则最好使用 资源池。一个最好的示例是数据库连接池。只要线程需要使用一个数据库连接,它就从池中取出一个,使用以后再将它返回池中。资源池也称为 资源库。

      调试大量的线程

      有时一个程序因为有大量的线程在运行而极难调试。在这种情况下,下面的这个类可能会派上用场:

       

           public class Probe extends Thread {
      
    public Probe() {}
      
    public void run() {
      
    while(true) {
      Thread[] x
    = new Thread[100];
      Thread.enumerate(x);
      
    for(int i=0; i<100; i++) {
      Thread t
    = x[i];
      
    if(t == null)
      
    break;
      
    else
      System.
    out.println(t.getName() + "\t" + t.getPriority()
      
    + "\t" + t.isAlive() + "\t" + t.isDaemon());
      }
      }
      }
      }