标题：xlet
xlet概述
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生命周期
每一个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
实现Xlet接口
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尚未做好改变状态的准备。
通过XletContext转换状态
如前所述，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请求变换自身的状态。