基本信息
Java手機核心技術MIDP建立在CLDC(Connected Limited Device Configuration)的基礎上。Sun於2002年9月發布了J2ME Personal Profile 1.0,但和MIDP不同的是,Personal Profile建立在CDC(Connected Device Configuration)的基礎上。CDC提供了一個功能完整的Java 2虛擬機,和CLDC相比,CDC要求有更高的記憶體和更可靠的網路連線。
Personal Profile包含了完整的AWT API集,支持圖形用戶接口(GUI),支持Applet和Xlet,為高端PDA套用提供了一個完整的開發、運行環境。
Xlet套用模型繼承自Personal Basis Profile,是Personal Profile最重要的特色之一。什麼是Xlet呢?就象J2SE環境下的Applet,Xlet也是一種必須在宿主(應用程式管理器)之內運行的套用。也就是說,Xlet本身不包含main()方法,不能作為獨立的應用程式運行。但是,Xlet總是實現一組讓應用程式管理器控制其狀態的接口。
和J2SE領域的Applet相比,Xlet在J2ME領域的地位可能重要得多——構想一下,PDA將能夠下載各種第三方的Xlet套用,輕鬆實現PDA功能的動態擴展;甚至一個Xlet可以通過互操作機制提供對其他Xlet的服務,從而開發出由多個模組化Xlet構成的類似客戶機/伺服器體系的複雜套用。
生命周期
每一個Xlet必須實現javax.microedition.xlet.Xlet接口定義的四個方法:public interface Xlet {
public void initXlet(XletContext ctx) throws XletStateChangeException;
public void startXlet() throws XletStateChangeException;
public void pauseXlet();
public void destroyXlet(boolean unconditional) throws
XletStateChangeException;
}
和Applet一樣,對於Xlet來說生命周期也是一個很重要的概念。Xlet的應用程式管理器正是通過上述四個方法來控制Xlet的狀態。要理解Xlet編程,首先必須理解Xlet的生命周期。
Xlet的生命周期包括下面四種狀態:
一 裝入(Loaded):已經從本地存儲器或網路裝入Xlet,且已調用其不帶參數的構造函式。此時如果調用Xlet的initXlet()方法,Xlet可以轉入暫停狀態。
二 暫停(Paused):Xlet已初始化,且已做好激活的準備,相當於進程的“已準備好”狀態——已經做好了隨時在CPU上運行的準備。這時如果調用Xlet的startXlet()方法,它就進入活動狀態。
三 活動(Active):Xlet正在正常運行。如果調用Xlet的destoryXlet()方法,則它進入“拆除”狀態,如果調用pauseXlet()方法,則進入暫停狀態。
四 拆除(Destroyed):這是Xlet的終止狀態。進入已拆除狀態的Xlet不能再轉入其他狀態,Xlet占用的所有資源將被回收。
Xlet可以從任何其他狀態轉入拆除狀態。圖1顯示了Xlet各種狀態的關係。
圖1
實現接口
javax.microedition.xlet.Xlet接口定義的方法也稱為“生命周期方法”。必須注意的是,用戶應用程式(包括Xlet本身)不應該直接調用生命周期方法——即使強行調用,Xlet的狀態也不會改變。生命周期方法應當由管理器調用,管理器通過生命周期方法來通知Xlet改變其狀態。從這個意義上來看,生命周期方法就象是一種事件句柄。
initXlet(XletContext ctx)
管理器裝入並實例化Xlet之後,調用initXlet()方法初始化Xlet。initXlet()的參數是一個XletContext,這個參數很重要,它是Xlet獲取其運行上下文環境的唯一途徑。XletContext類不僅提供了一些方法來提取傳遞給Xlet的參數,而且還有一個可供Xlet放置AWT組件的容器,更重要的是,Xlet本身還可以通過XletContext發出狀態變換命令以及與其他Xlet通信。
startXlet(),pauseXlet()
startXlet()方法告知Xlet開始提供服務,並將Xlet轉入活動狀態。pauseXlet()的功能恰好相反:要求Xlet停止服務,並將其轉入暫停狀態。
initXlet()和startXlet()主要的不同之處在於:前者只能調用一次,對於後者,當管理器希望Xlet進入或者重新進入活動狀態時可以多次調用。因為startXlet()可能被多次調用,所有只能執行一次的初始化工作應當在initXlet()而不是startXlet()中進行。
通常而言,類似startXlet()方法的位置是放置某些業務邏輯的好地方。但是,Personal Profile規範指出Xlet接口中定義的所有方法只能用於狀態轉換,Xlet套用管理器要求這些方法儘快返回。如果生命周期方法沒有在一定的時間內(與具體的套用實現有關)返回,管理器將認為遇到了錯誤,直接拆除Xlet。因此,不應該在任何生命周期方法內放置長時間執行的業務邏輯,如果Xlet需要提供某種可反覆啟動、停止的服務,可以考慮用一個專用的執行緒來提供服務,生命周期方法通過與該服務執行緒的通信來控制服務的啟動、停止,例如由生命周期方法設定一個服務啟動/停止的標記。
destroyXlet(boolean unconditional)
該方法通知Xlet結束運行,轉入拆除狀態。Xlet應當釋放所有的資源。參數unconditional由管理器設定,表示是否要無條件地拆除Xlet。如果unconditional是false,Xlet可以拋出一個StateChangeException異常,表示自己不想被拆除——但是,是否接受Xlet請求最終還是由管理器決定。也就是說,雖然Xlet有權合法地拋出StateChangeException異常,但最終決定其命運的還是管理器。如果管理器接受了Xlet要求不拆除的請求,它會給Xlet一些時間,一定的時間後再次調用destory()方法,這次unconditional一般會設定成true。當unconditional參數是true時,管理器將忽略任何XletStateChangeException異常,一旦destoryXlet()返回就直接拆除Xlet。
可以從生命周期方法拋出的異常有兩種:XletStateChangeException,未被捕獲的RuntimeException或錯誤。
如果生命周期方法拋出了未處理的RuntimeException或錯誤,管理器將立即調用Xlet的destoryXlet(true)方法,將Xlet拆除。因此,Xlet應當捕獲所有“正常的”(原因已知的)RuntimeException或錯誤,避免將RuntimeException直接拋給管理器從而導致Xlet被拆除。相對而言,XletStateChangeException可以由Xlet有意地拋出,表示Xlet尚未做好改變狀態的準備。
轉換狀態
如前所述,Xlet接口中定義的生命周期方法是供管理器通知Xlet轉換狀態用的。那么,如果Xlet本身想要轉換狀態,例如用戶想要結束Xlet(但不想等待管理器發出停止Xlet的命令),又該如何進行呢?
XletContext提供了三個方法讓Xlet發出轉換狀態通知,分別是:notifyDestroyed(),notifyPaused(),和resumeRequest()。notifyDestroyed()告訴管理器Xlet想要結束運行。就象調用destroyXlet()一樣,在調用notifyDestroyed()之前Xlet應當釋放所有的資源。調用notifyDestroyed()之後Xlet將立即無條件地轉入拆除狀態。
notifyPaused()將Xlet轉入暫停狀態。Xlet可以通過調用notifyPaused()為其他Xlet讓出運行資源。一般地,後繼的resumeRequest()調用能夠把Xlet返回到活動狀態,但正如該方法名稱所示的,調用resumeRequest()只是發出了一個請求,而該請求能否被接受是沒有辦法保證的,最終要由管理器來決定Xlet是否可以返回、何時返回活動狀態——Xlet可能要等待一段較長的時間才能返回活動狀態,可能根本不能返回活動狀態;或者,由於缺少資源,管理器可能調用destroyXlet()來結束一個暫停的Xlet。
開發實例
下面通過一個簡單的實例示範Xlet的開發。這個Xlet是一個簡單的數字時鐘,顯示出當前的小時、分鐘、秒,用戶可以隨時暫停或終止時鐘。這個時鐘可以通過它本身的按鈕控制,也可以通過管理器調用Xlet的生命周期方法來控制。Sun為Personal Profile提供了一個參考實現,其管理器稱為XletRunner()。如果在XletRunner中運行時鐘Xlet,用戶可以通過XletRunner的選單來控制時鐘狀態,例如要暫停時鐘,除了點擊時鐘的“暫停”按鈕,還可以選擇XletRunner的ClockXlet選單並選擇pause。
下面是時鐘Xlet的完整代碼。
import javax.microedition.xlet.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
public class ClockXlet implements Xlet,ActionListener {
TextField display;
MyClock clock;
Button pauseButton = new Button("暫停");
Button stopButton = new Button("停止");
Button resumeButton = new Button("繼續");
XletContext context;
public void initXlet(XletContext ctx) throws
XletStateChangeException{
Container c;
context = ctx;
try {
c = ctx.getContainer();
} catch (UnavailableContainerException e) {
throw new XletStateChangeException(e.getMessage());
}
display = new TextField(30);
clock = new MyClock(display);
pauseButton.addActionListener(this);
resumeButton.addActionListener(this);
resumeButton.setEnabled(false);
stopButton.addActionListener(this);
c.setSize(200,200);
c.setVisible(true);
c.add(display);
c.add(pauseButton);
c.add(resumeButton);
c.add(stopButton);
clock.start();
}
public void startXlet() {
clock.setPaused(false);
resumeButton.setEnabled(false);
pauseButton.setEnabled(true);
}
public void pauseXlet() {
clock.setPaused(true);
resumeButton.setEnabled(true);
pauseButton.setEnabled(false);
}
public void destroyXlet(boolean unconditional) {
clock.setStopped(true);
}
public void actionPerformed(ActionEvent e) {
if (e.getSource() == stopButton) {
clock.setStopped(true);
context.notifyDestroyed();
} else if (e.getSource() == pauseButton) {
clock.setPaused(true);
resumeButton.setEnabled(true);
pauseButton.setEnabled(false);
context.notifyPaused();
} else if (e.getSource() == resumeButton) {
context.resumeRequest();
} } }
class MyClock extends Thread {
boolean paused,stopped;
TextField display;
public MyClock(TextField t) {
display = t;
}
String getTime() {
Calendar rightNow = Calendar.getInstance();
String hour = String.valueOf(rightNow.get(Calendar.HOUR_OF_DAY));
String min = String.valueOf(rightNow.get(Calendar.MINUTE));
if (min.length() == 1) {
min = "0" + min;
}
String sec = String.valueOf(rightNow.get(Calendar.SECOND));
if (sec.length() == 1) {
sec = "0" + sec;
}
return hour + ":" + min + ":" + sec;
}
public synchronized boolean isStopped() {
return stopped;
}
public synchronized void setStopped(boolean value) {
stopped = value;
notifyAll();
}
public synchronized boolean isPaused() {
return paused;
}
public synchronized void setPaused(boolean value) {
paused = value;
notifyAll();
}
public void run() {
while (!isStopped()) {
try {
if (!isPaused()) {
Thread.sleep⑴;
display.setText(getTime());
} else {
synchronized (this) {
wait();
}
}
} catch (InterruptedException e) {
} } } }
首先來看看MyClock類,它的主要功能是在一個TextField中顯示出當前時間。MyClock類擴展了Thread類,其主要功能在run()方法中實現。MyClock類用兩個標記來表示是否出現了暫停或停止請求;如果沒有這類請求,時鐘將正常運行,每隔1秒刷新當前時間。如果已出現暫停請求,時鐘轉入等待狀態,直至暫停標記被清除再次喚醒執行緒。如果出現了停止時鐘的請求,執行緒結束運行。
MyClock由ClockXlet控制。initXlet()方法首先創建一個供MyClock使用的TextField,以及幾個用來控制時鐘的按鈕。然後,它啟動MyClock的執行緒。這裡的startXlet()和pauseXlet()都很簡單,很快就可以返回(記住,生命周期方法應當儘快返回),其主要功能就是設定/取消MyClock的暫停標記,從而起到暫停/繼續執行MyClock執行緒的作用。destroyXlet()方法設定MyClock的停止標記,從而終止執行緒執行。
actionPerformed()方法把用戶的動作轉換成對管理器的狀態變換請求。點擊時鐘的“暫停”按鈕暫停時鐘運行,同時它還通過調用XletContext.notifyPaused()向管理器傳送要求進入暫停狀態的請求。點擊“繼續”按鈕時Xlet傳送一個要求返回活動狀態的請求,如果管理器接受了請求,它就會調用Xlet的startXlet()方法,時鐘繼續運行。對於點擊“停止”按鈕的事件,處理方式也相似。
我們可以用Sun的Personal Profile參考實現提供的管理器XletRunner來測試ClockXlet。首先要從(由於百度不能提供網址連結,故去掉http!去sun java 下載!百度真垃圾,偷別人的東西,還不允許給他人打個標籤)products/ personalprofile/download.html下載管理器。Sun在下載頁面中聲明該版本的運行環境(當前唯一可免費下載的版本)只能用於Linux 2.2或更高的機器上,其他版本屬於商業軟體。但經過測試,我發現它在XP下也運行得很好,如圖2所示。
以Windows XP環境為例,從Sun網站下載得到的是一個ZIP檔案,解開壓縮,不必理會它的運行需求說明,只要將j2me-pp1.0\lib裡面的內容全部複製到Java源檔案所在目錄。用javac -classpath personal.jar ClockXlet.java命令編譯。完成後,用java -cp personal.jar com.sun.xlet.XletRunner -name ClockXlet -path c:/test/ClockXlet命令就可以啟動ClockXlet。
總結:Xlet和Applet一樣,只能在管理器中運行。在Xlet編程中,狀態轉換是極其重要概念,每一個Xlet都必須實現接口規範定義的四個生命周期方法。Xlet本身不能直接調用生命周期方法,但可以通過XletContext請求變換自身的狀態。