盡管支持移動信息設備規范MIDP 1.0的手機大量地出現在市面上,不管是Nokia未來發售的各款手機,還是Motorola T720、V60i,Sony Ericsson P800等手機,每一款都支持MIDP 1.0。但是,Sun也沒忘記繼續和各家手機廠商制定下一代Java手機的規格—MIDP 2.0。
應用程序管理員在規格中也稱作Application Management Software。這是一個用來執行J2ME應用程序的程序,它負責管理該裝置上所有的J2ME應用程序。
應用程序管理員的設計方式會隨著平臺的不同而不同,但是大致上可以分成兩種方式:
1. 在背后運作,使用者不知道應用程序管理員的存在。這種類型的應用程序管理員概念如圖1所示。

背后運作的應用程序管理員的設計方式,使得一般的J2ME程序看起來和應用程序管理員一樣,即使實際上應用程序管理員在背后運作,使用者也很難感受到。這種設計可以在MIDP for Palm之中看到,Java HQ就是這樣的東西,如圖2所示。

但是,如果是程序開發者一旦安裝了Developer.prc,仍然可以透過Java HQ之中的Developer Preference里的MIDlets按鈕來觀察整個系統之中所安裝的每一個Java應用程序。
2. 一個單一的進入點,使用者必須先進入應用程序管理員,然后才能啟動個別的Java應用程序。這種類型的應用程序管理員概念如圖3所示。

這種應用程序管理員設計可以在Motorola A388、Nokia 9210、Nokia 7650的手機上看到。
一個完整的MIDP應用程序是由一個JAD文件(純文字文件)與JAR(ZIP壓縮檔)所組成。JAD與JAR之間的關系可以用圖4簡單描述。之所以有這樣的設計,主要為了下面兩個原因:

1. 網絡傳輸費用;
2. 安全性。
MIDP 2.0之后,為了保護JAR不受竄改,同時也讓安裝程序的人可以確定MIDlet的來源,所以特別增加了安全設計如圖5。

其中,MIDlet-Jar-RAS-SHA1屬性值為經過base64編碼的執行文件數字簽章。而MIDlet-Certificate-
最后請注意,并非每種裝置在安裝時都要求同時有JAD與JAR,有些裝置只需要JAR即可。不過,有JAD和沒有JAD的J2ME應用程序在安全性上是有差異的。
根據MIDP規格,所謂MIDP執行環境(MIDP Execution Environment)指的是下面幾項所構成的集合:
1. 實作CLDC中所定義的類別函數庫的類別檔(以Java撰寫)及原生程序(Native code,以C撰寫)。MIDlet Suite里不能有與CLDC類別函數庫同樣名稱的類別;
2. 實作MIDP中所定義的類別函數庫的類別檔(以Java撰寫)及原生程序(Native code,以C撰寫)。MIDlet Suite里不能有與MIDP類別函數庫同樣名稱的類別;
3. 所有來自同一個JAR檔中的類別檔。包括設計者自己所撰寫的類別、其它的JSR(例如Profile或Optional Package),或其它開放的函數庫(例如kXML或kSOAP);
4. 所有來自同一個JAR檔之中的非類別檔(即資源文件),另外,記錄管理系統(RMS,MIDP版的數據庫管理系統)也是可共享的資源之一;
5. 權限確認與連結外部資源;
6. 描述文件與清單文件的內容。
以上這幾點構成所謂的MIDP執行環境。應用程序管理員會保證這些資源都可以在執行時期供MIDlet存取。而且,位于同一個MIDlet Suite的MIDlet會共享同一組MIDP執行環境,并且可以彼此互動。MIDlet可以調用CLDC的類別函數庫,也可以調用MIDP的類別函數庫,如圖6所示。

只有存取標準CLDC與MIDP函數庫的MIDlet Suite才可以跨平臺。通常手機廠商會針對自己的裝置開發專屬的API,例如Nokia的UI API、SMS API、Camera API。一旦程序使用了這些專屬API,那么除非其它廠商也在其虛擬機器之中實作這些API,否則很難達到跨平臺的目的。
位于JAR檔之中的類別檔可以被同一個JAR檔之中的MIDlet所使用。同一個JAR檔里的資源檔可以透過java.lang.Class.getResourceAsStream()來存取。描述檔的內容則可以透過javax.microedition.mdilet. MIDlet.getAppProperty()取得,如圖7所示。

如果曾經撰寫過Java Applet或Java Servlet,就知道制作Java Applet,必須繼承自java.applet.Applet這個類別;制作Java Servlet,則程序必須繼承自javax.servlet. http.HttpServlet這個類別。同理,要撰寫能夠在手機上執行的Java MIDlet必須要繼承自javax.microedition.midlet. MIDlet類別,如圖8所示。

javax.microedition.midlet.MIDlet類別中定義了三個抽象方法,它們分別是:startApp() ==> 至運作狀態;pauseApp() ==> 至停止狀態;destoryApp() ==> 至消滅狀態。應用程序管理員就是透過這三個抽象方法來控制MIDlet的生命周期。因此,所有的MIDlet都必須實現這三個方法,才保證能正常運作。因此,一個MIDlet的程序外殼至少要如下:
|
根據MIDP規格,MIDlet中不應該有“public static void main(Straing[] args)”這個方法。如果有則應用程序管理員會忽略不管。
一旦MIDlet被JAM載入之后,首先會先呼叫MIDlet中沒有參數的建構式以進行初始化的工作。如果熟悉Java語法一定知道Java語言的一些特性,就是如果沒有在程序中加入任何建構式,編譯器會自動幫助加入一個預設建構式。但是如果已經撰寫了自己的建構式(有任何參數),那么編譯器將不會自動幫助加上預設建構式。因此,如果有必要撰寫有參數的建構式,別忘了再手動加上一個沒有參數的建構式,否則MIDlet將無法正確地初始化。
當MIDlet被應用程序管理員產生之后,MIDlet就開始運作,程序設計師應該做的事情如圖9所示。

我們會使用Display.getDisplay(this)來取得代表該MIDlet顯示屏的Display對象。從應用程序管理員呼叫startApp()到MIDlet結束運作這段時間之內,不管何時呼叫Display.getDisplay(this),取得的都是同一份Display對象的參考。
要設定顯示在屏幕上的畫面,會使用Display對象的參考,并調用其setCurrent()方法,即display.setCurrent( Displayable類別的子類別實體)。因此一個可以運作的MIDlet程序,至少如HelloMIDlet.java,代碼為:
|
注意:根據規格MIDlet只能由應用程序管理員產生,我們不能自己在程序里生成其它的MIDlet,并呼叫其生命周期函數,這樣做將引發SecurityException異常。
前面簡單地敘述了應該如何撰寫一個MIDlet的輪廓。但事實上,MIDlet的運作稍微復雜一點。所以接下來要仔細探討MIDlet的運作細節,也就是MIDlet的生命周期。
當MIDlet被應用程序管理員成功地初始化之后,就開始展開了它的生命周期。圖10描述了一個MIDlet的生命周期。MIDlet的生命周期完全由應用程序管理員控制,也就是說,當MIDlet要從一個狀態變成另外一個狀態時,應用程序管理員會呼叫對應的回呼函數(Call Back,也就是MIDlet類別定義的那三個抽象方法)。MIDlet基本上有三種狀態,分別是停止狀態(Paused)、啟動狀態(Active)及毀滅狀態(Destroyed)。MIDlet開始時一定是先進入停止狀態,然后應用程序管理員再將它轉換成啟動狀態,然后調用startApp(),見圖10。只有當應用程序管理員認為MIDlet的狀態必須改變時,才會呼叫圖中的相關函數。

如果MIDlet自己調用這些函數,通常不會發生錯誤(除非程序本身有邏輯上的錯誤),但是也不會造成狀態的轉換,只是一個單純的函數呼叫而已。如果MIDlet在狀態轉換回呼函數執行時發生錯誤,那么就應該丟出MIDletStateChange Exception異常,讓應用程序管理員知道該如何處理。
請使用下列程序HelloMIDlet.java代碼來驗證MIDlet的生命周期:
|
執行結果時我們會在訊息顯示窗口看到:
|
此結果表示建構式先被呼叫,然后startApp()才會呼叫。
這時如果我們按下強制關閉應用程序的按鈕 ,屏幕上就會出現:
|
這代表當我們使用裝置上的按鈕強制關閉MIDlet時,應用程序管理員會調用destroyApp(),并傳入true作為參數。因此,撰寫程序時,可以假定destroyApp()傳入true時,是硬件強制關閉MIDlet的情形。
從圖10中可以看出,startApp()很可能不只是被呼叫一次,而是每次從停止狀態重新回到運作狀態的時候都會被應用程序管理員調用,所以只需要被初始化一次的動作就不適合放在startApp()之中,請改用建構式做初始化動作。如果startApp()丟出MIDletStateChangeException或RuntimeException或兩者的子類別,那么會立刻進入毀滅狀態,而且系統會自動調用destroyApp(true)。
由于規格告訴我們,startApp()的執行時間應該盡可能短。如果程序在執行時,發生的錯誤是可以稍候就解決的(很可能是系統資源暫時不足),那么程序設計師就該直接丟出MIDletStateChangeException。攔截之后,再調用notifyPaused(),稍待一會再藉由異步事件呼叫resumeRequest(),重新試看看。如果發生錯誤即使稍待一會也無法解決,那么程序設計師就應該直接調用notifyDestroyed()來結束程序。參見如下代碼:
|
執行結果如下:
|
一般來說,應用程序管理員會因為某些狀況必須請MIDlet停止運作,例如手機突然來電、鬧鈴響了或者使用者切換到其它程序執行。在這些情況下,為了避免MIDlet占用太多系統資源,應用程序管理員就會調用該MIDlet的pauseApp()。這樣程序設計師應該在pauseApp()之中適時釋放一些非必需的資源,等到回到運作狀態時,應用程序管理員會重新呼叫startApp(),這時再將這些之前被pauseApp()釋放的資源重新找回來。
同樣的情況也發生在destroyApp()。通常此方法被調用的時候,代表MIDlet要被關閉了,所以程序設計師應該在這里釋放自己所配置的資源。只要MIDlet進入了毀滅狀態,就無法再回頭。如果是系統自己調用destroyApp(),那么在destroyApp()執行時萬一發生例外,這些例外將被忽略,MIDlet一樣會被關閉。根據規格,我們不能在MIDlet之中直接呼叫System.exit()或Runtime.exit()來結束程序的執行。如果這樣做,就會引發java.lang.SecurityException異常。
除了由應用程序管理員來控制MIDlet的生命周期之外,MIDlet本身也可以決定自己的狀態,但不是自己改變狀態,而是MIDlet先呼叫上述相對應的狀態改變函數。這些函數會發出訊息通知應用程序管理員,請它來幫助改變MIDlet的狀態,但是決定權在應用程序管理員,不保證一定可行。狀態改變函數如圖11所示:

對照以上這兩張與狀態有關的圖,舉個例子大家可能會比較清楚:假設今天如果是MIDlet主動要將MIDlet的狀態由運作狀態變成停止狀態,那么直接呼叫pauseApp()函數只會執行pauseApp()之中的程序代碼,而無法改變MIDlet的狀態。MIDlet必須呼叫notifyPaused()以通知應用程序管理員,應用程序管理員收到通知之后,才會讓MIDlet進入停止狀態。
不過,由MIDlet調用notifyPaused()與應用程序管理員主動要求停止,兩者之間是有所差別的。它們主要在于應用程序管理員主動要求停止時,pauseApp()會被呼叫,而由MIDlet調用notifyPaused(),pauseApp()不會被調用。但是兩者都會讓MIDlet進入停止狀態。所以在MIDlet調用notifyPaused()之前,最好自己也先調用pauseApp()比較適當。
同樣的情況也發生在notifyDestroyed()與destroyApp()。除非是系統強制關閉MIDlet,否則最好MIDlet先呼叫destroyApp(),然后再呼叫notifyDestroyed()。請應用程序管理員幫助將MIDlet轉換到毀滅狀態,最后結束MIDlet的運作。單單MIDlet自己呼叫destroyApp()是沒有用的。
destroyApp()有個布爾值作為參數,根據MIDP的規格,如果傳入true,那么MIDlet不管如何應該無條件釋放所有資源,然后讓應用程序管理員結束MIDlet的運作,這是系統或硬件強制關閉MIDlet的情形。如果使用者調用notifyDestroyed()來結束MIDlet,那么在調用destroyApp()時,最好傳入false,代表這并非系統或硬件強制關閉,這時如果MIDlet不希望結束執行,那么它可以藉由丟出MIDletStateChangeException異常告知呼叫它的人:“我還不想被消滅,請待會再來。”
這里可以看出startApp()、pauseApp()及destroyApp()并非控制MIDlet生命周期的函數,它們只是一個提供初始化資源、釋放資源的地方而已。
根據MIDP 規格書中所說,即使MIDlet處于停止狀態,它仍然可以處理異步事件(Asynchronous Event),比方說Timer的事件或是其它回呼函數(Callback)。這將涉及到resumeRequest()的使用。resumeRequest()會將MIDlet從停止狀態回到運作狀態,并調用startApp()。由于當時MIDlet處于停止狀態,所以必須依靠異步事件使MIDlet重新回到運作狀態。
在一些范例程序中,我們常常只呼叫notifyDestroyed(),而沒有呼叫destroyApp(),這是因為范例通常沒有釋放資源的需求,所以可以不用呼叫。但是如果是正規的程序,建議記得呼叫destroyApp(false)會比較好。
通過本文的討論,相信大家都可以發現MIDP更成熟了,功能更強了,但是也更復雜了。
附注:本文中出現的Java程序代碼已在Java 2 SDK 1.4.x與J2ME Wireless Toolkit 2.0的Win32版本上完成測試。本文所有操作皆在Windows 2000 Professional與Windows XP Professional中文版操作系統上經過測試。本文的范例程序代碼請至Http://www.javatwo.net/experts/moli/publish/下載。
名詞解釋
MIDP: Java API中面向移動終端的集合。通過與J2ME中的面向移動終端產品配置CLDC配合使用,就能夠提供J2ME應用程序所需的運行環境。主要用于手機、PDA及雙向尋呼機等低價位移動信息終端。
MIDlet: 即一個可以執行的J2ME/MIDP應用程序基本單位。除了繼承自javax.microedition.midlet.MIDlet之外,還包括讓此類別可以順利執行的所有其它類別和資源檔(只要是非類別檔都稱作資源檔)所構成的集合,所以一般又稱做MIDlet應用程序(MIDlet Application)。
MIDlet Suite: 許多MIDlet所構成的集合一般又叫做MIDlet應用程序套件。MIDlet Suite和MIDlet的關系,就好像Office與Word、Excel、PowerPoint、Access的關系。
JAR檔(.jar檔): 實際上包含MIDlet Suite的檔案,屬于ZIP檔格式。
描述檔(.jad檔): 用來描述一個MIDlet Suite基本數據,以及該MIDlet Suite內含的MIDlet內含的MIDlet相關信息(類別名稱、圖標、程序名)的外部檔案(不在JAR檔內部)。
應用程序管理員(Java Application Manager): 負責將MIDlet Suite安裝到機器上執行,以及負責管理MIDlet生命周期的機制(或軟件)總稱。應用程序管理員會根據使用者的需求安裝、啟動、停止或移除相對應的MIDlet。