第二章 技术背景
2.1 J2ME
2.1.1概念
J2ME(Java 2 Micro Edition)作为Java 2的一个组成部分,它与J2SE、J2EE并称。根据Sun的定义:J2ME是一种高度优化的Java运行环境,主要针对消费类电子设备的,例如蜂窝电话和可视电话、数字机顶盒、汽车导航系统等等。J2ME技术在1999年的JavaOne Developer Conference大会上正式推出,它将Java语言的与平台无关的特性移植到小型电子设备上,允许移动无线设备之间共享应用程序。
J2ME 在设计其规格的时候,遵循着“对于各种不同的装置而造出一个单一的开发系统是没有意义的事”这个基本原则。于是 J2ME 先将所有的嵌入式装置大体上区分为两种:一种是运算功能有限、电力供应也有限的嵌入式装置(比方说PDA 、手机);另外一种则是运算能力相对较佳、并请在电力供应上相对比较充足的嵌入式装置 (比方说冷气机、电冰箱、电视机顶盒 (set-top box))。因为这两种型态的嵌入式装置,所以Java 引入了一个叫做Configuration 的概念,然后把上述运算功能有限、电力有限的嵌入式装置定义在Connected Limited Device Configuration,即受限连接设备配置,简称CLDC规格之中;而另外一种装置则规范为 Connected Device Configuration,即连接设备配置,简称CDC规格。也就是说, J2ME 先把所有的嵌入式装置利用Configuration 的概念区隔成两种抽象的型态。
2.1.2架构
J2ME与J2SE和J2EE相比,J2ME总体的运行环境和目标更加多样化,但其中每一种产品的用途却更为单一,而且资源限制也更加严格。为了在达到标准化和兼容性的同时尽量满足不同方面的需求,J2ME的架构分为Configuration(配置)、Profile(简表)和Optional Packages(可选包)。它们的组合取舍形成了具体的运行环境。
Configuration主要是对设备纵向的分类,分类依据包括存储和处理能力,其中定义了虚拟机特性和基本的类库。已经标准化的配置有CLDC和CDC。
Profile建立在Configuration基础之上,一起构成了完整的运行环境。它对设备横向分类,针对特定领域细分市场,内容主要包括特定用途的类库和API。CLDC上已经标准化的Profile有Mobile Information Device Profile ( MIDP)和Information Module Profile(IMP),而CDC上标准化的Profile有Foundation Profile(FP)、Personal Basis Profile(PBP)和Personal Profile(PP)。
可选包独立于前面两者提供附加的、模块化的和更为多样化的功能。目前标准化的可选包包括数据库访问、多媒体、蓝牙等等。
下面的图表描述了不同的虚拟机、配置和简表之间的关系。它同时把 J2SE API 和它的 Java 虚拟机进行了比较。虽然 J2SE 虚拟机通常被称为一种 JVM,但是 J2ME 虚拟机KVM 和 CVM 都是 JVM 的子集。KVM 和 CVM 均可被看作是一种 Java 虚拟机,不过它们是J2SE JVM的压缩版,并特定于J2ME。
图2 不同虚拟机关系表
2.1.3 MIDlet及其运行机制
在前面一节中我们介绍了CLDC/MIDP的软件体系结构,这里给出的MIDlet的概念——在MID设备上运行的Java程序被称为MIDlet,这种命名的方法如同我们熟悉的Applet。下面将深入讨论一些MIDlet所涉及的内容。
MIDlet
与通常的Java程序相比MIDlet有比较大的不同,从某种意义上来说MIDlet更类似与Applet,简而言之与J2SE程序相比MIDlet没有main()这个程序初始入口点,同时MIDlet也不能调用Runtime.exit() 和System.exit()方法来中止虚拟机的运行。如果调用的话将会抛出SecurityException异常 。MIDlet是在MID设备上运行的Java程序每一个MIDlet必须派生自抽象类。 javax.microedition.midlet.MIDlet的继承体系如下图所示:
图3 MIDlet的继承体系
下面给出一个具体的MIDlet示例
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
public class MyMIDlet extends MIDlet{
public MyMIDlet{
}
protected void startApp() throws MIDletStateChangeException{
}
protected void pauseApp(){
}
protected void destroyApp(boolean arg0){
}
throws MIDletStateChangeException{
}
}
MIDlet的lifecycle
下面将分析MyMIDlet的结构来介绍其的运行的Lifecycle。
对源程序进行观察可以发现MIDlet程序的运行是由startApp(),pauseApp()和destroyApp()这3个方法控制的,它们都在javax.microedition.midlet.MIDlet中定义,所有的MIDlet都必须实现这3个方法。
startApp()方法用于标志一个MIDlet的开始执行。 与MyMIDlet程序的构造函数不同。startApp()不光是在初始化完一个MIDlet时执行。只要该MIDLet被从Paused态激活(变为Active态),startApp()就会被调用。
pauseApp()方法标志着MIDlet进入了 Pause状态。而destroyApp()方法则标志着MIDlet进入destroyed态。
MIDlet的执行实际是通过AMS即Application Management software来进行管理的。AMS是位于操作系统级别上用来管理MIDlet运行的底层机制的总称,所谓MIDlet state(MIDlet状态)就是它一手操办控制管理的。MIDlet state确保了AMS随时可以消灭该MIDlet。同时MIDlet请求进入Pause态,当需要时再次激活。
MIDlet state分为Paused、Active、Destroyed三种状态。当AMS创生一个新的MIDlet实体时,对应于MIDlet,表现为其constructor被调用,进入Paused状态。当所有的准备工作都做好以后,AMS判断现在MIDlet可以运行了,于是调MIDlet.startApp()方法进入Active态。当AMS决定要把MIDlet转入Paused状态时,就会调用MIDlet.pauseApp()方法,MIDlet就会暂停执行,通常Paused态会用于释放所占资源。当AMS判断MIDlet不再需要,就会调用MIDlet.destroyApp()方法,此时MIDlet被消灭。
上述说明了AMS如何控制MIDlet的状态改变。程序员也可请求MIDlet的状态的变换通过调用resumeRequest, notifyPaused, notifyDestroyed这三个方法 。
我们的MyMIDlet程序先把destroyApp() 的unconditional值置为false,抛出一个MIDletStateChangeException 异常,表示MIDlet这时还不想被destroy。而notityDestroyed()通知AMS:MIDlet进入destroyed态。具体的细节请参阅MIDP API文档。MIDlet的状态的改变可以用下图表示。
图4 MIDlet的状态转换图
2.2 蓝牙
2.2.1概述
蓝牙技术(Bluetooth)是一种使用电波的、近距离的无线数据通信技术,它是为有线电缆的无线化而开发的。它的传输速度为1Mbps,传输距离为10米左右。利用“蓝牙“技术能够有效地简化掌上电脑、笔记本电脑和移动电话手机等移动通信终端设备之间的通信,也能够成功地简化以上这些设备与internet之间的通信,从而使这些现代通信设备与因特网之间的数据传输变得更加迅速高效,并为无线通信拓宽道路。因此,蓝牙技术使得现代一些轻易携带的移动通信设备和电脑设备不必借助电缆就能联网,并且能够实现无线上因特网,其实际应用范围还可以拓展到各种家电产品、消费电子产品和汽车等信息家电,从而组成一个巨大的无线通信网络。蓝牙系统由以下功能单元组成:无线单元、链路控制单元、链路管理单元、相关软件。蓝牙工作在全球通用的2.4Mb/s(即工业、科学、医学)频段,其数据传输速率为1Mb/s,该技术采用时分双工传输方案来实现全双工传输。
蓝牙规范接口可以直接集成到电脑上或者通过PC 卡或者 USB 接口进行连接。可以通过蓝牙蜂窝电话连接远端网络;利用蓝牙蜂窝电话做扬声器;在蓝牙笔记本电脑、手持机和移动电话间进行商用卡交易;在蓝牙笔记本电脑、手持机和移动电话间进行时间同步等等。蓝牙是一个独立的技术规范,不依赖于任何其他的技术规范。
蓝牙技术是一种新技术,它的问世无疑给移动通信的进一步发展带来新的动力。与此同时,也会开创一些新的、目前还不知道的应用。显然,为了更具生命力,它必须在解决自身问题的过程中不断进步。但应该相信在不久的将来,几乎所有家庭和办公设备都将是无线连接的,随之而来的将是生活和工作的日益简单和高效。
2.2.2 J2ME蓝牙
2.2.2.1 J2ME 蓝牙协议栈
J2ME 蓝牙协议栈如下图所示。
图5 J2ME蓝牙协议栈图
Bluetooth Radio 、LC、LMP和Host Controller Interface Firmware组成了协议栈的底层硬件部分。HCI、L2CAP、SDP、RFCOMM、OBEX组成了协议栈的上层软件部分。协议栈各部分组成的说明如下所示。
l Bluetooth Radio:蓝牙射频,用于调制和解调收发的蓝牙信号。
l LC:基带连接控制器,控制物理连接、射频跳动及封装包。
l LMP:连接管理器,控制和配置与其它设备的连接。
l Host Controller Interface Firmware:主控制器的硬件部分。
l HCI:主机控制器接口(Host Controller Interface)。这一层顾名思义就是主机(计算机)和控制器(蓝牙设备)之间的接口。可以看到,其他所有的层都要经过 HCI。
l L2CAP:即逻辑链接控制器适配层协议(Logical Link Controller Adaptation Protocol)。为上层协议提供面向连接和无连接的数据服务,并提供多协议功能和分割重组操作,允许传输和接收最大长度为64KB的L2CAP数据包。
l SDP是服务发现协议(Service Discovery Protocol)层,用于在远程蓝牙设备上寻找服务。服务器维护一张服务记录列表,每个服务记录都包含服务器上一个服务的信息,每个服务对应一个服务记录。客户端访问服务器时,首先要获得服务器的访问记录,然后通过服务记录建立连接。
l RFCOMM:基于L2CAP协议的虚拟串口协议(virtual serial port protocol),因为它允许蓝牙设备模拟串口的功能。
l OBEX:为协议栈最高层,可以用来传输文件或者交换对象数据,OBEX协议是基于RFCOMM实现的。
在本设计中采用RFCOMM协议来实现手机与计算机进行通信,这主要是因为RFCOMM可以进行流式数据通信。
2.2.2.2 J2ME 蓝牙开发接口
JSR82规范是专门针对蓝牙开发设计的应用程序编程接口,其中包含如下两个包。
l Javax.bluetooth:核心API。
l Javax.obex:对象交换API。
Javax.bluetooth包中的类和接口如下表所示:
类和接口 |
功能 |
DiscoveryListener接口 |
定义了设备发现通知和服务发现通知的回调 |
L2CAPConnection接口 |
定义了L2CAP连接客户端功能 |
L2CAPConnectionNotifier接口 |
定义了L2CAP连接服务器功能 |
ServiceRecord接口 |
定义了服务记录,包含了服务信息 |
DataElement类 |
定义了服务属性可能用到的数据类型 |
DeviceClass类 |
表示蓝牙规范中定义的设备类 |
DiscoveryAgent类 |
服务代理类,同时支持设备与服务的发现 |
LocalDevice类 |
定义了本地蓝牙设备 |
RemoteDevice类 |
定义了远端蓝牙设备 |
UUID类 |
在蓝牙中,每个服务和服务属性都有一个全球唯一的标识(UUID) |
BluetoothConnectionException异常 |
当蓝牙连接不能被成功创建时,会触发该异常 |
BluetoothStateException异常 |
当蓝牙系统接收到当前状态想不能处理的请求时,会触发该异常 |
ServiceRegistrationException异常 |
当向本地服务发现数据库中添加服务记录失败时,触发该异常 |
表1 Javax.bluetooth包中的类和接口表
2.2.3 WINDOWS蓝牙
2.2.3.1 windows 蓝牙协议栈
windows 蓝牙协议栈如下图所示:
图6 windows 蓝牙协议栈图
在本设计中只用到windows蓝牙协议栈中的两个部分,对于其它部分这里不作介绍,这两部分分别是:
l WshBth : Windows蓝牙套接字帮助组成部分。 WshBth被Windows套接字层调用,来执行套接字操作。 WshBth主要通过TDI(输驱动程序接口)接口使用RfComm协议。 WshBth(一种服务,负责调查缓存和转发数据)还调用到BthServ进行远程设备查询,BthPort(一种被USB蓝牙加载的微型驱动)进行本地无线查询。
l RfComm:实现了蓝牙串行电缆仿真协议的组件。 RfComm也使用L2CAP和SDP接口来发现BthPort 。RfComm上层展示了TDI接口,使这一部分近似为网络传输。这是WshBth从用户模式的API连接到蓝牙发送和接收数据的过程。用户模式的应用程序可以使用Windows SDK中所描述的WinSock接口访问RfComm。
2.2.3.2 windows 蓝牙设计的一般过程
创建服务端套接字
1. 提供Winsock的版本和实现细节的数据来初始化caller application。可以通过调用WSAStartup函数来获得这个数据。
WSADATA wsd;
WSAStartup (MAKEWORD(2,2), &wsd);
2. 调用socket函数来创建一个蓝牙套接字。
SOCKET server_socket=socket (AF_BT, SOCK_STREAM, BTHPROTO_RFCOMM);
socket函数的参数值将套接字设置为蓝牙服务。
3. 通过设置SOCKADDR_BTH结构体来储存服务器设备的信息。SOCKADDR_BTH sa;
memset (&sa, 0, sizeof(sa));
sa.addressFamily = AF_BT;
sa.port = channel & 0xff;
注意 为了避免冲突,在选择服务器通道时建议将channel设置为0,这样RFCOMM将自动使用下一个有效的通道。结构体中的信息用来将套接字绑定到服务器设备的本地地址上。
4. 调用bind函数绑定第二步中创建的server_socket,传入第三步中创建的sa的引用指定设备信息。
if (bind (server_socket, (SOCKADDR *)&sa, sizeof(sa)))
{
//Perform error handling
closesocket (server_socket);
return 0;
}
5. 用listen函数来监听客户端蓝牙设备发送的连接请求。
if (listen (server_socket,5))
{
//Perform error handling
closesocket (server_socket);
return 0;
}
6. 用accept函数来接受传入的连接请求。
SOCKADDR_BTH sa2;
int size = sizeof(sa2);
SOCKET s2 = accept (server_socket, (SOCKADDR *)&sa2,&size);
调用accept将返回SCOKADDR_BTH类型的客户端地址。
7. 调用closecocket函数来关闭套接字。
closesocket(server_socket);
要结束对Winsock服务的使用,调用WSACleanup函数。在程序中对每个成功调用的WSAStartup都必须对应地调用WSACleanup。
创建客户端套接字
1. 提供Winsock的版本和实现细节的数据来初始化caller application。可以通过调用WSAStartup函数来获得这个数据。
WSADATA wsd;
WSAStartup (MAKEWORD(2,2), &wsd);
2. 调用socket函数来创建一个蓝牙套接字。
SOCKET client_socket = socket (AF_BT, SOCK_STREAM,
BTHPROTO_RFCOMM);
socket函数的参数值将套接字设置为蓝牙服务。
3. 通过设置SOCKADDR_BTH结构体来储存客户端要连接到的远程设备的信息。
a. 创建并初始化SOCKADDR_BTH变量。
SOCKADDR_BTH sa;
memset (&sa, 0, sizeof(sa));
b. 将btAddr成员赋值为包含目标设备地址的BT_ADDR变量。
sa.btAddr = b; //b is a BT_ADDR variable
应用程序可以接受字符串类型的设备地址,但必须将其转换并储存为一个BT_ADDR类型变量。
c. 如果服务标识符有效,则将serviceClassId成员设置为基于RFCOMM的服务的GUID。这种情况下,客户端执行SDP查询然后使用得到的服务器通道。或者如果要使用硬编码的通道编号,将port成员变量设置为服务器通道编号。
sa.port = channel & 0xff;
4. 调用connect函数来连接到蓝牙套接字。
if (connect (client_socket, (SOCKADDR *)&sa, sizeof(sa)))
{
//Perform error handling.
closesocket (client_socket);
return 0;
}
传递第3步中设置好的SOCKADDR_BTH来指定目标设备的属性。连接建立后,你可以通过发送和接收数据来和目标设备通信。
5. 要关闭与目标设备的连接调用closesocket函数关闭蓝牙套接字,并且确保使用CloseHandle函数释放套接字。
closesocket(client_socket);
CloseHandle ((LPVOID)client_socket);
6. 要结束对Winsock服务的使用,调用WSACleanup函数。在程序中对每个成功调用的WSAStartup都必须对应地调用WSACleanup。