開發高可移植性J2ME的軟件
隨著MTK的流行,使現在的J2ME虛擬機市場上品牌眾多,除了索愛,Nokia S40,Moto,三星,LG等國際大品牌的虛擬機,更是有MTK,展訊內置的一些不知名的虛擬機,因此當初Write Once,Run AnyWhere變成了Write Once,Debug AnyWhere了。對于一個沒有經驗的J2ME程序員來說,開發一個兼容性高的軟件變成了噩夢,不斷的在不同手機,不同平臺上打log,在這臺手機上解決了這個問題,跑到另外一臺機器上問題有重新了,噢,my god!我不干了。別急!我寫這篇文章的目的就是要告訴大家,對于這種狀況,我們也不是束手無策的。下面就等我慢慢的道來解決之道。
本文主要適合那些有經驗的J2ME程序員在優化軟件,或者是需要考慮軟件兼容性時的參考文檔。
Jblend 平臺
JBlend 是一家日本的嵌入式虛擬機廠家生產的J2ME虛擬機,此虛擬機大量的用于低端手機平臺,本人發現有使用此虛擬機的平臺有,MTK,MOTO。
官方網站:http://www.aplixcorp.com/chs/index.html 。
索尼愛立信平臺
索愛的虛擬機平臺是:Java Platform。最新版本是8。索愛的平臺在性能上,程序的穩定性方面要優于其他虛擬機平臺。而且APIs方面的bug也很少,在網絡支持方面也很優秀。基本上不會因為你忘記關閉連接而導致連接泄漏。
官方網站:http://developer.sonyericsson.com/site/zhcn/docs_and_tools/p_docs_and_tools.jsp
S40平臺
S40平臺是Nokia針對S60智能操作系統推出適應低端手機的手機操作系統,相對其他虛擬機平臺來說,S40虛擬機對J2ME的支持相對比較完善,而且穩定些,不過網絡環境這塊,S40對網絡資源泄漏特別關注,具體不同的手機,對同時打開多個連接有限制,這里建議大家做個測試,就不再累贅了。
官方網站:http://www.forum.nokia.com/
S40平臺詳解:http://tech.sina.com.cn/mobile/n/2006-09-22/1053107637.shtml
S60 平臺
Nokia 智能機平臺下的J2ME虛擬機。相對S40來說,S60支持的特性比較多,而且有些比較特殊的用法,比如獲取系統相關屬性的時候就是其中之一。
什么是JCP?
JCP(Java Community Process) 是一個開放的國際組織,主要由Java開發者以及被授權者組成,職能是發展和更新Java技術規范、參考實現(RI)、技術兼容包(TCK)。Java技 術和JCP兩者的原創者都是SUN計算機公司。然而,JCP已經由SUN于1995年創造Java的非正式過程,演進到如今有數百名來自世界各地Java 代表成員一同監督Java發展的正式程序。JCP維護的規范包括J2ME、J2SE、J2EE,XML,OSS,JAIN等。組織成員可以提交JSR(Java Specification Requests),通過特定程序以后,進入到下一版本的規范里面。所有聲稱符合J2EE規范的J2EE類產品(應用服務器、應用軟件、開發工具等),必須通過該 組織提供的TCK兼容性測試(需要購買測試包),通過該測試后,需要繳納J2EE商標使用費。兩項完成,即是通過J2EE認證(Authorized Java Licensees of J2EE)。
什么是JSR?
JSR是Java Specification Requests的縮寫,意思是Java 規范請求。是指向JCP(Java Community Process)提出新增一個標準化技術規范的正式請求。任何人都可以提交JSR,以向Java平臺增添新的API和服務。JSR已成為Java界的一個重要標準。
下面是J2ME JSR規范列表
名稱 |
內容 |
JSR 118 |
MIDP 2.1 規范。定義了MIDP 相關的接口,高級UI,低級UI,RMS,網絡相關的APIs |
JSR 82 |
定義了藍牙接口相關的APIs |
JSR135 |
Mobile Media API,定義了多媒體相關開發的組件APIs |
JSR 172 |
1. 一個輕量級的標準XML解析器 |
JSR 75 |
JSR 75(PDA Optional Packages for the J2METM Platform)中定義了兩個可選包: |
JSR 177 |
安全APIs |
JSR 211 |
Content Hander 內容處理APIs,可以調用此API打開相應的文件,比如你可以打開jar安裝文件,打開mp3。 |
JSR 239 |
Open GL@ES。主要用于圖形相關操作 |
JSR 179 |
Location APIs 主要是用于LBS服務 |
JSR 180 |
SIP APIs SIP是一個應用層的信令控制協議。用于創建、修改和釋放一個或多個參與者的會話。這些會話可以好似Internet多媒體會議、IP電話或多媒體分發。會話的參與者可以通過組播(multicast)、網狀單播(unicast)或兩者的混合體進行通信。 |
JSR 184 |
Mobile 3D Graphics APIs,3D圖形開發。 |
JSR 229 |
手機支付APIs |
JSR 234 |
手機高級多媒體支持,可以支持更豐富的多媒體操作 |
JSR 238 |
國際化支持APIs |
JSR 248 |
JSR 248: Mobile Service Architecture MSA 移動服務架構。 MSA for CLDC規范定義了移動電話上的下一代Java平臺,當然是基于CLDC的J2ME平臺。 MSA for CLDC的目的是為了減少J2ME平臺的API分裂,為開發者定義一個高操作性的應用程序和服務環境。 JTWI(Java Technology for Wireless Industry,JSR 185)定義了一系列的規范來強制實現JTWI規范的設備必須實現某些JSR,例如MIDP2.0,WMA和MMAPI等。MSA for CLDC可以認為是JTWI的第2版,它規定了一個高度集中的J2ME平臺運行環境。 |
#p#
檢查JSR支持
檢查JSR的支持簡單的方式有兩種:
1. 是通過System.getProperty("property_name")的方式進行判斷,一般如果存在相關的APIs支持,它會返回一個非null字符串。
檢測代碼
System.getProperty(property_key); |
2. 通過Class.forName(clase_name)的方式。
private boolean hasClassExit(String aClassName) { |
上面的檢測代碼相對比較簡單,而且也容易理解,關鍵是那些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 |
依賴于底層實現 | |
microedition.locale* |
JSR 37 |
依賴于底層實現 |
microedition.commports |
依賴于底層實現 | |
microedition.hostname |
localhost | |
microedition.profiles |
MIDP2.0 | |
file.separator |
文件分割符 |
依賴于底層實現(/,\) |
microedition.pim.version |
JSR 75 |
1.0 |
microedition.smartcardslots |
JSR 177 |
依賴于底層實現 |
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 |
依賴于底層實現 |
wireless.messaging.mms.mmsc |
JSR 205 |
依賴于底層實現 |
CHAPI-Version |
JSR 211 |
JSR 211 |
Nokia的一些系統參數 | ||
com.nokia.network.access |
網絡參數 |
pd - GSM pd.EDGE - EDGE pd.3G - 3G pd.HSDPA - 3G csd - GSM CSD/HSCSD bt_pan - Bluetooth PAN network wlan - WIFI na - 無任何網絡 |
com.nokia.mid.dateformat |
日期格式 |
Yy/mm/dd |
com.nokia.mid.timeformat |
時間格式 |
hh:mm |
com.nokia.memoryramfree |
動態內存分配 Note: S60 第3版不支持 |
|
com.nokia.mid.batterylevel |
電池狀態 |
|
com.nokia.mid.countrycode |
城市代碼 |
|
com.nokia.mid.networkstatus |
網絡工作狀態 |
|
com.nokia.mid.networkavailability |
網絡是否激活狀態 |
|
com.nokia.mid.networkid |
網絡ID |
返回2個值 Network ID 網絡簡稱 |
com.nokia.mid.networksignal |
||
com.nokia.mid.cellid |
Cellid |
基站信息ID |
com.nokia.mid.imei |
Imei號 |
手機唯一標識號 |
com.nokia.mid.imsi |
應用程序屬性
應用程序屬性值是在應用程序描述符文件或者MANIFEST文件中定義的,當我們部署應用程序的時候可以定義應用程序屬性。比如下面是一個典型的JAD文件內容。
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就是應用程序屬性值,我們可以通過MIDlet的成員方法getAppProperty()來得到它,代碼片斷如下:
import javax.microedition.midlet.*; |
屬性值對大小寫是敏感的,如果屬性值在底層系統、JAD文件和Manifest文件中都沒有定義的話,那么將返回Null。
#p#
簡單的Demo
下面是簡單的測試環境的代碼,有經驗的朋友可以很容易就就跑起來。
代碼片段
/**
* 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”,多個格式之間使用至少一個空格進行間隔 |
video.encodings |
代表手機支持的視頻格式,返回值格式為“encoding=video/3gpp”,多個格式之間使用至少一個空格進行間隔 |
video.snapshot.encodings |
代表手機使用getSnapshot方法獲得的視頻快照格式,返回值格式為“encoding=png”,多個格式之間使用至少一個空格進行間隔 |
streamable.contents |
代表手機支持的流媒體格式,返回null代表不支持 |
表2 Wireless Messaging API屬性
屬性名稱 |
屬性作用 |
wireless.messaging.sms.smsc |
代表手機發送短信時的短信服務中心號碼 |
表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” |
【編輯推薦】