-------------一般類問題--------------
1 J2ME中查表法使用三角函數
CLDC和MIDP都沒有提供三角函數,而且CLDC1.0中也沒有浮點數,所以我們的選擇是查表。使用8位定點數的sin和cos表。下面是wtk自帶demo中的代碼,只提供了有限的幾個角度,實際使用時根據需要細化角度值。
// sines of angles 0, 10, 20, 30, 40, 50, 60, 70, 80, 90,all *256
private static final int[] SINES =
{ 0, 44, 88, 128, 165, 196, 222, 241, 252, 256 };
// angle is in degrees/10, i.e. 0..36 for full circle
private static int sineTimes256(int angle)
{
angle %= 36; // 360 degrees
if (angle <= 9) // 0..90 degrees
{
return SINES[angle];
}
else if (angle <= 18) // 90..180 degrees
{
return SINES[18-angle];
}
else if (angle <= 27) // 180..270 degrees
{
return -SINES[angle-18];
}
else // 270..360 degrees
{
return -SINES[36-angle];
}
}
// angle is in degrees/10, i.e. 0..36 for full circle
private static int cosineTimes256(int angle)
{
return sineTimes256(angle + 9); // i.e. add 90 degrees
}
(2006.5 注:有一些算法可以生成三角函數值,這樣只要在游戲載入時生成一下函數表即可,節省一些數據)
2 J2ME中使用隨機數
產生0~n之間的隨機數:
(ran.nextInt()>>>1)%n
或
(ran.nextInt()&0x7FFFFFFF)%n
產生-n~0之間的隨機數:
(ran.nextInt() | 0x80000000 )%n
3 嘗試IO優化
正在開發的一個游戲,由於讀地圖的時候做了圖片切割,所以速度比較慢。(在我開發上一個游戲的時候,讀取地圖時沒有裝載切割圖片,速度非常快,看來IO操作的速度和createImage,drawImage相比是微不足道的)對於IO的優化也許根本不會明顯的提高速度,但我還是試了一下。
分析了一下代碼,在最初的代碼中為了比較方便的讀取各種類型的數據,使用DataInputStream套接InputStream。可是我仔細看了一下我讀取得數據,居然都是byte,唯一的一個char也是被我用兩個byte手工組裝起來的。這下,DataInputStream看來是不需要了。於是我做了個實驗,沒改動之前讀取地圖耗時1242ms,將DataInputStream去掉直接使用InputStream耗時1065ms,雖然每次試驗的結果都稍有不同,但大概還是節約了200ms左右。
還能再加快點嗎?再觀察一下代碼,我發現數據是通過多次的read操作讀取進來的。太過頻繁的 io操作會不會降低速度呢?如果用一個字節數組作緩沖一次性將數據都讀進來會不會快點?嗯,試一試才知道。但是我怎麼知道一個流的大小呢? InputStream的avaliable方法總是返回-1啊!打開兩次流,第一次先計算大小?對了,還有一個方法。直接將文件大小寫到文件前面。地圖文件是用自己的編輯器生成的,知道大小很容易。於是我在文件前面用兩個byte紀錄了文件的大小,先從流中讀取2個byte,得到文件大小後,再用 read(byte[],int,int)方法將整個流讀取到緩沖中。然後,我的所有數據操作都從緩沖中讀取。好,試驗一下,結果是:1154ms。阿? 慢了近100ms。事實證明了這個猜想是錯誤的。原因?也許只有了解KVM的機制才知道。
弄完速度的問題,我又覺得讀取文件的try塊太大了,因為是邊讀邊處理數據,所以try塊變得很大。try塊太大會增加class文件的大小。於是我用一個方法將讀取byte的操作封裝起來,當然這個方法是聲明為private static的,但究竟能不能內聯,只有編譯器和kvm才知道。在這個方法內部從流中讀取一個字節的時候采用了try,catch結構,這就使一個大 try塊分散成若干小try塊。試驗了一下,耗時1089ms,诶,還是慢了點。現在對於速度的要求比空間更高,更何況減小try塊節省的10幾個字節打包後基本忽略不計了。所以這個優化又失敗了。
小結:能使用簡單流的時候就不要使用復雜流,不要太相信理論上的說法,只有試了才知道。
注:試驗數據是Nokia3100手機的實機測試數據,在Nokia 3300上這個數據更小些,最快約800多ms
4 壓縮還是不壓縮
做J2ME的都知道Midlet Suite的容量實在太小了,於是不免想做點壓縮。前些天,我就嘗試了一次壓縮。我自己定義的地圖文件裡有3層數據,其中2,3層有大片連續分布的相同的值。唉?我一琢磨,使用一個簡單的行長編碼壓縮,僅對這個值進行行長編碼,算法很簡單速度又不慢,卻可以大大減小地圖文件的大小。看起來真的很不錯诶!說干就干,忙了半天,又改地圖編輯器,又改游戲中讀地圖的代碼。總算搞定,試了一下,原來2.23k的一個文件被壓縮到900多字節。好像很不錯啊,接著我打了個jar包,卻突然發現這個jar文件好像並沒有比原來小阿!似乎還大了點。我連忙找出備份的代碼,果然原來的jar更小點!怎麼回事啊??我突然想到, jar本身就是壓縮格式的。難道。。。我趕快用winrar打開兩次的jar文件觀察。~~~~~原來如此!原來的jar中,2.23k的文件的包大小為 185字節,而我現在的jar中,900多字節的文件的包大小為216字節。也就是說,我自己先壓縮一遍的文件打包後還不如不壓縮的小!
看來自己做壓縮之前,一定要先看看你想壓縮的文件在包裡面的大小。還有對於png文件,使用某些工具優化後,在包裡面的大小卻變大了。這個還真是要注意阿~!
(05.12.31注:某些壓縮算法確實比zip壓縮效率要高,可以使用,不過副作用是解壓導致loading時間變長)
(2006.5注:有些時候,需要節省一下內存,可以將數據打包壓縮一下,package & compress模式,存在內存中,需要時解壓)
5 同時多處異常
程序出現exception時,在一個外包函數處捕獲到了,顯示為函數a出現異常,然後去a中捕獲卻沒捕獲成功,但是仍然發生了異常.
原來是外包函數中調用的另一個函數b也產生了同樣的異常.
同時多處異常-小心!
----------------開發工具問題-----------------
1 Eclipse Tips
1.在工具條上有個文本形象的按鈕"show source of selected element only".當編輯類的某個成員(方法或域)時,按下這個按鈕,則當前窗口會只顯示你正在編輯的類成員.再按一下則恢復.
2.顯示java文件行號.菜單中選擇Window->Preferences打開Preferences窗口後選擇Java->Editor,在右邊的選項中選中Show line numbers.
顯示非java文件行號.在Preferences窗口中選擇Workbench->Editors->Text Editor,同樣右邊的選項中選中Show line numbers.
3.編輯代碼時,按ctrl+/可以注釋當前行或選中的多行代碼;按Atrl+/可以顯示自動完成代碼的提示。
4.選中代碼,按 ctrl+shift+F 格式化代碼
5.輸入syso,按atrl+/可出來 System.out.println("") ;
2 運行Nokia模擬器的一個注意事項
這是一個老問題了,原來用WTK的時候就有,在WTK中啟動Nokia的模擬器,如果先前已經打了包,那麼運行的是打包的程序,想當年經常會很郁悶為什麼改動了沒效果,後來養成一個習慣,將jar裝到手機測試後隨手刪除。
今天用JBuilder的時候又碰到了這個問題,也是Nokia的模擬器,如果已經建立了一個archive,那麼Nokia模擬器運行的總是包,呵呵,所以要麼將archive從project中remove,要麼每次都rebuilder這個archive。
3 Eclipse集成Motorola模擬器
在Eclipse的菜單/工具條中選擇Run->External Tools,打開面板後,選擇program,然後new一個新的配置
1 在Location中填入Moto模擬器的路徑,如:C:\Program Files\Motorola\SDK v4.2 for J2ME\EmulatorA.1\bin\emujava.exe,Moto的不同模擬器支持n種不同機型,需要看moto sdk的文檔才知道。
2 在Arguments裡填入執行的參數,包括jad路徑,模擬器使用的機型。如:"${project_loc}\deployed\${project_name}.jad" -deviceFile Resources\V600.props
我是讓模擬器執行deployed裡面的jad/jar,${project_loc}是工程路徑,${project_name}是工程名。這裡選擇的機型是V600.
說明:這種方法的局限在於只能執行jar,所以每次運行前必須打包。實際使用前需要為沒種機型配置一個run,由於使用了通配參數,所以所有的工程都可以使用一個配置
(05.12.31注:現在某些MotoSDK已經可以和Eclipse集成了!)
4 初次使用JBuilder 7-若干小問題
(1) MobileSet問題
JBuilder7需另外安裝MobileSet, Mobileset自帶了一個WTK. 如果不安裝MobileSet,JB7配置JDK時不能自動識別WTK,安裝MobileSet後,可以通過配置JDK的方法加入新的WTK
(2) 資源文件問題
JBuilder的所有源文件都應該放在source path中,可以在工程屬性中設置source path,資源文件也一樣。既可以和源文件放在一個source path(即文件夾)中,也可以放在另外的source path中。需要注意的是,JBuilder只默認識別一定數量的後綴,如png,如果你使用了其他後綴的資源文件,如dat,bin,需要先把該文件通過add files加入到工程中,選擇文件屬性,設置為copy,這樣該後綴的文件就被識別為資源文件了。
(3) 光標不對問題
最簡單的辦法-改字體,我改成了第一種字體(JB7中),感覺和默認字體沒什麼不同。至於這個問題的根本解決方法網上有文論述。
(4) 鼠標滾輪無效問題
據說這個問題只在JB7和以下版本中存在,原因是只有J2SDK1.4以上才支持滾輪,所以需要將JB7的JDK改成1.4的. 方法是修改JBuilder7\bin\jdk.config文件,將javapath和addpath兩行修改,例如:
# javapath ../jdk1.3.1/jre/bin/hotspot/jvm.dll
javapath Y:\j2sdk1.4.2\jre\bin\server\jvm.dll
# addpath ../jdk1.3.1/lib/tools.jar
addpath Y:\j2sdk1.4.2\lib\tools.jar
5 百寶箱應用編譯打包事宜
1 編譯時,設置javac 的target vm為1.1即可通過移動檢測。wtk中無法實現。在Eclipse中可以在java-compiler-Compliance and Classfiles中做以下設置:
Compiler compliance level: 1.4
Generated .class files compatibility: 1.1
Source compatibility: 1.3
(2005.12.31注:JBuilder中也有類似的選項,如果使用命令行或Ant,都只要將javac的targetVm參數設置為 target 1.1)
2 用eclispe打混淆包。但eclipse編寫jad中文會出現亂碼,所以用wtk編寫正確的jad,然後用wtk打包(注意不能覆蓋eclispe打的包),這是為了用wtk獲得正確的jad和manifest文件。將elcipse打包出的jar解壓,用wtk生成的mainifest代替原jar 中的mainifest文件,然後用winrar打包(zip格式,可選最大壓縮,注意要選擇所有的文件後打包,不要將外面的整個目錄打包).最後將 jad中的jar size改為這個最新的jar的字節數。
(2005.12.31注:我不用eclipse很多年,據說現在的eclipse me新版沒這問題了,我當時用的時候eclipse me的版本才0.4.6)
另:1. Nokia S60,SE k700機器中顯示的游戲名字為MIDlet-1中的名字,而Nokia40為MIDlet-Name中的名字
2. 根據sp提供的資料Nokia 7650 游戲不能用中文名(其實NGageQD可以)
----------------機型相關問題-----------------
1 Nokia S60 IO操作內存洩漏不可不察
Nokia7650,3650
游戲運行過程中,有時會出現“存儲已滿”的對話框,出現的位置不固定
游戲運行過程中,有時出現“應用程序錯誤 NullPointerExcept”,“程序已關閉 MidpUi”的對話框
游戲運行過程中,有時會出現“程序已關閉 MidpUi ViewSrv 9”的對話框,出現的位置不固定
其實這個問題是由S60的getResourceAsStream方法內存洩漏的bug引起的,由於每次切換地圖時 io操作都要讀取大量數據,內存洩漏積累到一定程度就引起了“存儲已滿”,白屏,死機,進而會引起null pointer異常等。解決方法是盡量減少io操作的次數。如果內存夠大就一次將資源讀入。
2 NokiaS60模擬器異常退出
症狀:模擬器自動關閉,沒提示任何錯誤
原因:使用了Nokia UI API中的燈光或振動控制,而Nokia S60部分機型和對應的模擬器不支持這兩個特性.
3 NokiaS60 UI API bug
1 旋轉後,並以clip的方式向緩沖上貼圖,clip無效
2 無法創建透明muttable Image
此兩點,致命傷,帶來許多不變
4 Nokia S60的幾個問題
(1) 不能每幀調用 System.gc(),否則嚴重降低fps
(2) Nokia S60機器的不同機型對於translate 和 setClip的處理不一樣。在Nokia N-Gage QD等機型中,setClip是相對於translate以後的坐標計算的,而在Nokia 6600,6670等機型中,setClip不受translate的影響,永遠只相對於屏幕左上角(0,0)點計算。所以如果在Nokia6670中,使用先translate再setClip的方法畫子圖,則會出現錯誤。為了統一代碼,在Nokia S60中不要使用translate,即使用,兩次translate之間不要進行setClip.修改後的畫子圖函數為:
public static void drawSubImg(Graphics g,Image img,int x,int y,int sx,int sy,int swidth,int sheight)
{
g.setClip(x,y,swidth,sheight);
g.drawImage(img,x-sx,y-sy,GLT) ;
g.setClip(0,0,width,height) ;
}
(3) 部分Nokia機型(6600,6670等)退出後報錯null pointer exception的解決方法
不要在在主while循環中調用destroyApp,而改成檢測一個標志,退出主循環後再調用
destroyApp
boolean exit ;
...
while(!exit){
...
if(...){
exit = true ;
}
...
}
destroyApp(true);
注:可在destroyApp內部調用notifyDestroyed
5 Nokia"不能運行應用程序"錯誤新解
Nokia手機運行J2ME程序的時候出現“不能運行應用程序”的錯誤,一般都是內存不足引起的,但今天遇到這樣的錯誤,卻發現是另一個原因。即當使用 nokia的UI API,DirectGraphics的drawImage時,如果旋轉參數設置不當,也會出現“不能運行應用程序”的錯誤。
6 Nokia系統bug兩則
(1) Nokia7650(V4.46)應用程序目錄顯示bug
應用程序安裝後,打開應用程序目錄,顯示錯誤提示:
"程序已關閉 MidpUi USER9",應用程序目錄無法進入。
分析後發現,原來是新安裝的應用程序沒有在mainfest.mf中的
midlet-1屬性中指定應用程序圖標,導致程序目錄無法顯示圖標。
在我所見到NokiaS40機器上和NGageQD上,如果圖標沒指定或指定了但
不存在,將顯示默認的圖標。
此bug對於其它版本的7650或者其他機型是否存在尚不得知。
解決方法:使用seleQ將7650c:\system\midp中剛安裝的程序目錄刪掉,即可正常進入應用程序目錄。
在應用中使用自己的應用程序圖標,並正確設置,以避免讓用戶遭遇到此bug。
(2) Nokia3100(v3.10)游戲目錄振動設置與應用程序中使用振動沖突的bug
在Nokia3100等機型中,提供了一個游戲目錄管理游戲類應用。該目錄
可以設置目錄中的游戲運行時是否發聲,振動和使用網絡。對於
Nokia3100(V3.10)如果將振動設置關掉,而在應用程序中使用了振動,則
會產生一個異常。此bug是在10個月之前發現的,記不清是哪個異常了。
此bug對於其它版本的3100或者其他機型是否存在尚不得知。
解決方法:在應用程序中使用振動的地方增加異常處理。
7 Motorola手機J2ME應用問題
(1) 應用程序圖標
必須在jad 文件Midlet-Icon屬性中指定圖標文件,Midlet-1中指定的圖標無效
Moto V系列圖標大小應為15*15,其他尺寸無法顯示。
(2) 左右軟鍵問題
Motorola手機操作系統設定是:右軟鍵確認,左軟鍵取消。所以,我們的程序應該和這個習慣保持一致。
(3) Key Code
Moto V的key code不同於其他Midp2.0機器
左軟鍵:21
右軟鍵: 22
中鍵: 20
up: 1
down: 6
left: 2
right: 5
(2005.12.31注:在遇到新機型時,先測試一下keyCode比較好)
8 MIDP2.0 Canvas全屏問題
MIDP2.0 Canvas可以調用setFullScreenMode(true)將Canvas設置成全屏,但設置成全屏後新的Canvas width & height的獲得對於不同手機卻並不一樣。
(1) MotoV系列
調用setFullScreenMode(true)後,將觸發sizeChanged事件,此事件從系統接受兩個參數,即為Canvas全屏後的width & height,通過這個事件可以獲得新的寬高。
protected void sizeChanged(int w, int h)
{
width = w ;
height = h ;
}
但要注意,此事件並不是同步的,就是說如果你調用了setFullScreenMode(true)之後,立即使用新的width,height,有可能獲得錯誤的結果。
(2) SE K700
調用setFullScreenMode(true)後,不會觸發 sizeChanged,而是通過getWidth和getHeight獲得新的寬高。SE的setFullScreenMode調用後是立即返回的,所以可以獲得正確的width & height
對於其他機型暫時還不了解
----------------移植問題-------------------
1 鍵盤響應
不同的機型對於鍵盤事件的響應不一樣。經過我的測試,Nokia 7210,3100一次只能接受一個按鍵信息。(我寫了個測試程序,發現如果一個鍵被按下後沒有松開,則KeyPressed事件不會再產生,即其他鍵的按下操作無效)所以,用緩沖處理控制精靈運動時,如果規定只能四方向運動。如果up已按下,再按下left,精靈的運動方向並不會改變。不過將按鍵緩沖。按下up,按下left不釋放,松開up---精靈就會向左運動。(在松開up後產生了left的KeyPressed事件!奇怪嗎?松開up後我並沒有進行"按下"left這個動作--left鍵在up松開前就被按下了且沒有松開。似乎機器一直在監測鍵盤上各鍵的狀態,並且有一個等待隊列。)
在wtk的標准模擬器上就不同了。它可以接受多個按鍵“同時”按下的事件。所以如果用四個並列的if處理,精靈是可以斜著運動的。如果用if else處理,則如果已經按下一個方向鍵,然後再按下另一個,是否能改變方向受到if else 語句中順序的影響。即,如果是 if(up) else if(left),則會先檢查up鍵,所以如果已經按下了left,再按up是可以向上運動的,反過來就不行了。(這個自然:)
其它的機型由於手頭沒有機器,我也沒試過。應該也是如此吧。
2 多機型移植經驗談
開發的時候平台是Nokia 40,然後移植到Nokia 60, Moto V, SE等,總結一下大概需要幾個版本。
1。 Nokia 40版, 使用Midp1.0+Nokia UI API
2。 Nokia 60版, 使用Midp1.0+NOkia UI API
3. Nokia Midp2.0版,如6600,7610,使用Midp2.0
4。Moto V版,使用Midp2.0
5。 SE版,使用Midp2.0
6. 三星s100,s200,c100,使用Midp2.0
幾點開發經驗:
1。各機型之間最大的差別就是屏幕大小不同。所以游戲中要能自適應屏幕大小
2。不使用Midp2.0的GameAPI會比較方便移植,只要自己封裝切圖,旋轉等函數即可。NokiaUI API和Midp2。0都支持圖片選轉。2.0支持的更好。注意Nokia 60不支持創建可變的透明圖片,所以要用其他方法代替。
3。NOkia 6600,7610的UI API有問題(圖片旋轉),所以用了Midp2.0代替
4。支持MIDP2。0的機器程序大致相同,其中MOto,SE,SX都差不多。但也有細微差別。如SE不支持全屏。所以screenSizeChanged方法無效。
5。說說聲音播放。NOkia s40上我堅決不用聲音,一是容量限制,二是太難聽。其他機型都可以支持midi和wav.不過沒有發現可以同時播放2個midi的機型,moto v和se都可以同時播放midi和wav,nokia則不行。
3 移植一法
近日觀察某些游戲的源代碼(反編譯後的),發現有個方法挺方便游戲的移植的。定義一個接口(比如 stringTable)將游戲中所用到的靜態字符串都定義為接口的常量。然後,讓使用到這些字符串的類實現stringTable接口。這樣移植的時候只要修改接口裡面的字符串就行了。當然,對於游戲中坐標的定位,最好使用getWidth(),getHeight()還有Font類的方法 stringWidth,不要定死了。這樣的話,移植工作就比較輕松了。
4 檢測機型
在J2ME開發中,往往遇到根據不同機型做不同事情的情況,比如Nokia3650的鍵盤比較特殊,Nokia7650不支持mmapi,所以需要獲得機型信息。
下面是一段簡單的代碼
public static void checkPlatform()
{
String platform = System.getProperty("microedition.platform") ;
String tmp = null ;
if(platform.length()==9)
tmp = platform ;
else if(platform.length()>9){
tmp = platform.substring(0,9) ;
}
if(tmp!=null){
if(tmp.equals("Nokia3650")){
is3650 = true ;
}
else if(tmp.equals("Nokia7650")){
is7650 = true ;
}
}
}
獲得機型信息還包括版本號等等,所以要截取前面的幾個字符比較。
不過得到的機型字符串有時並不保險,如早期的Nokia N-Gage獲得得並不是N-Gage,不過3650和7650還是可以的.