要把J2ME程序與J2SE程序區分開來,其依據就是J2ME運行所處的受限環境。多數J2ME系統的主要受限條件就是可以存儲和運行程序所需內存的大小。例如,許多MIDP設備限制應用程序的尺寸不大於50K,這遠遠不及Server端J2SE運行環境下那些成兆的程序。實際應用中,程序會很容易超出這些限制條件。通過本篇您將學到一些減小程序尺寸大小的技巧,並在下面的例子中實踐這些技術。這個例子MIDlet僅僅顯示一個文本框並在其內容改變時發聲。
package com.j2medeveloper.techtips;
import javax.microedition.lcdui.*;
public class BeforeSizeOptimization extends
BasicMIDlet {
public static final Command exitCommand =
new Command( "Exit",
Command.EXIT, 1 );
public BeforeSizeOptimization(){
}
protected void initMIDlet(){
getDisplay().setCurrent( new Mainform() );
}
public class Mainform extends form {
public Mainform(){
super( "Mainform" );
addCommand( exitCommand );
append( textf );
setCommandListener( new CommandListener(){
public void commandAction( Command c,
Displayable d ){
if( c == exitCommand ){
exitMIDlet();
}
}
}
);
setItemStateListener(
new ItemStateListener() {
public void itemStateChanged(
Item item ){
if( item == textf ){
AlertType.INFO.playSound(
getDisplay() );
}
}
}
);
}
private TextField textf =
new TextField( "Type anything", null,
20, 0 );
}
}
雖然這個MIDlet在此僅作為一個例子,但使用的尺寸優化技巧可以適用於任一J2ME的profile上。
注意,上面的MIDlet類需要下面的輔助類:
package com.j2medeveloper.techtips;
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;
public abstract class BasicMIDlet extends MIDlet {
private Display display;
public BasicMIDlet(){
}
protected void destroyApp( boolean unconditional )
throws MIDletStateChangeException {
exitMIDlet();
}
public void exitMIDlet(){
notifyDestroyed();
}
public Display getDisplay(){ return display; }
protected abstract void initMIDlet();
protected void pauseApp(){
}
protected void startApp()
throws MIDletStateChangeException {
if( display == null ){
display = Display.getDisplay( this );
initMIDlet();
}
}
}
用J2ME WTK打包時,本例子MIDlet占用4K空間。
減小尺寸的首要步驟就是通過修正程序的功能實現來去掉多余的類。程序的所有功能確實必須都實現嗎?用戶可以不需要這些“附屬功能”嗎?要設計盡可能小的程序,這裡的MIDlet例子已經相當小了。
第二步就是深入考察程序定義的內部類,特別是匿名類。記住,每個類文件都有一定量的與之相關的系統開銷。即便最普通的類也有系統開銷。
public class foo {
// nothing here
}
編譯上邊的類,生成的類文件大約200byte大小。比如實現一個事件監聽器,就是對匿名類的常見使用。在例子MIDlet中就定義了兩個此類的監聽器。接下來進行的最簡單的優化就是,讓主MIDlet類實現CommandListener和ItemStateListener接口,並把監聽器代碼移至此處。記住,多個對象可以使用同樣的監聽器。必要時,可以使用傳遞至commandAction和itemStateChanged方法的參變量來區分它們。
內部類也可使代碼過大,因為編譯器必須生成特殊的變量和方法,以便內部類可以訪問包含它們的類的私有內容。請參考內部類的規范以獲取更多信息。
第三步,盡量使用現有的類。例如,基於CLDC的profile沒有構造集合類,所以我們可以用內建的Hashtable和Vector類來實現之。構造MIDP程序時也可采用此法。例子MIDlet中定義了一個form字類來生成主表,可以容易的如下直接生成:
mainform = new form( "Mainform" );
mainform.addCommand( okCommand );
mainform.setCommandListener( listener );
這裡沒有正確或者錯誤的答案,只是要推敲。
第四步就是破壞程序的繼承關系。你也許把相關的代碼放到一個或多個抽象類中,這是OOD中為提高程序間代碼重用的推薦做法。雖然破壞繼承關系與你所學知識相違背,但簡化的繼承關系更有意義。特別的,當你的的抽象類?D?D可能來自其他項目?D?D僅僅被繼承一次時,破壞繼承關系的結果不言而喻。例如,例子MIDlet繼承了BasicMIDlet類,但兩者合並為一個類。
第五步就是要縮短名字長度,如包名、類名、方法名和數據元素名。看起來有些蠢,但一個類文件確實包含太多的符號信息。縮短各量的名字可以縮小生成的類文件尺寸。這種節省不會特別明顯,但多個類中進行總加的結果還是可觀的。包名對減小尺寸來講特別合適。MIDP程序是完全自我包容的,完全可以不使用包名,因為在手持設備上包名根本不可能與其他類名沖突。例子MIDlet中,可以把com.j2medeveloper.tchtips包名去掉。
注意,一般來講,縮短名字不需要手工去做,要用一個“混淆器”去做。“混淆器”的主要功能是“隱藏”程序代碼,使之不能通過反編譯讀出。它的副作用是減小了程序的尺寸。因為隱藏過程主要通過更改方法和數據成員的名字來完成。有一個開源的混淆器稱為RetroGuard,可以免費從http://www.retrologic.com得到。也有一些商業包可用。(當為基於CLDC的profile混淆時,記得在預校驗之前混淆,否則混淆器將使類文件中的預校驗數據失效。)
最後,深入數組的初始化。(例子MIDlet沒有做數組初始化,但對程序來說初始化是重要的一步) 在編譯時,一個數組初始化聲明如下所示:
int arr[] = { 0, 1, 2, 3 };
而實際生成代碼的過程如下所示:
arr[0] = 0;
arr[1] = 1;
arr[2] = 2;
arr[3] = 3;
這個過程可以通過使用Java 2 SDK中附帶的javap工具把二進制代碼反編譯成類文件去看(使用-c選項)。也許你會詫異於看到的內容,特別當你希望看到的是一排排二進制常數時。有兩種方法可以讓你看不到反編譯的程序代碼,(1)把數據編碼為字符串,運行時解碼之,或者(2)把數據存為二進制文件並與程序打包,用類裝載器的getResourceAsStream方法在運行時存取之。
以上只是一些指導性的方法,對每個J2ME程序而言,這裡沒有具體到步驟。但是多數方法可以應用的本例。優化後的MIDlet如下所示:
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;
public class ASO extends MIDlet
implements CommandListener,
ItemStateListener {
private Display display;
private form mainform;
private TextField mainformTF =
new TextField( "Type anything", null,
20, 0 );
public static final Command exitCommand =
new Command( "Exit",
Command.EXIT, 1 );
public ASO(){
}
public void commandAction( Command c,
Displayable d ){
if( c == exitCommand ){
exitMIDlet();
}
}
protected void destroyApp( boolean unconditional )
throws MIDletStateChangeException {
exitMIDlet();
}
public void exitMIDlet(){
notifyDestroyed();
}
public Display getDisplay(){ return display; }
protected void initMIDlet(){
mainform = new form( "Mainform" );
mainform.addCommand( exitCommand );
mainform.setCommandListener( this );
mainform.setItemStateListener( this );
mainform.append( mainformTF );
getDisplay().setCurrent( mainform );
}
public void itemStateChanged( Item item ){
if( item == mainformTF ){
AlertType.INFO.playSound( getDisplay() );
}
}
protected void pauseApp(){
}
protected void startApp()
throws MIDletStateChangeException {
if( display == null ){
display = Display.getDisplay( this );
initMIDlet();
}
}
}
關於作者:Eric Giguere是來自Sybase下屬iAnywhere Solutions的軟件開發人員。他致力於手持設備和無線計算領域的Java技術。他是滑鐵盧大學的數學學士和數學碩士,寫了很多有關計算的文章。