開發(fā)高可移植性J2ME的軟件
隨著MTK的流行,使現(xiàn)在的J2ME虛擬機市場上品牌眾多,除了索愛,Nokia S40,Moto,三星,LG等國際大品牌的虛擬機,更是有MTK,展訊內(nèi)置的一些不知名的虛擬機,因此當(dāng)初Write Once,Run AnyWhere變成了Write Once,Debug AnyWhere了。對于一個沒有經(jīng)驗的J2ME程序員來說,開發(fā)一個兼容性高的軟件變成了噩夢,不斷的在不同手機,不同平臺上打log,在這臺手機上解決了這個問題,跑到另外一臺機器上問題有重新了,噢,my god!我不干了。別急!我寫這篇文章的目的就是要告訴大家,對于這種狀況,我們也不是束手無策的。下面就等我慢慢的道來解決之道。
本文主要適合那些有經(jīng)驗的J2ME程序員在優(yōu)化軟件,或者是需要考慮軟件兼容性時的參考文檔。
Jblend 平臺
JBlend 是一家日本的嵌入式虛擬機廠家生產(chǎn)的J2ME虛擬機,此虛擬機大量的用于低端手機平臺,本人發(fā)現(xiàn)有使用此虛擬機的平臺有,MTK,MOTO。
官方網(wǎng)站:http://www.aplixcorp.com/chs/index.html 。
索尼愛立信平臺
索愛的虛擬機平臺是:Java Platform。最新版本是8。索愛的平臺在性能上,程序的穩(wěn)定性方面要優(yōu)于其他虛擬機平臺。而且APIs方面的bug也很少,在網(wǎng)絡(luò)支持方面也很優(yōu)秀?;旧喜粫驗槟阃涥P(guān)閉連接而導(dǎo)致連接泄漏。
官方網(wǎng)站:http://developer.sonyericsson.com/site/zhcn/docs_and_tools/p_docs_and_tools.jsp
S40平臺
S40平臺是Nokia針對S60智能操作系統(tǒng)推出適應(yīng)低端手機的手機操作系統(tǒng),相對其他虛擬機平臺來說,S40虛擬機對J2ME的支持相對比較完善,而且穩(wěn)定些,不過網(wǎng)絡(luò)環(huán)境這塊,S40對網(wǎng)絡(luò)資源泄漏特別關(guān)注,具體不同的手機,對同時打開多個連接有限制,這里建議大家做個測試,就不再累贅了。
官方網(wǎng)站:http://www.forum.nokia.com/
S40平臺詳解:http://tech.sina.com.cn/mobile/n/2006-09-22/1053107637.shtml
S60 平臺
Nokia 智能機平臺下的J2ME虛擬機。相對S40來說,S60支持的特性比較多,而且有些比較特殊的用法,比如獲取系統(tǒng)相關(guān)屬性的時候就是其中之一。
什么是JCP?
JCP(Java Community Process) 是一個開放的國際組織,主要由Java開發(fā)者以及被授權(quán)者組成,職能是發(fā)展和更新Java技術(shù)規(guī)范、參考實現(xiàn)(RI)、技術(shù)兼容包(TCK)。Java技 術(shù)和JCP兩者的原創(chuàng)者都是SUN計算機公司。然而,JCP已經(jīng)由SUN于1995年創(chuàng)造Java的非正式過程,演進(jìn)到如今有數(shù)百名來自世界各地Java 代表成員一同監(jiān)督Java發(fā)展的正式程序。JCP維護(hù)的規(guī)范包括J2ME、J2SE、J2EE,XML,OSS,JAIN等。組織成員可以提交JSR(Java Specification Requests),通過特定程序以后,進(jìn)入到下一版本的規(guī)范里面。所有聲稱符合J2EE規(guī)范的J2EE類產(chǎn)品(應(yīng)用服務(wù)器、應(yīng)用軟件、開發(fā)工具等),必須通過該 組織提供的TCK兼容性測試(需要購買測試包),通過該測試后,需要繳納J2EE商標(biāo)使用費。兩項完成,即是通過J2EE認(rèn)證(Authorized Java Licensees of J2EE)。
什么是JSR?
JSR是Java Specification Requests的縮寫,意思是Java 規(guī)范請求。是指向JCP(Java Community Process)提出新增一個標(biāo)準(zhǔn)化技術(shù)規(guī)范的正式請求。任何人都可以提交JSR,以向Java平臺增添新的API和服務(wù)。JSR已成為Java界的一個重要標(biāo)準(zhǔn)。
下面是J2ME JSR規(guī)范列表
| 
 名稱  | 
 內(nèi)容  | 
| 
 JSR 118  | 
 MIDP 2.1 規(guī)范。定義了MIDP 相關(guān)的接口,高級UI,低級UI,RMS,網(wǎng)絡(luò)相關(guān)的APIs  | 
| 
 JSR 82  | 
 定義了藍(lán)牙接口相關(guān)的APIs  | 
| 
 JSR135  | 
 Mobile Media API,定義了多媒體相關(guān)開發(fā)的組件APIs  | 
| 
 JSR 172  | 
 1. 一個輕量級的標(biāo)準(zhǔn)XML解析器  | 
| 
 JSR 75  | 
 JSR 75(PDA Optional Packages for the J2METM Platform)中定義了兩個可選包:  | 
| 
 JSR 177  | 
 安全APIs  | 
| 
 JSR 211  | 
 Content Hander 內(nèi)容處理APIs,可以調(diào)用此API打開相應(yīng)的文件,比如你可以打開jar安裝文件,打開mp3。  | 
| 
 JSR 239  | 
 Open GL@ES。主要用于圖形相關(guān)操作  | 
| 
 JSR 179  | 
 Location APIs 主要是用于LBS服務(wù)  | 
| 
 JSR 180  | 
 SIP APIs SIP是一個應(yīng)用層的信令控制協(xié)議。用于創(chuàng)建、修改和釋放一個或多個參與者的會話。這些會話可以好似Internet多媒體會議、IP電話或多媒體分發(fā)。會話的參與者可以通過組播(multicast)、網(wǎng)狀單播(unicast)或兩者的混合體進(jìn)行通信。  | 
| 
 JSR 184  | 
 Mobile 3D Graphics APIs,3D圖形開發(fā)。  | 
| 
 JSR 229  | 
 手機支付APIs  | 
| 
 JSR 234  | 
 手機高級多媒體支持,可以支持更豐富的多媒體操作  | 
| 
 JSR 238  | 
 國際化支持APIs  | 
| 
 JSR 248  | 
 JSR 248: Mobile Service Architecture MSA 移動服務(wù)架構(gòu)。 MSA for CLDC規(guī)范定義了移動電話上的下一代Java平臺,當(dāng)然是基于CLDC的J2ME平臺。 MSA for CLDC的目的是為了減少J2ME平臺的API分裂,為開發(fā)者定義一個高操作性的應(yīng)用程序和服務(wù)環(huán)境。 JTWI(Java Technology for Wireless Industry,JSR 185)定義了一系列的規(guī)范來強制實現(xiàn)JTWI規(guī)范的設(shè)備必須實現(xiàn)某些JSR,例如MIDP2.0,WMA和MMAPI等。MSA for CLDC可以認(rèn)為是JTWI的第2版,它規(guī)定了一個高度集中的J2ME平臺運行環(huán)境。  | 
#p#
檢查JSR支持
檢查JSR的支持簡單的方式有兩種:
1. 是通過System.getProperty("property_name")的方式進(jìn)行判斷,一般如果存在相關(guān)的APIs支持,它會返回一個非null字符串。
檢測代碼
System.getProperty(property_key);  | 
2. 通過Class.forName(clase_name)的方式。
private boolean hasClassExit(String aClassName) { | 
上面的檢測代碼相對比較簡單,而且也容易理解,關(guān)鍵是那些JSR 支持的屬性名稱,或者APIs的寫法。
下面是部分屬性名稱,僅供參考。
| 
 System property  | 
 Description  | 
 Value  | 
| 
 microedition.platform  | 
 Defined in CLDC 1.0 and CLDC 1.1.  | |
| 
 microedition.encoding  | 
 Always returns ISO-8859-1.  | |
| 
 microedition.configuration  | 
 Defined in CLDC 1.0 and CLDC 1.1.  | |
| 
 microedition.profiles  | 
 依賴于底層實現(xiàn)  | |
| 
 microedition.locale*  | 
 JSR 37  | 
 依賴于底層實現(xiàn)  | 
| 
 microedition.commports  | 
 依賴于底層實現(xiàn)  | |
| 
 microedition.hostname  | 
 localhost  | |
| 
 microedition.profiles  | 
 MIDP2.0  | |
| 
 file.separator  | 
 文件分割符  | 
 依賴于底層實現(xiàn)(/,\)  | 
| 
 microedition.pim.version  | 
 JSR 75  | 
 1.0  | 
| 
 microedition.smartcardslots  | 
 JSR 177  | 
 依賴于底層實現(xiàn)  | 
| 
 microedition.location.version  | 
 JSR 179  | 
 1.0  | 
| 
 microedition.sip.version  | 
 JSR 180  | 
 1.0  | 
| 
 microedition.m3g.version  | 
 JSR 184  | 
 1.0  | 
| 
 microedition.jtwi.version  | 
 JSR 185  | 
 1.0  | 
| 
 wireless.messaging.sms.smsc  | 
 JSR 205  | 
 依賴于底層實現(xiàn)  | 
| 
 wireless.messaging.mms.mmsc  | 
 JSR 205  | 
 依賴于底層實現(xiàn)  | 
| 
 CHAPI-Version  | 
 JSR 211  | 
 JSR 211  | 
| 
 Nokia的一些系統(tǒng)參數(shù)  | ||
| 
 com.nokia.network.access  | 
 網(wǎng)絡(luò)參數(shù)  | 
 pd - GSM pd.EDGE - EDGE pd.3G - 3G pd.HSDPA - 3G csd - GSM CSD/HSCSD bt_pan - Bluetooth PAN network wlan - WIFI na - 無任何網(wǎng)絡(luò)  | 
| 
 com.nokia.mid.dateformat  | 
 日期格式  | 
 Yy/mm/dd  | 
| 
 com.nokia.mid.timeformat  | 
 時間格式  | 
 hh:mm  | 
| 
 com.nokia.memoryramfree  | 
 動態(tài)內(nèi)存分配 Note: S60 第3版不支持  | 
|
| 
 com.nokia.mid.batterylevel  | 
 電池狀態(tài)  | 
|
| 
 com.nokia.mid.countrycode  | 
 城市代碼  | 
|
| 
 com.nokia.mid.networkstatus  | 
 網(wǎng)絡(luò)工作狀態(tài)  | 
|
| 
 com.nokia.mid.networkavailability  | 
 網(wǎng)絡(luò)是否激活狀態(tài)  | 
|
| 
 com.nokia.mid.networkid  | 
 網(wǎng)絡(luò)ID  | 
 返回2個值 Network ID 網(wǎng)絡(luò)簡稱  | 
| 
 com.nokia.mid.networksignal  | 
||
| 
 com.nokia.mid.cellid  | 
 Cellid  | 
 基站信息ID  | 
| 
 com.nokia.mid.imei  | 
 Imei號  | 
 手機唯一標(biāo)識號  | 
| 
 com.nokia.mid.imsi  | 
||
應(yīng)用程序?qū)傩?/STRONG>
應(yīng)用程序?qū)傩灾凳窃趹?yīng)用程序描述符文件或者M(jìn)ANIFEST文件中定義的,當(dāng)我們部署應(yīng)用程序的時候可以定義應(yīng)用程序?qū)傩?。比如下面是一個典型的JAD文件內(nèi)容。
MIDlet-1: HttpWrapperMidlet,httpwrapper.HttpWrapperMIDlet
MIDlet-Jar-Size: 16315
MIDlet-Jar-URL: HttpWrapper.jar
MIDlet-Name: HttpWrapper
MIDlet-Vendor: Vendor
MIDlet-Version: 1.0
MicroEdition-Configuration: CLDC-1.0
MicroEdition-Profile: MIDP-1.0
Which-Locale: en
其中Which-Locale就是應(yīng)用程序?qū)傩灾?,我們可以通過MIDlet的成員方法getAppProperty()來得到它,代碼片斷如下:
import javax.microedition.midlet.*;  | 
屬性值對大小寫是敏感的,如果屬性值在底層系統(tǒng)、JAD文件和Manifest文件中都沒有定義的話,那么將返回Null。
#p#
簡單的Demo
下面是簡單的測試環(huán)境的代碼,有經(jīng)驗的朋友可以很容易就就跑起來。
代碼片段
/** 
* getSysInfo 
*/ 
private void getSysInfo() { 
addInfo( "Microedition Configuration: ",
getInfo(System.getProperty( "microedition.configuration")));
addInfo( "Microedition Profiles: ",
getInfo(System.getProperty( "microedition.profiles")));
addInfo( "microedition.jtwi.version:",
getInfo(System.getProperty( "microedition.jtwi.version")));
addInfo( "microedition.platform:",
getInfo(System.getProperty( "microedition.platform")));
addInfo( "microedition.locale:",
getInfo(System.getProperty( "microedition.locale")));
addInfo( "default encoding:",
getInfo(System.getProperty( "microedition.encoding")));
addInfo( "microedition.commports",
getInfo(System.getProperty( "microedition.commports")));
addInfo( "microedition.hostname",
getInfo(System.getProperty( "microedition.hostname")));
//  microedition.smartcardslots 
addInfo( " microedition.smartcardslots",
getInfo(System.getProperty( " microedition.smartcardslots")));
addInfo( "com.nokia.network.access",
getInfo(System.getProperty( "com.nokia.network.access")));   
addInfo( "com.nokia.mid.dateformat",
getInfo(System.getProperty( "com.nokia.mid.dateformat")));
addInfo( "com.nokia.mid.timeformat",
getInfo(System.getProperty( "com.nokia.mid.timeformat")));
addInfo( "com.nokia.memoryramfree",
getInfo(System.getProperty( "com.nokia.memoryramfree")));
addInfo( "com.nokia.mid.batterylevel",
getInfo(System.getProperty( "com.nokia.mid.batterylevel")));
addInfo( "com.nokia.mid.countrycode",
getInfo(System.getProperty( "com.nokia.mid.countrycode")));
addInfo( "com.nokia.mid.networkstatus",
getInfo(System.getProperty( "com.nokia.mid.networkstatus")));
addInfo( "com.nokia.mid.networksignal",
getInfo(System.getProperty( "com.nokia.mid.networksignal")));
addInfo( "com.nokia.mid.networkid",
getInfo(System.getProperty( "com.nokia.mid.networkid")));
addInfo( "com.nokia.mid.networkavailability",
getInfo(System.getProperty( "com.nokia.mid.networkavailability")));
addInfo( "com.nokia.mid.cellid",
getInfo(System.getProperty( "com.nokia.mid.cellid")));
addInfo( "com.nokia.mid.imei",
getInfo(System.getProperty( "com.nokia.mid.imei")));
addInfo( "com.nokia.mid.imsi",
getInfo(System.getProperty( "com.nokia.mid.imsi")));
String[] timeZoneIDs = java.util.TimeZone.getAvailableIDs(); 
StringBuffer timeZonesBuffer = new StringBuffer(); 
for (int i = 0; i < timeZoneIDs.length; i++) {
timeZonesBuffer.append(timeZoneIDs[i]).append('\n'); 
} 
addInfo( "Total memory:",
Long.toString(Runtime.getRuntime().totalMemory()) + " bytes");
addInfo( "Free memory:",
Long.toString(Runtime.getRuntime().freeMemory()) + " bytes");
addInfo( "Available TimeZones:", timeZonesBuffer.toString());
addInfo( "Default TimeZone:", java.util.TimeZone.getDefault().getID());
addInfo( "com.siemens.mp.lcdui.Image", hasClassExit("com.siemens.mp.lcdui.Image") + "");
addInfo( "com.motorola.phonebook.PhoneBookRecord",        hasClassExit("com.motorola.phonebook.PhoneBookRecord") + "");
addInfo( "com.motorola.Dialer", hasClassExit("com.motorola.Dialer") + "");
addInfo( "com.jblend.util.Case", hasClassExit("com.jblend.util.Case") + "");
addInfo( "com.samsung.util.AudioClip", hasClassExit("com.samsung.util.AudioClip") + "");
addInfo( "com.mot.iden.multimedia.Lighting",     hasClassExit("com.mot.iden.multimedia.Lighting") + "");
} 
private boolean hasClassExit(String aClassName) { 
try { 
Class.forName(aClassName); 
return true; 
} catch (Exception e) { 
return false; 
} 
} 
public String getInfo(String info) { 
if (info == null) { 
return "<unknown>";
} else { 
return info; 
} 
} 
public void addInfo(String name, String value) { 
iForm.append(new StringItem(name, value)); 
}
代碼片段2
try {
Class.forName( "javax.microedition.media.control.VideoControl");
addInfo( "MMAPI: ", "yes" );
addInfo( "MMAPI-Version: ", getInfo(System.getProperty("microedition.media.version")) );
} catch (ClassNotFoundException e) {
addInfo( "MMAPI: ", "no" );
}
try {
Class.forName( "javax.wireless.messaging.Message");
addInfo( "WMAPI 1.1: ", "yes" );
try {
Class.forName( "javax.wireless.messaging.MultipartMessage");
addInfo( "WMAPI 2.0: ", "yes" );
} catch (ClassNotFoundException e) {
addInfo( "WMAPI 2.0: ", "no" );
}
} catch (ClassNotFoundException e) {
addInfo( "WMAPI 1.1: ", "no" );
}
try {
Class.forName( "javax.bluetooth.DiscoveryAgent");
addInfo( "Bluetooth-API: ", "yes" );
try {
Class.forName( "javax.obex.ClientSession");
addInfo( "Bluetooth-Obex-API: ", "yes" );
} catch (ClassNotFoundException e) {
addInfo( "Bluetooth-Obex-API: ", "no" );
}
} catch (ClassNotFoundException e) {
addInfo( "Bluetooth-API: ", "no" );
}
try {
Class.forName( "javax.microedition.m3g.Graphics3D");
addInfo( "M3G-API: ", "yes" );
} catch (ClassNotFoundException e) {
addInfo( "M3G-API: ", "no" );
}
try {
Class.forName( "javax.microedition.pim.PIM");
addInfo( "PIM-API: ", "yes" );
} catch (ClassNotFoundException e) {
addInfo( "PIM-API: ", "no" );
}
try {
Class.forName( "javax.microedition.io.file.FileSystemRegistry");
addInfo( "FileConnection-API: ", "yes" );
} catch (ClassNotFoundException e) {
addInfo( "FileConnection-API: ", "no" );
}
try {
Class.forName( "javax.microedition.location.Location");
addInfo( "Location-API: ", "yes" );
} catch (java.lang.Throwable e) {
addInfo( "Location-API: ", "no" );
}
try {
Class.forName( "javax.microedition.xml.rpc.Operation");
addInfo( "WebServices-API: ", "yes" );
} catch (ClassNotFoundException e) {
addInfo( "WebServices-API: ", "no" );
}
try {
Class.forName( "javax.microedition.sip.SipConnection");
addInfo( "SIP-API: ", "yes" );
} catch (ClassNotFoundException e) {
addInfo( "SIP-API: ", "no" );
}
try {
Class.forName( "com.nokia.mid.ui.FullCanvas");
addInfo( "Nokia-UI-API: ", "yes" );
} catch (ClassNotFoundException e) {
addInfo( "Nokia-UI-API: ", "no" );
}
try {
Class.forName( "com.siemens.mp.MIDlet");
addInfo( "Siemens-Extension-API: ", "yes" );
try {
Class.forName( "com.siemens.mp.color_game.GameCanvas");
addInfo( "Siemens-ColorGame-API: ", "yes" );
} catch (ClassNotFoundException e) {
addInfo( "Siemens-ColorGame-API: ", "no" );
}
} catch (ClassNotFoundException e) {
addInfo( "Siemens-Extension-API: ", "no" );
}
}
附表:屬性表
表1 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”,多個格式之間使用至少一個空格進(jìn)行間隔  | 
| 
 video.encodings  | 
 代表手機支持的視頻格式,返回值格式為“encoding=video/3gpp”,多個格式之間使用至少一個空格進(jìn)行間隔  | 
| 
 video.snapshot.encodings  | 
 代表手機使用getSnapshot方法獲得的視頻快照格式,返回值格式為“encoding=png”,多個格式之間使用至少一個空格進(jìn)行間隔  | 
| 
 streamable.contents  | 
 代表手機支持的流媒體格式,返回null代表不支持  | 
表2 Wireless Messaging API屬性
| 
 屬性名稱  | 
 屬性作用  | 
| 
 wireless.messaging.sms.smsc  | 
 代表手機發(fā)送短信時的短信服務(wù)中心號碼  | 
表3FileConnection 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  | 
 代表手機中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”  | 
【編輯推薦】















 
 
 
 
 
 
 