当前位置:文档之家› j2me_inanut-03

j2me_inanut-03

55MIDP 与

MIDlet 第三章

MIDP 与MIDlet

仅有有限资源的设备无法支持完备的虚拟机和完整版的J2SE 核心包,而CLDC 则为在这些设备上运行Java 提供了相应的基础。不过,如果你是一名应用开发人员,那么在编写软件时就不可能只与CLDC 提供的API 打交道,因为其中不提供任何可与用户、存储设备或网络进行交互的功能。CLDC 将作为一个基础层,在其之上可以提供一系列简表,这些简表则提供了CLDC 中没有的功能,而且对应各简表所面向的各类设备,分别采用了与之适合的形式。MIDP (Mobile Information Device Profile ,移动信息设备简表)就属于此类简表,它所面向的是带有有限用户界面的小容量设备,其界面是一个带有某种输入功能的小屏幕。这一章将介绍MIDP ;在后面两章,我们将对它如何支持用户界面、联网以及信息的持久存储做更详细的说明。MIDP 概述

MIDP 是基于CLDC 和KVM 的一个Java 平台版本,它面向小容量设备(主要是蜂窝电话和双向寻呼机)。它还适合于在PDA 上运行,对于3.5版本(或更高版本)的PalmOS 也有一个可用的实现(从长远来看,这些设备将使用PDA 简表,此类简表也基于CLDC )。MIDP 规范由Java Community Process 开发,并可由https://www.doczj.com/doc/9c2734878.html,/jsr/detail/37.jsp 下载得到。

对于一个实现MIDP 的设备,在其软件体系结构中MIDP 的逻辑位置如图3-1所示。实现MIDP 的软件运行在由CLDC 提供的KVM 中,并为使用MIDP API 编写的应用代码提供额外的服务。MIDP 应用叫做MIDlet 。如图3-1所示,MIDlet

可以直接使

第三章56

用MIDP功能以及第二章所述的API,这些API是MIDP由CLDC本身所继承的。MIDlet并不访问主机平台的底层操作系统,而且除非不再具有移植性,否则它也根本无法访问。由于KVM并不支持JNI,所以MIDP应用直接访问本地平台功能的惟一办法就是将本地代码链接到一个定制版本的虚拟机中。

图3-1:MIDP

Sun公司提供了一个可用于Windows的MIDP参考实现以及无线工具包,此工具包

中包括有面向Windows、Solaris和Linux 的MIDP版本,另外对于基于PalmOS的PDA,还提供了一个单独的MIDP产品。设备生产商通常将Sun公司的参考实现作为自己产品的基础。他们往往会把附加的代码作为其MIDP实现的一部分集成进来,从而为在设备间不能移植的MIDlet(因此也不属于参考软件)提供安装、删除和管理等管理功能。如图3-1所示,此代码(标以“OEM代码”)可能会综合使用一些MIDP和CLDC服务,因此也会依赖于主机平台的操作系统。核心MIDP软件的某些部分本身是与设备相关的,因此也需要由生产商提供。这通常包括部分联网支持、用户界面组件以及提供持久存储的代码。

MIDP硬件需求

如前所述,MIDP面向仅有有限内存、CPU和显示功能的小设备。以下几节将描述最低的硬件需求。

内存

从以后几章可以看到,MIDP包括了许多不属于核心Java平台的软件,因此所要求的内存比最小CLDC环境所要求的要多。MIDP规范要求至少有128KB RAM可用来存储MIDP规范本身,更多内存则是CLDC所需要的。除此以外,还必须有至少

MIDP 与MIDlet 57MIDP 与

MIDlet 32KB 用于Java 堆。在实际应用中,32KB 的堆存在很大限制,而且要求开发人员在分配对象时极为小心,还需要采取措施以避免在不必要时仍持有对象引用,从而使垃圾回收器可以尽可能快地回收堆空间。除了RAM 需求外,MIDP 设备还必须有至少8KB 的非易失性内存用作持久存储,采用这种存储方式,可以使MIDlet 所保存的信息在设备关闭的情况下不会丢失。但是不能保证更换电池时所存储的内容能被保留下来,而且通常希望设备还能提供某种方法(诸如PDA 的“热同步”机制)来将其内容备份到一个更稳定的位置上。

显示屏

MIDP 设备的一个显著特征就是它只有很小的显示屏。规范要求屏幕应当至少有96像素宽54像素高,而且每个像素(大致)均为方格。屏幕必须至少支持两种颜色,而且许多蜂窝电话的能力也仅仅如此。作为最高上限,PDA 的屏幕通常每个方向均有160像素,并支持多达65536种不同的颜色。功能上的大相径庭一为那些想要编写完全可移植的MIDlet 的开发人员,带来了一些有意思的挑战,而且还需要MIDP 用户界面库的一些折衷,可参见第四章和第五章。

输入设备

与显示屏一样,在一个MIDP 平台上也可能会有多种不同类型的输入设备。诸如RIM 无线手持设备等复杂一些的设备拥有一个包括文字和数字的完备键盘,如图3-2左图所示。类似地,基于PalmOS 的手持设备允许用户采用一种称为Graffiti 的速记形式在屏幕的一个特定区域“书写”;对于仍倾向于传统方式的用户,它们还提供了一个模拟的屏上键盘。图3-2右图显示了一个Palm 手持设备的Graffiti

区域。图3-2:手持输入设备

与这些功能强大的键盘(或键盘替代产品)相比,大多数蜂窝电话上所看到的是更为基本的键盘,图3-3显示了一个此类例子。这种键盘提供了相当容易的数字输入功能,

但是用户要键入字母字符时就需要稍费些劲了,而且几乎都没有可用的特殊字符。

第三章58

MIDP规范假设设备至少有一个相当于键盘的配置,从而使用户可以键入0到9,另外还有类似于图3-3顶部“钻石”形状所示的箭头键和选择按钮,这里的选择按钮即箭头中间的白色圆圈。这些需求蜂窝电话都能满足,其他设备可能也可以满足。例如,对于Palm,可以编程建立一些按钮来作为方向箭头,而选择操作则可以通过用笔点击屏幕来完成。在第五章我们将看到,输入设备的缩减要在处理用户界面的API 中有所反映,而且对于MIDlet所在的设备,要处理来自于该设备键盘的事件,就需要开发人员非常小心。

图3-3:典型的蜂窝电话键盘

互连性

移动信息设备拥有某种网络访问设施,这可以是蜂窝电话或寻呼机中内置的无线连接,也可以是连到PDA的一个单独的MODEM。MIDP没有假定设备要长期与网络保持连接,也没有假设网络直接支持TCP/IP。不过,它确实要求设备厂商至少提供一点,即设备要支持HTTP 1.1,要么直接利用一个Internet协议栈实现(连接到MODEM的Palm手持设备即采用这种方法),要么通过WAP网关与Internet建立一个无线连接“桥”。这样就允许开发人员编写与网络相关的MIDlet,而且它们在所有得到支持的平台上都能很好地工作。

MIDP软件需求

Sun公司的MIDP参考版本并非商业产品。通过实现有关代码从而在Sun公司的参考代码与厂商的硬件和操作系统软件之间搭建“桥梁”,设备厂商可以将此参考实现移

MIDP 与MIDlet 59MIDP 与

MIDlet 植到自己的硬件或软件中。与前面所述的硬件一样,对于参考实现所基于的软件,其功能存在如下假设(如图3-1中“主机平台操作系统”所示):

●操作系统必须提供一个保护的执行环境,在其中可运行JVM 。由于CLDC 支持J2SE 的线程功能,所以主机平台很可能支持多线程,而且如果确实如此的话,KVM 就可以直接使用它。不过,即使本地操作系统中多线程不可用,MIDP 实现仍要求提供多线程特性。一方面是属于应用代码的Java 线程,另一方面是VM 、MIDP 和核心库内所用的线程,通过在它们之间共享一个可用的线程即可做到提供多线程特性。

●需要某种形式的联网支持。在某些平台上(如PalmOS )可使用一个套接字级的API ,基于此可以实现强制性的MIDP HTTP 支持。对于不提供此方便接口的设备(包括那些无法直接连至一个基于IP 的网络的设备),厂商需要为HTTP 提供一种方法从而在设备自己的网络和Internet 之间搭“桥”。MIDP 的联网方面内容将在第六章中详细讨论。

●软件必须对系统键盘或小键盘(keypad )(或类似配置)以及定点设备提供访问(条件是这些设备可用)。若发生按键或释放键的操作,以及定点设备移动或启动时,软件都必须能够分发相应事件(例如,对于一个带有点击笔的手持设备,当笔触及屏幕、离开屏幕以及在屏幕上移动时,软件就必须分发一个事件)。无论由用户按键所发布的是何代码,厂商都需要将它映射为一组标准值,这样相同的按键就会在不同硬件平台上产生同样的结果。有关问题将在第五章做更深入的讨论。

●必须能够访问设备的屏幕。MIDP 允许MIDlet 将屏幕看成是像素的一个矩形矩阵,每个像素可以单独地被设置为设备所支持的某种颜色。因此,要求软件提供对屏幕的访问,就如同它是一个位图图像设备。MIDP 用户界面和图像将在第四章和第五章中详细介绍。

●平台必须提供某种形式的持久存储,这样在设备关闭时(也就是说,在其最小电源模式下,但不一定是完全不加电)也不会丢失其状态。MIDP 对此存储提供了记录级的访问,因此要求主机软件为其持久存储机制提供某种编程接口。MIDP 存储API 将在第六章中描述。

MIDP Java 平台

MIDlet 可用的Java 平台由第二章所述的CLDC 以及一组MIDP 专用的包所提供,这些包以javax.microedition 包层次的形式加以组织。核心库本身不受MIDP

规范的

第三章60

影响;惟一的变化是增加了java.util包中的J2SE 1.3定时器功能,这将在后面的“Timer与TimerTask”一节中介绍。MIDP规范对核心库有如下需求:

●与applet类似,MIDlet所在的执行环境与Java应用的执行环境仅稍有不同。可

以很快看到,MIDlet的初始入口点不是其MIDlet类的main()方法,而且不允许MIDlet中止Java VM。为了实施这一限制,System和Runtime类中的exit()方法都必须在其被调用时抛出一个SecurityException异常。

●除了CLDC所定义的系统属性,MIDP设备必须设置microedition.locale属

性来反映设备所在的区域。构成区域名的方式与J2SE所用的方式稍有不同,因为语言和国家部分是由一个连字号而非下划线字符分隔的。对此属性,一个常用的值即为MIDP设备上的en-US,而J2SE开发人员所用的则是en_US。不过,由于MIDP和CLDC都不提供对本地化的支持,所以此属性在格式上的这种微小差别对于MIDlet没有直接影响。实际上,它在安装来自外部的MIDlet时会用到,从而可以选择适用于设备拥有者所在区域的MIDlet版本。因此属性必须由提供软件的代理(可能是运行于某Web服务器的一个servlet)正确地加以表示。

●系统属性microedition.profiles必须至少包括值MIDP-1.0。将来,随着新版

本MIDP规范的发布和实现,支持多个简表的设备就可以在此简表中全部列出,并用空格来分隔其名称。

总之,相对于CLDC的需求,MIDP所引入或修改的系统属性值如表3-1所示。

表3-1:MIDP系统属性

属性含义值microedition.locale设备的当前区域例如en-US microedition.profiles所支持简表的列表(用空格分隔各简表)MIDP-1.0

MIDlet与MIDlet套件

运行在MIDP设备上的Java应用叫做MIDlet。一个MIDlet包括至少一个Java类,此类必须派生自MIDP定义的抽象类javax.microedition.midlet.MIDlet。MIDlet 运行于Java VM中的一个执行环境,此环境提供了一个定义完备的生命期,生命期由每个MIDlet必须实现的MIDlet类中的方法加以控制。MIDlet还必须使用MIDlet 类中的方法,从而由其环境得到服务,另外如果想让MIDlet成为设备可移植的,那么它必须只能使用MIDP规范中所定义的API。

MIDP 与MIDlet 61MIDP 与

MIDlet 可以将一组相关的MIDlet 收集到一个MIDlet 套件(suite )中。一个套件中的所有MIDlet 将作为一个实体打包并安装到设备上,而且它们只能作为一个组进行卸载和删除。一个套件中的MIDlet 可以共享其所在环境的静态和运行时资源,如下所列:●在运行时,如果设备支持多个MIDlet 的并发运行,那么一个MIDlet 套件中的所有活动MIDlet 都在同一个Java VM 中运行。因此,同一套件中的所有MIDlet 将共享所有Java 类的同一实例以及加载到Java VM 中的其他资源。这意味着数据可以在MIDlet 间共享,而且通常的Java 同步原语可以用于防止并发访问,这不仅包括单一MIDlet 中的访问,还包括来自于同一套件的MIDlet 的并发执行。●在MIDlet 套件级管理设备的持久存储。MIDlet 可以访问其自己的持久数据以及同一套件中的其他MIDlet 。不过,一个MIDlet 不可能访问其他套件中的持久存储,这是因为用于标识数据的命名机制中隐式地包括了MIDlet 套件。其部分原因是为了避免由彼此无关的来源得到的MIDlet 产生不必要的名字冲突,另外这也是一个安全措施,用于防止MIDlet 的数据被不可信来源所导入的恶意代码读取或破坏。

作为在MIDlet 间共享类和数据的一个例子,假设一个MIDlet 套件包括一个名为Counter 的类,它要对任何时刻该套件中运行的MIDlet 实例进行计数。public class Counter {

private static int instances;

public static synchronized void increment() {

instances++;

}

public static synchronized void decrement() {

instances--;

}

public static int getInstances() {

return instances;

}

}

无论相应套件中有多少MIDlet 在Java VM 中运行,此类都仅有一个实例加载到该VM 中。这说明所有这些MIDlet 都共用同一个静态变量instances ,而且increment 和decrement 方法会影响同一个计数器。这些方法是同步的,这就防止了instances 变量被各MIDlet

中的任何线程同时访问。

第三章62

MIDlet安全

对于开发人员,处理MIDlet安全是一个很简单的问题,这是因为根本就没有什么安全问题!J2SE所用的Java安全模型既强大又灵活,不过从内存资源方面来看代价过高,而且它需要一定程度的管理,这就超出了一个移动设备用户的知识范围。因此,无论是CLDC还是MIDP都没有包括J2SE中可用的API调用的安全检查,只有Runtime和System exit()方法例外,它们不能由MIDlet使用。

这意味着,比起applet之于浏览器用户,对于移动设备的拥有者来说, MIDlet更是潜在的威胁,这是因为对于Java applet,浏览器通过SecurityManager提供了一个“沙箱”,而MIDlet却没有受到这种“沙箱”的限制。在安装MIDlet时,移动设备的拥有者务必要小心,建议只接受来自可信源的软件。不幸的是,在写这本书时,还没有办法使用户完全确信谁确实提供一个MIDlet,或者MIDlet代码在传送给设备的途中未被破坏;为J2SE平台提供此功能的鉴别机制(即公钥加密和证书)不是MIDP 规范的标准部分。安全版的HTTP协议(HTTPS)有助于缓解这一问题,在将来的MIDP版本中将考虑加入。与此同时,仅提供有限的安全性以防备恶意MIDlet。没有MIDlet API可以对已经在设备上的信息(如地址和电话号码簿或日历)进行访

问,而且MIDlet不可能直接控制设备。在第六章中我们将看到,MIDlet可以在设备上保存信息,但是其存储是该MIDlet及其套件所专有的,因此该MIDlet只能破坏自己的数据。

MIDlet打包

在将MIDlet发送到设备上进行安装前,需要对其适当地打包。要将以下内容建立在同一个JAR文件中,这包括作为MIDlet主要入口点的MIDlet子类、它所需要的任何其他类(除了MIDP本身所提供的以外)以及它在运行时需要访问的任何图像或其他文件。向设备通知JAR文件内容的打包信息必须在JAR的清单文件中提供。类似的打包信息还在另一个称为Java应用描述文件(Java application descriptor,简写为JAD文件)的文件中提供,该文件独立于JAR存在。一个JAR可以包括不只一个MIDlet,在这种情况下,认为所有MIDlet都处在同一个MIDlet套件中。换种说法,也就是在同一MIDlet套件中的所有MIDlet都必须打包到同一个JAR中。

无论清单文件还是JAD文件都是简单的文本文件,每行的形式为:

属性名:属性值

MIDP 与MIDlet 63MIDP 与

MIDlet 名和值之间由一个冒号和一个可选的空格分开。与MIDlet 安装相关的所有属性名都冠以前缀“MIDlet-”。表3-2中列出了所有这些属性,并对其相关值做了简要描述。JAR 和JAD 列中的值指出了各属性在对应该列的文件中是强制性的(M )、可选的(O )还是可忽略的(I )。

表3-2:MIDlet 打包属性属性名JAR JAD 值和含义MIDlet-Name M M 打包在JAR 文件中的MIDlet 套件名。此名可以显示给用户

MIDlet-Version M M 打包在JAR 文件中的MIDlet 套件版

本号。版本号的形式为a.b.c (例如

1.2.3)

,各字段中值越大表示版本越

新,左边的字段优先级较高。例如,

版本1.2.5

比版本1.2.3更新,同理,

版本

2.1.5比1.

3.7更新

MIDlet-Vendor M M MIDlet 套件提供者名。此文本可以

采用任意格式显示给用户

MIDlet-n M I MIDlet 套件中描述MIDlet 的属性。

值n 要由一个从1开始的数字值替

换,以标识各个MIDlet 。与此属性

相关的值的格式将在正文中介绍

MicroEdition-Profile M I 此套件中MIDlet 所用MIDP 规范的

版本。如果出现多个版本,则必须以

空格分隔。所指定的版本将与目标

设备microedition.profiles 属性

中所列的版本相比较,从而确定

MIDlet 是否与之兼容。MIDP-1.0是

此属性的一个典型值

MicroEdition-Configuration M I 此套件中MIDlet 所需的J2ME 配置。

此值要与目标设备的microedition.

configuration 属性相比较以确定

兼容性

MIDlet-Description O O 要显示给用户的MIDlet 套件描述

第三章64

表3-2:MIDlet打包属性(续)

属性名JAR JAD值和含义

MIDlet-Icon O O在安装期间或安装后可用于表示

MIDlet套件的一个图标。此图标必

须是一个PNG(Portable Network

Graphic,可移植网络图形)文件MIDlet-Info-URL O O一个文件的URL,该文件中包含有

描述MIDlet套件的更多信息。此文

件的内容可以显示给用户,用户可

以据此决定是否安装此MIDlet套件MIDlet-Data-Size O O M I D l e t套件所需的最小持久存储

量。这是指用于对MIDlet套件所用

数据进行长期存储的空间,而不是

安装和管理MIDlet套件本身所需的

空间。此大小以字节为单位。如果未

提供此属性,则假定MIDlet套件并

不需要持久存储。MIDlet是否可以

使用多于其原来请求的持久存储空

间要依赖于具体设备

MIDlet-Jar-URL I M这是JAR文件的URL,该文件中包

括有由这些属性所描述的MIDlet或

MIDlet套件。此属性仅用于应用描

述文件

MIDlet-Jar-Size I M MIDlet JAR文件的大小(以字节为

单位)。此属性只用于应用描述文件MIDlet-Install-Notify I O对于从一个远程服务器完成的

MIDlet安装,此URL用于报告安装

的成功与否。此属性不包括在当前

的MIDP规范中,但却得到了无线工

具包的支持。详细内容见后面的“发

送与安装MIDlet”一节

MIDP 与MIDlet 65MIDP 与

MIDlet 表3-2:MIDlet 打包属性(续)属性名JAR JAD 值和含义MIDlet-Delete-Confirm I O MIDlet 由其所安装的设备上删除之

前,将向用户显示此消息。与MID-

let-Install-Notify 类似,此属性目前并未包括在正式的规范中

MIDlet 专有的属性O O 通过包括可在运行时访问的属性,

MIDlet

开发人员可以为MIDlet 提供

有限的配置能力

可以看到,许多属性都必须既在清单文件(驻留于JAR 中)又在JAD

文件(不驻留于JAR 中)中提供。为说明其原因,有必要先理解为什么要用到这两个文件。清单文件的工作是向设备指出

JAR 中MIDlet 套件的名字和版本,并指定对应于各个MIDlet 的类文件。不过,为了充分利用此信息,设备必须下载JAR 并提取出清单。完成这一工作后,就可以显示与MIDlet-Name 、MIDlet-Version 和MIDlet-Vendor 属性相关的值,此外还可显示与可选的MIDlet-Description 和MIDlet-Icon 属性相关的值。这些属性允许用户确定是否需要安装此MIDlet 。不过,对应于MIDlet 套件的JAR 可能非常大,而移动设备访问的网络通常都相当慢,在此网络上进行检索可能需要花费相当的长时间。如果其内容中惟一有用的描述都在JAR 本身中,那么大量的时间都会浪费在传输大文件上,而这些文件一经收到即会作为无意义的内容被拒绝。

为了解决这个问题,清单文件中的部分属性以及一些额外信息将被复制在JAD 文件中。在此并不下载整个JAR ,MIDP 设备将首先取其JAD 文件,这比起JAR 来说要小得多,因此可以很快地传输。设备再向用户显示此JAD 文件的内容,这样他就能够确定是否需要获取JAR 文件。JAD 文件中包括有一些来自于清单文件的属性,另外还有一些未出现在清单中的内容。常见属性如下:

MIDlet-Name

MIDlet-Vendor

MIDlet-Version

MIDlet-Description

MIDlet-Icon

MIDlet-Info-URL

MIDlet-Data-Size

第三章66

这些属性(可能除了最后一个)都可以提供给用户,以帮助确定相应JAR文件的内容是否值得下载。前三个属性在JAR和JAD文件中都是强制性的,而且MIDP规范要求它们的值必须相同。其余的属性都是可选的。如果既在清单中出现又在JAD文件中出现,那么JAD文件中的值优先级更高(在此阶段,设备只能看到JAD文件中的值)。

JAD文件还包括另外两个属性,它们不出现在清单文件中:

MIDlet-Jar-Size

MIDlet-Jar-URL

MIDlet-Jar-Size属性可以显示给用户,从而帮助他确定需要花费多长时间才能得到JAR文件;另外还使用户可以推测设备是否有足够的空闲空间来安装JAR。假设用户决定安装此MIDlet套件,下一步就是获取JAR本身,该文件可使用MIDlet-Jar-URL属性的值找到。

假设一个名为“Wireless Java Inc.”的公司创建了一个MIDlet套件,称为Wireless-Trader,它允许用户从一个MIDP设备上完成在线股票交易。此套件包括两个MIDlet,一个用于交易,另一个用于简单地浏览股票价格。这两个MIDlet的主类分

别为com.wireless.TradeMIDlet和com.wireless.BrowseMIDlet,它们都用到了com.wireless.Utils类中的通用代码。此套件的清单如下所示:

MIDlet-Name: WirelessTrader

MIDlet-Vendor: Wireless Java Inc.

MIDlet-Version: 1.0.1

MIDlet-Description: A set of MIDlets for online trading.

MIDlet-Icon: /com/wireless/icons/wireless.png

MIDlet-Info-URL: https://www.doczj.com/doc/9c2734878.html,/trader/info.html

MIDlet-Data-Size: 512

MicroEdition-Profile: MIDP-1.0

MicroEdition-Configuration: CLDC

MIDlet-1: StockTrader,/com/wireless/icons/trader.png,com.wireless.

TradeMIDlet

MIDlet-2: StockBrowser,/com/wireless/icons/browser.png,com.

wirelessBrowseMIDlet

在JAR中,此文件表示为META-INF/MANIFEST.mf。JAR文件中还需包括以下文件:/com/wireless/BrowseMIDlet.class

/com/wireless/TradeMIDlet.class

/com/wireless/Utils.class

/com/wireless/icons/browser.png

MIDP 与MIDlet 67MIDP 与

MIDlet /com/wireless/icons/trader.png

/com/wireless/icons/wireless.png

对于清单文件中的属性以及JAR 的内容,需要注意以下几点:

●JAR 包括两个MIDlet 类文件以及对应于com.wireless.Utils 的类文件,后者包括两个MIDlet 都要用到的代码。不过此文件并不需要由清单文件引用。JAR 还包括三个由清单文件引用的图标。

●相对于JAR 文件本身,存在一个对应于MIDlet 套件的图标文件,MIDlet-Icon 属性即包括此文件的绝对路径。

●各MIDlet 都有一个对其描述的属性;此属性名的形式为MIDlet-n ,其中n 是一个整数。此属性的值形式如下:

name,icon,class

name 为MIDlet 套件中的MIDlet 名。向用户显示MIDlet 套件的内容时,还会连同MIDlet 名一起提供设备可能使用的图标,这里的icon 即为此图标的绝对路径。class 是MIDlet 的主类名。图标是可选的;如果不需要图标,则应忽略:MIDlet-1:StockBrowser,,com.wireless.BrowseMIDlet

注意,即使指定了一个图标,设备也不是必须要显示它。对于由可选的MIDlet-Icon 属性所定义的MIDlet 套件图标,这一点同样适用。

对于此套件的JAD 文件可如下构造:

MIDlet-Name: WirelessTrader

MIDlet-Vendor: Wireless Java Inc.

MIDlet-Version: 1.0.1

MIDlet-Description: A set of MIDlets for online trading.

MIDlet-Info-URL: https://www.doczj.com/doc/9c2734878.html,/trader/info.html

MIDlet-Data-Size: 512

MIDlet-Jar-Size: 10312

MIDlet-Jar-URL: https://www.doczj.com/doc/9c2734878.html,/trader/Midlets.jar

此文件包括有设备显示给用户的信息,另外还有MIDlet 套件JAR 的URL 。在这里,清单和JAR 中的通用属性值均相同,但是不能通过在JAD 文件中指定不同值来覆盖MIDlet-Description 、MIDlet-Icon 、MIDlet-Info-URL 和MIDlet-Data-Size 属性。

为了做到充分的可移植性,JAD 文件应该用ISO-8859-1来编码,因为所有MIDP 实现都需要支持这种字符编码。使用其他编码的成功与否取决于目标设备(该设备可能不支持相应的编码),另外还在于将JAD 文件传输给设备的方式。例如,

如果文件使

第三章68

用HTTP获取,则Content-Type头可以用于指定编码,请参见后面的“MIDlet的发送与安装”一节的说明。有些情况下,将ISO-8859-1编码中不可用的(或者是不易从标准键盘访问的)Unicode字符包括在JAD文件中是很有用的。MIDP参考实现允许用\uxxxx的形式使用Unicode转义序列(escape sequence),从而克服编码的限制。例如,下一行中包括有MIDlet套件描述中的版权字符(Unicode值00A9):MIDlet-Description: A set of MIDlets for online trading. \u00A9 Wireless Java Inc.

尽管此功能在MIDP参考实现中是可用的,但在MIDP规范中却未提及,因此对于实际设备是否提供支持是不能保证的。

在运行时,MIDlet可以通过使用https://www.doczj.com/doc/9c2734878.html,ng.Class的getResourceAsStream()方法来访问取自其JAR的文件。除了类文件,对JAR中的任何文件都可以采用这种方法访问。对于应当显示在用户界面上的图像或文本文件,通常就是采用这种方法。在第四章中将介绍一个这样的例子。MIDlet还可以在清单文件和JAD中定义自己的私有属性,并在运行时访问这些属性,请参见本章后面的“开发MIDlet”一节。

MIDlet执行环境与生命期

所有MIDlet都派生自抽象基类javax.microedition.midlet.MIDlet,其中包括MIDP平台为控制MIDlet生命期所调用的方法,还有MIDlet本身用来请求改变其状态的方法。MIDlet必须有一个公共的默认构造函数(也就是说,不需要参数的构造函数),如果需要完成一些初始化工作,或者如果没有显式的构造函数,那么开发人员就会提供这样一个构造函数,Java编译器会插入一个空的默认构造函数。MIDlet 类的“基干”可如下所示:

public class MyMIDlet extends MIDlet {

//可选的构造函数

MyMIDlet() {

}

protected void startApp() throws MIDletStateChangedException {

}

protected void pauseApp() {

}

protected void destroyApp(boolean unconditional)

throws MIDletStateChangedException {

}

}

MIDP 与MIDlet 69MIDP 与

MIDlet 在任一给定时刻,MIDlet 可能处于以下三种状态之一:暂停、活动和销毁。这些状态之间的关系以及合法的状态变迁如图3-4的状态图所示。

图3-4:MIDlet 的生命期

加载MIDlet 时,最初是处于暂停状态。然后完成一般的类和实例的初始化,也就是说,第一次加载MIDlet 类时会调用静态的初始化程序,在MIDlet 实例创建时,所有实例的初始化程序都将被调用,然后再调用其公共的无参数构造函数。如果MIDlet 在执行其构造函数时抛出一个异常,则MIDlet 被销毁。如果MIDlet 并未抛出异常,则安排它在以后某个时间执行。其状态将由暂停改为活动,而且会调用其start-App()方法。MIDlet 类像下面这样声明此方法:

Protected void startApp() throws MidletStateChangeException;

此方法是一个抽象方法,这意味着必须在你自己的MIDlet 中加以实现,而且它是保护型的,这说明它要么由MIDlet 类本身调用,要么由javax.microedition.midlet 包中的其他类调用。在参考实现中,MIDlet 生命期方法要由包Scheduler 中的某个类调用,但是MIDP 规范中并不要求使用这个类。只要支持这一节所述的MIDlet 生命期,得到许可的人就可以提供其自己的调度实现。M I D l e t 开发人员通常会将startApp()方法重定义为公共的,这当然是一种安全的做法,但并非完全必要,因为即使声明这些方法为保护型的,厂商所提供的实现仍须继续工作。startApp()方法可以正常结束,在这种情况下,可能允许MIDlet 运行,也可能会通知MIDP 平台MIDlet 不希望此时运行。为实现后者,有如下几种方法:●如果startApp()方法检测到一个使之终止执行的错误条件,但是它以后可能不再出现(即一个临时错误条件),

则需要抛出一个MIDletStateChangeException 异常。这将把MIDlet

移回至暂停状态,从而在以后可以再来启动。

第三章70

●如果startApp()方法检测到一个错误条件,而且不可能恢复(即非临时错误条

件),它应当调用其notifyDestroyed()方法,后面将对此加以描述。

●最后,MIDlet可能会抛出MIDletStateChangeException以外的某个异常,可

能是故意为之,也可能是由于它调用的某个方法抛出了此异常,而且startApp()方法未捕获到此异常。在这种情况下,则假定出现了一个严重错误,通过调用destroyApp()方法(后面将对此进行描述)来销毁此MIDlet。

如果MIDlet没有做以上工作,则处于活动状态,而且可以继续运行,直到被暂停或被销毁为止。完成其startApp()方法后,MIDlet即返回,而且它没有任何方法包含主逻辑来接受所传递的控制,那么MIDlet的代码放在哪里呢?通常情况下,MIDlet 有一个用户界面,并根据按钮或指针移动所产生的事件执行相应代码。MIDlet还可以启动单独的后台线程来运行不依赖于用户界面的代码,或者可以使用一个定时器周期性地进行工作调度(如后面所示)。如果采用这些方法,那么在MIDlet本身暂停或销毁时适当地管理后台线程和/或定时器是很重要的。

任何时刻,MIDP平台都可以将一个MIDlet置为暂停状态。例如,在一个蜂窝电话上,主机软件检测到一个来电并需要释放电话的显示屏,从而使用户可以接电话。

MIDlet暂停时,即调用其pauseApp()方法:

protected abstract void pauseApp();

与startApp()类似,MIDlet需要为此方法提供一个实现。对于这种状态变化的适当响应取决于MIDlet本身,但是,通常情况下,需要释放它所占有的所有资源,并保存当前状态,这样在以后再次激活它时即可自行恢复。

转移到暂停状态的主要后果是MIDlet不再访问屏幕;它所创建的任何线程都不会自动终止,而且定时器会保持活动。MIDlet可以选择终止任何打开的网络连接或后台线程,在要求暂停时还可取消活动的定时器,但是并不要求这样做。

如果主机平台决定恢复一个暂停的MIDlet,例如由于一个来电终止,MIDlet的startApp()方法就会再次得到调用以通知MIDlet它可以访问屏幕了。其结果是,MIDlet的startApp()方法需要仔细编写,从而在必要的情况下区别其首次调用和后来的调用,前者表明MIDlet是首次启动,而后者则说明是由暂停状态恢复,这样可以避免资源被分配多次。当然,如果一个MIDlet在移至暂停状态时释放了其所有资源,那么在恢复时,让它执行startApp()方法中的相同初始化代码来重新分配资源,这样做可能是合适的。不过,编写得很好的MIDlet还会在startApp()方法中采取特殊操作,从而使其用户界面及其内部状态恢复到它暂停之前,而不是重新显示初始屏幕。

MIDP 与MIDlet 71MIDP 与

MIDlet 在MIDlet 生命期中startApp()方法可以被调用不止一次,这导致了一个问题,即初始化工作应该在此完成还是在MIDlet 的构造函数中完成。开发人员可以自由选择更为方便的位置来分配资源,并准备M I D l e t 的状态。通常情况下,应当在startApp()方法中分配将在pauseApp()中释放的资源。其他资源则既可在startApp()方法中分配,也可在构造函数中分配,不过要注意确保startApp()方法中所做的分配不会在由暂停状态恢复时再次重复。

startApp()方法和构造函数之间存在着一个重要的区别,根据MIDP 规范可知,MIDlet 只能在首次调用startApp()方法的时候访问对应于屏幕的Display 对象(请参见第四章)。因此,在对规范的一个严格解释中,涉及到Display 对象的初始化工作不能在构造函数中完成。当然,实际的MIDP 实现可能不会采用这一限制,但是,如果MIDlet 在其构造函数中访问Display 对象,就会削弱可移植性。

MIDlet 可能会拒绝由暂停状态进行恢复,这可以通过在调用其startApp()方法时抛出一个MIDletStateChangeException 异常来做到,如前所述。

当主机平台需要终止一个MIDlet 时,它需要调用MIDlet 的destroyApp()方法:public abstract void destroyApp(Boolean unconditional) throws

MIDletStateChangeException;

在destroyApp()方法中,MIDlet 需要释放它所分配的所有资源,终止所有后台线程,并停止任何活动的定时器。当MIDlet 以这种方式终止时,unconditional 参数的值则为true ,这表示MIDlet 不能防止进程继续执行。不过,在有些情况下,让MIDlet 有机会选择不终止会很有用,因为它可能还有需要保存的数据。可以用参数false 调用destroyApp()方法,在这种情况下,MIDlet 可以通过抛出一个MIDlet-StateChangeException 异常指出它希望继续执行。以下代码说明了这一技术如何用于实现一个MIDlet 的有条件关闭:

try {

//调用destroyApp 以释放资源

destroyApp(false);

//安排销毁MIDlet

notifyDestroyed();

} catch (MIDletStateChangeException ex) {

//MIDlet 并不希望关闭

}

此代码可以用于对MIDlet 的用户界面中一个退出按钮做出响应。开始时直接调用MIDlet 自己的destrroyApp()方法来释放资源。如果MIDlet

要终止时并未处于适

第三章72

当的状态,则调用destroyApp()方法时参数为false,MIDlet应该抛出一个MIDletStateChangeException异常。调用代码要捕获此异常,并且如上所示什么也不做。另一方面,如果MIDlet准备终止,则应正常地完成destroyApp()方法,这种情况下调用代码会使用MIDlet的notifyDestroyed()方法来通知MIDP平台该MIDlet希望终止。

此例还说明了notifyDestroyed()方法的使用,它由MIDlet使用以自愿终止。理解destroyApp()和notifyDestroyed()方法之间的关系以及何时使用它们是很重要的:

●当MIDlet由平台销毁时,很有可能是因为用户做了此请求,这样MIDlet的

destroyApp()方法随即得到调用,且参数为true,此方法完成后MIDlet即被销毁。在这种情况下,MIDlet没有必要调用其notifyDestroyed()方法。

●若MIDlet自己想终止,则一般是因为它没有更有用的工作去做,或者是用户按

下了退出按钮。这可以通过调用其notifyDestroyed()方法实现,如此可通知平台该MIDlet要被销毁。在这种情况下,平台并不调用MIDlet的destroyApp()方法;它假定该MIDlet已经准备好要终止。在调用notifyDestroyed()之前,

大多数MIDlet会调用自己的destroyApp()方法来完成通常的整理工作,如上所示。

注意MIDlet若要自愿地终止,调用notifyDestroyed()方法是惟一的方法。MIDlet 不能通过调用System或Runtime的exit()方法来终止,这是因为它们均抛出一个SecurityException异常。

MIDlet还可调用另外两个方法来影响其生命期:

public final void notifyPaused();

public final void resumeRequest();

notifyPaused()方法通知平台MIDlet希望转至暂停状态;其效果就好像是平台调用了MIDlet的pauseApp()方法。当MIDlet调用notifyPaused()时,平台并没有调用它自己的pauseApp()方法,这与其在对notifyDestroyed()做出响应时并未调用destroyApp()是类似的,因为它假设MIDlet已经准备好要暂停。因此,MIDlet 通常在调用notifyPaused()前会先调用pauseApp(),这样在挂起MIDlet之前可以完成适当的工作步骤。

resumeRequest()方法的作用正好与notifyPaused()相反;它通知平台:一个处于暂停状态的MIDlet希望返回到活动状态。将来某个时候,平台可以通过调用其

MIDP 与MIDlet 73MIDP 与

MIDlet startApp()方法来重新启动此MIDlet 。resumeRequest()方法通常由一个后台线程调用,或者由一个定时器调用,此定时器在MIDlet 暂停时仍处于活动状态,下一节将对此举一个例子。

开发MIDlet

为了说明MIDlet 的生命期以及可以如何对其进行控制,我们将创建一个非常简单的MIDlet ,它要完成以下工作:

●在调用其构造函数时打印一个消息。

●创建一个不断激活的定时器,如果它为活动状态,则置MIDlet 为暂停状态,如果它本来是暂停状态,则返回到活动状态。若定时器经过两次这样的循环,则终止MIDlet 。

●启动时创建一个后台线程,其作用只是每秒打印一条消息。此线程只能在MIDlet 活动时运行。

由于你还不知道如何创建用户界面,因此这一示例MIDlet 将通过向标准输出流写消息来实现通信。在一个实际的设备上,是无法看到写到标准输出或标准错误上的内容的(除非使用了设备厂商提供的调试功能),但是大多数设备仿真器都提供了一种监视这些流的内容的方法。有许多产品可用来建立和测试MIDlet ,从而既可以在模拟环境下,也可以在实际设备上完成;第九章将描述这样一些产品。在此,我们将使用无线工具包,这是Sun 公司免费提供的。

用无线工具包建立MIDlet

无线工具包提供了一个MIDP 实现,另外还提供了一个仿真器,可以将它定制为外形和功能类似于真正的蜂窝电话。它还可以与一个第三方仿真器联合使用,从而使你了解到你的MIDlet 在基于PalmOS 的手持设备上功能如何。不过,这并不是一个完备的开发环境,因为它不提供集成的编辑器来创建、查看和修改源代码。相应地,如果希望将作为完整开发周期的一部分,就需要一个文本编辑器或IDE 来管理源代码。在写这本书的时候,无线工具包可以安装为与Forte for Java 和Borlandc JBuilder 集成,前者可由Sun 公司的Web 网站下载得到,不过使用任何IDE 都可以。

使用无线工具包的第一步是创建一个工程,它会对源代码、类和MIDlet 套件的相应资源加以管理。为实现这一步,启动KToolbar ,并按下New Project

按钮以打开新

第三章74

工程对话框,如图3-5所示。对于这个例子,MIDlet主类的名字为ora.ch3.Example-MIDlet,而工程名可以任意选定。

图3-5:用无线工具包创建一个新工程

按下此对话框中的Create Project按钮时,无线工具包会打开另一个窗口,如图3-6所示;其中包括一组选项卡,在此可以提供有关属性以生成对应MIDlet JAR和JAD 文件的清单。可以通过点击需要调整的单元并键入新值来编辑这些属性。Required 选项卡上的字段包括如表3-2中所示的标为强制性的属性。大多数默认值无需修改即可使用。例如,MIDlet-Name字段(实际上是将要用于MIDlet套件的名字,而非对应于某个MIDlet)就与工程名相一致,而将要创建的JAR的名字也要取自于工程名。在此选项卡上惟一可能修改的字段是M I D l e t-V e n d o r,其初始默认值为S u n

Microsystems。

图3-6:为MIDlet套件设置必要的属性

为了定义需要加入到MIDlet套件中的MIDlet,选择MIDlets选项卡。初始情况下,其中只包含一行,而且其内容由工程名构造。在此例中,这个套件只包括ora.ch3包中一个称为ExampleMIDlet的MIDlet,因此应该按下Edit按钮并编辑此选项卡上MIDlet-1属性的值,使之如下所示:

Key Name Icon Class

MIDlet-1ExampleMIDlet/ora/ch3/icon.png ora.ch3.ExampleMIDlet

相关主题
文本预览
相关文档 最新文档