基于J2ME的无线移动设备的应用程序调优乔少杰{shaojieqiao@163.com}(四川大学计算机学院四川成都610064)摘要:随着需求的增长和信息技术的进步,无线移动设备的应用日益普及。目前的无线移动设备的软件存在较多的不足,为了解决这一问题,本文提出了对基于J2ME平台的MIDP应用程序进行优化的方案,通过对无线移动设备的代码和有限的内存资源的优化,使无线移动设备的性能得到很大的提高。关键字:无线移动设备,优化,J2ME,MIDPTheoptimizationofapplicationsbasedonJ2MEonwirelessmobiledeviceQIAOShao-jie(SchoolofComputerScienceofSichuanUniversity,Chengdu,Sichuan610064)Abstract:Withtheincreasingrequirementsandtheadvancementoftheinformationtechnology,theapplicationsofthewirelessmobiledeviceareprevalent.Presently,therearemanydisadvantagesinthesoftwareofwirelessmobiledevice.Inordertosolvethisproblem,thispaperproposesthemethodbywhichtooptimizetheapplicationsbasedonJ2ME.TheperformanceofthewirelessmobiledevicehasbeengreatlyimprovedbyoptimizingthecodeandlimitedMemoryresources.Keywords:wirelessmobiledevice,optimize,J2ME,MIDP1.引言进入21世纪,随着信息技术的迅猛发展,手机及其他无线设备越来越多的进入普通家庭的工作和生活中。由于Java程序具有“一次编译,处处运行”的特点,使其可以跨平台,运行在不同的嵌入式系统中。因此,Java技术在开发无线移动设备应用程序中的优势显露无疑。从Java1.0发表之后,Java就被广泛地使用在桌上型应用程序和Applet的开发上。当Java的版本演进到Java2,这时为了明显区分各种Java的应用,分割出了J2EE、J2SE、以及J2ME三种版本。J2SE标准版实现了所有Java标准规范中所定义的核心类库,也支持所有的Java基本数据类型。J2EE是一种利用Java2平台来简化企业解决方案的开发、部署和管理相关的复杂问题的体系结构。J2EE不仅巩固了标准版中的许多优点,而且提供了EJB,JavaServletsAPI,JSP及XML技术的全面支持。J2ME(Java2MicroEdition)是针对嵌入式设备及消费类电器的Java版本,J2ME可以广泛的应用于各种无线移动设备中。与J2SE相比,J2ME对VM、Configuration和Profile等3层结构做了特殊的实现,在VM层,手机上移植了只需几百KB的内存就可以运行的KVM(K虚拟机);在Configuration层,J2ME规定了连接限制设备配置(ConnectedLimitedDeviceConfiguration),它使用于运算功能有限、电力供应也有限的嵌入式设备(比如说手机,PDA)中;在Profile层,J2ME规定了移动信息设备框架(MobileInformationDeviceProfile),应用于手机等无线移动设备。虽然基于J2ME技术的平台非常适合手机等无线移动设备应用程序的开发,但传统的无线移动设备仍然存在许多的不足:1.内存十分有限,一般只有几十KB。对于应用程序,一般采用动态内存进行内存分配。2.处理器的处理能力有限,运算速度较慢,同一时间只能运行单一进程。3.运算效率和图形处理能力很差,图片必须转化为png格式才能显示。为了解决这些问题,提出了下文的方法。2.优化准则2.1内存测试基准[1]为了测试内存的使用情况,可以使用在java.lang.Runtime中所提供的两种方法:publiclongfreeMemory();publiclongtotalMemory();第一种方法告诉程序当前有多少字节的剩余内存可以使用,第二种方法给出在当前运行环境中总共的内存大小。为了查询一个对象使用的内存情况,可以使用下面这段程序:Runtimeruntime=Runtime.getRuntime();Longfirst,last;System.gc();first=runtime.freeMemory();Objectobject=newObject();last=runtime.freeMemory();longsize=first–last;运行上述代码后,变量size中就记录了对象object需要使用的内存的大小。2.2时间测试基准除了可以检查内存的使用情况外,还应该关注应用程序的运行速度。可以使用java.lang.System类中的currentTimeMillis()函数获得应用程序的运行时间。计算程序执行时间的代码如下:longstart=System.currentTimeMillis();//要计时的运算代码放在这儿longtime=System.currentTimeMillis–start;3.优化方案3.1优化代码运行速度3.1.1优化循环应将重复的常数计算移至关键循环之外,一个典型的通过矢量t来实现的循环可以用如下的程序表示:for(inti=0;it.size();i++){Objecto=t.elementAt(i);}每经过一次上述循环,需要调用一次t.size(),这次调用其实是可以避免的,下面是对上面代码的优化:intsize=t.size();for(inti=0;isize;i++){Objecto=t.elementAt(i);}3.1.2使用数组代替对象通常数组比对象运行的更快,更可靠。如文献[1]中所述,在矢量和哈希表中,虽然矢量和哈希表即简单又方便,但使用它们会增加大量的系统开销。因为在创建矢量或哈希表时,应该正确的为它们分配大小。如果开始分配的矢量或哈希表太小,那么使用它们的过程中就要动态的分配内存,这种动态扩大的代价是昂贵的。矢量在动态扩大时将创建一个新的内部数组,并将旧数组中的元素拷贝到新的数组中去。哈希表在动态扩大时将分配新的数组并执行一个花费很大的重散列操作。所以在使用矢量或哈希表的场合,如果需要提高效率,就可以使用数组来代替。如果使用RMS系统,可能程序中会使用ByteArrayInputStream类进行记录的处理,但使用ByteArrayInputStream类的代价很大。因此,应该直接使用字节数组来进行处理。3.1.3使用缓存I/O缓存可以加快对文件的随机访问,所以在频繁进行I/O操作的应用程序中具有重要的作用。虽然数据I/O流提供了读写一个字节的方法,但是应尽量避免从数据流中读写一个字节。因为单个字节的读写效率非常低,应尽量使用带有缓冲的流读写方法。在J2ME中没有提供这两个类,要想提高I/O效率,就应该自己设计符合要求的带有缓冲的I/O方式。3.1.4内存清理[1]一个有效而又简单的使用内存的方式在程序中使用主动地清理内存。一旦不再使用内存就应立即释放,这样能提高应用程序的性能。在程序中如果有数组不再使用,就应该释放,方法是设置数组指向null,使数组能被垃圾收集器收集,也可以调用System.gc()方法来进行垃圾收集。对于网络连接来说,一旦程序不再使用这些网络连接就应该立即释放。实现此功能的方法是使用finally语句,下面的代码没有使用finally语句,因此没有实现立即释放不使用的网络连接。HttpConnectionhc=null;InputStreamin=null;try{hc=(HttpConnection)Connector.open(url);in=hc.openInputStream();//从in中读取数据in.close();hc.close();}catch(IOExceptione){//处理异常}当程序在设法从一个网络的输入流中读取数据时,发生一个意外,这时就会导致异常的出现。在这种情况下,程序的执行将会跳转到异常处理部分,但已经打开的输入流尚未关闭。这些打开的输入流会占用大量的内存,使用下面的代码可以避免这个问题。HttpConnectionhc=null;InputStreamin=null;try{hc=(HttpConnection)Connection.open(url);in=hc.openInputStream();//从in中读取数据}catch(IOExceptione){//处理异常}finally{try{if(in!=null)in.close();if(hc!=null)hc.close();}catch(IOExceptione){}}3.2优化内存使用3.2.1创建和重复使用对象应尽量避免在一个循环内部创建一个新的对象,因为每创建一个新对象时,内存将被重新分配,而内存分配将花费大量的时间。而且一般来说,在一个循环体内创建的对象,有可能在循环体结束时被释放。这就意味着,每一次循环体的执行将促使系统运行一次垃圾收集器,这将导致循环体的执行速度与垃圾收集器的运行速度差不多。下面是这样的一个例子[1]:Object[]inputs=newObject[0];int[]results=newint[0];intlength=inputs.length;for(inti=0;ilength;i++){Processorp=newProcessor(inputs[i]);results[i]=p.calculateResult();}为了避免在循环体内创建对象所造成的影响,可以采用如下的方法[1]进行处理,可以大大提高程序运行的效率。Object[]inputs=newObject[0];int[]results=newint[0];intlength=inputs.length;Processorp=newProcessor(inputs[i]);for(inti=0;ilength;i++){p.setInput(inputs[i]);results[i]=p.calculateResult();}在一个循环体内创建对象,将会导致程序性能受到很大得的影响。由于要花很长的时间来新建一个对象,于是应该尽量重复利用对象。因此,昀明智的做法是保存和更新老对象的字段,而不是创建一个新对象。例如,不要在自己的paint()方法中新建一个Font对象。相反,应将其声明成实例对象,再初始化一次。在这以后,可在paint()里需要的时候随时进行更新。3.2.2字符串和字符串缓存文献[1]中指出,在Java中,使用“+”操作符连接两个字符串时,有可能导致新的字符串对象的创建,而新字符串对象的创建又触发内存管理的工作。当创建并修改一个字符串缓存时,实际的工作是在一个内部字符数组中进行的,而当从一个已有的字符串缓存创建一个新的字符串时,实际上新的字符串中并不包含一个新的字符数组,而仅仅指向原来字符缓存中的字符数组。这样看来,似乎不会导致新的内存的分配。但是,如果程序修改了新字符串的内容,字符串缓存会自动创建一个新的字符数组提供给新的字符串,这个新字符数组是旧字符数组的复制。这样,内存分配就执行了。一个字符串可能就是一个被创建的新对象。如果程序员想在一个循环内部连接多个字符串,那么就应该使用有效的方法,避免生成新字符串的同时导致大量内存的分配。一个有效的方法就是用字符数组取代字符串和字符串缓存。使用字符数组是一个快速有效的解决方法。但是有时函数调用的参数大多是把字符串当作参数,函数的返回类型也是字符串。如果使用字