摘 要
有些时候,对于一个企业级移动应用程序,从服务器将信息推向移动设备,并且自动激活一个已安装的移动应用程序使其进行必要的处理是非常重要的。由短信服务(SMS)作为推信息的协议,使用push注册机制可以让使用Mobile Information Device Profile 2.0的JAVA微小版本应用程序拥有这一特征。
版权声明:任何获得Matrix授权的网站,转载时请务必保留以下作者信息和链接
作者:Srijeeb Roy ;magic003(作者的blog:http://blog.matrix.org.cn/page/magic003)
原文:http://www.matrix.org.cn/resource/article/44/44449_Push+JAVA+Mobile.html
关键字:Push;JAVA;mobile
移动技术日渐流行。Java微小版本,或者叫Java ME(Sun的J2ME平台的新名字),是最流行的开发移动应用程序的技术之一。使用Java ME,我们可以在使用JVM或KVM的手持设备上运行多种无线应用程序。
Connected Limited Device Configuration (CLDC)被包含在Java ME中,它是面向那些只拥有有限资源,使用KVM的设备。同样,Mobile Information Device Profile(MIDP)也被包含在Java ME中,这是一个为了在手机上运行应用程序的基于CLDC的profile。运行在移动设备中的应用程序模块叫做MIDlet,是一个MIDP应用程序。一个MIDlet主要是一组有序运行的类,并且它们由运行在移动设备中的应用程序管理软件(AMS)所控制。
MIDP的最新版本(2.0)引入了很多新的特征来帮助开发者建立健壮的企业级应用程序。其中一个比较重要的特征就是push注册机制。在Java ME应用程序中,我们有时候需要从服务器推数据,并在设备上自动启动一个移动应用程序,而不需要由用户明确的启动设备。设想一种情形,当一个针对他/她的名字的工作条款被建立时,用户必须能自动得到通知,并且必须尽快地对此工作条款做出反应。Java ME的push注册机制能够很容易地将信息推向一个Java ME应用程序,并自动启动该程序。在这篇文章中,我将向你展示如何将push注册机制特征添加到你的移动应用程序中。
Push注册机制的行为可以被描述为如下三个步骤:
1.MIDlet在移动设备中注册一个连同协议名称的端口,如果任何信息到达指定的端口,并且使用相同的协议,那么AMS就将它转交给MIDlet。注册使用Java ME应用程序描述符(JAD)文件静态的完成。程序也能使用应用程序内置的API执行动态注册。
2. 从服务器,信息被发送到特定的移动设备,使用MIDlet应用程序注册监听的协议和端口。
3. 在信息被传递到移动设备后,AMS调用注册了监听此端口和协议的MIDlet应用程序。一旦信息被转交到MIDlet,那么处理信息就是此应用程序的责任了。典型的,根据信息的信息的内容,一个应用程序会选择打开一个屏幕,并允许用户与服务器进行一些事务。
在这篇文章的例子中,为了从服务器推信息,我们将使用一个GSM(移动通信全球系统)调制解调器。图1较高层次地描述了我们将在此文中实现的场景。
图1. 从服务器push SMS消息到移动设备的高层场景
在jad文件中,每一个push注册条目都包含如下信息:MIDlet-Push-<n>: <ConnectionURL>, <MIDletClassName>, <AllowedSender>。
MIDlet-Push-<n>:push注册属性名称。MIDlet套件中可以包含多条push注册。<n>的数值从1开始,并且对于附加的条目必须使用连续的序数。第一个发现的缺失条目将中止列表。任何剩余的条目都会被忽略。
ConnectionURL:被Connector.open()使用的连接字符串。
MIDletClassName:负责连接的MIDlet。指定的MIDlet必须使用MIDlet-<n>记录在描述文件或jar文件的manifest中登记过。
AllowedSender:一个指定的过滤器, 它将限制哪些发送者能够能正当启动请求的MIDlet。
MIDP 2.0 规范定义了数据报和socket带内连接的语法。当其他规范为其他连接类型定义push 语义时,它们必须既定义过滤器域期望的语法,又定义连接URL字符串的期望格式。
在jad文件中,一个push注册的典型例子,使用socket连接,类似于如下:
MIDlet-Push-1: socket://:77, com.sample.SampleApplication, *.
这个示例描述符条目在77端口处保存一个流套接字,并且允许所有的发送者。
从服务器推信息到移动设别会带来一些问题:如果我们想发送信息到一个在指定端口注册了监听流套接字的特定设备,我们必须知道那个移动电话的无线网络IP。因为在无线网络中,很多手机不使用始终连接环境(有时候,提供商不支持设备中网络中的静态IP),发送信息到设备是有问题的。如果我们不知道设备的无线IP,我们将不能使用套接字连接从服务器发送信息到设备。
短信服务(SMS)在这种情况下派上了用场。使用SMS,我们指定目标设备的电话号码;因此在这种情况下,我们不需要知道设备的IP地址。但是,使用SMS作为触发器同样会带来一些问题:因为MIDP2.0规范只定义了针对数据报和套接字带内连接的语法,而没有针对SMS连接的,所以不保证所有支持MIDP2.0的设备都能使用SMS作为触发器来进行push注册。但无线消息API(WMA1.1)-一个在MIDP上能支持SMS的的可选包-现在得到很多移动设备的支持,所以有更大的可能性,SMS作为push注册机制的触发器将得到很多设备的支持。对于这篇文章,我使用Nokia 6600移动电话,它是支持SMS作为push注册机制的触发器的。
另外,从服务器发送一条SMS消息到设备不是简单直接的,因为有很多途径存在。SMS服务提供商提供API(或者暴露服务URL),通过这些API你能从你的服务器端应用程序发送消息到你指定的移动电话上。但这种方法依赖于SMS服务提供商和它特殊的计划。可选的方式是使用一个GSM调制解调器,这样你需要使GSM调制解调器与你的服务器端应用程序进行交互。在这篇文章中,我将使用一个开源的产品,SMSLib for Java V1.0(原名jSMSEngine),它能使GSM调制解调器与你的Java服务器端程序进行交互。
另一个在此需要注意的要点是一条简单的SMS消息将不会激活MIDlet。我们必须发送SMS消息到MIDlet注册监听的特定的端口。因此被用来发送SMS消息的软件(或SMS服务提供商)必须能够将它发送到设备指定的端口。SMSLib for Java v1.0 支持这一功能。
当我们使用GSM调制解调器方案,我们必须了解GSM调制解调器将在内部使用SIM(订户识别模块)卡来发送SMS消息。SIM卡依赖于某个移动服务提供商。因此每条SMS短信将带来与从常规GSM移动电话发送消息同样的花费。正相反,对于一个企业级应用(依赖于服务计划),通过提供商的SMS网关发送批量SMS消息会被证实更节省开销。但是,如果应用程序不需要发送大量SMS消息来出发MIDlet,那么GSM调制解调器方案会是有效的开销,并能从移动服务提供商那里取消特殊的批量SMS服务依赖。
虽然我建议为了近期的产品使用购买一个单独的GSM调制解调器,但是测试此行为不要求够买。通常地,很多GSM移动电话模型带有一个内置的GSM调制解调器。那些移动模型中的任何一个都能够作为GSM调制解调器,来代替单独的调制解调器。在这篇文章中,我使用另外一个Nokia 6600移动电话,而不是一个单独的GSM调制解调器,因为Nokia 6600有一个内置的GSM调制解调器。
现在,让我们开发一个实例程序,是我们能够从一个Java服务器端应用程序发送一条SMS消息到一个移动电话的指定端口,并自动启动移动设备中的一个MIDlet。
使用push注册特征开发客户端MIDlet
为了开发客户端,我们使用Sun Java Wireless Toolkit(原名为J2ME Wireless Toolkit)。我使用版本2.2。这个产品是免费的,可以从Sun的网站下载。为了安装和运行此工具包,你必须在你的机器上装有J2SE 1.4.2_02或更新的版本。
我使用Windows 2000 Professional 操作系统。
安装Sun的工具包后,按照如下描述的步骤:
1.从开始菜单打开KToolBar:选择程序,然后J2ME Wireless Toolkit 2.2,然后KToolbar。将会打开一个应用程序窗口,如图2所以。
图2. 打开KToolbar
2.现在,在刚才打开的窗口中点击新建工程图标。会打开一个弹出窗口;在那里你可以指定工程名称和MIDlet类名。在工程名称中输入MySamplePushRegistryProject,在MIDlet类名中输入com.sample.MySamplePushRegistry。
图3. 新建一个工程
3.在步骤2后,会自动出现另一个弹出窗口,它将允许你设定项目的其他设置。确保你在API 选择标签中。在此标签中,从目标平台下拉菜单中选择JTWI(如果还没有选择)。同样确保CLDC 1.0单选按钮被选中。不要钩选Mobile Media API多选框(因为不会使用任何跟多媒体有关的API)。请参考图4。
图4. 设置API首选项
4.现在进入push注册标签。点击添加按钮。将会出现一个弹出窗口。在连接URL域中输入sms://:50001,在类域中输入com.sample.MySamplePushRegistry,在允许发送者域中输入*。请参考图5。
图5 设置push注册的属性
5.在步骤4后,一个条目将会被加入到父窗口中。如图6所示。
图6. 设置push注册的属性(续)
6.现在进入许可标签。单击添加按钮。从许可树中选择javax/microedtion/io/Connector/sms并单击OK。重复相同 的步骤来添加许可javax/wireless/messaging/sms/receive 和 javax/microedtion/io/PushRegistry。
7.在步骤6后,三项许可将被添加到应用程序中。如图7所示。
图7. 添加许可
8.现在进入用户定义标签。这里,我们添加用户定义的变量,这将包含SMS端口。从我们的程序,我们查阅此用户定义变量来读取SMS端口。在此标签中,单击添加按钮。打开一个弹出窗口。输入SMS-Port作为属性名称。选择OK。出现了最初的弹出的窗口。输入50001作为SMS-Port的值。如图8所示。
图8. 添加定制属性SMS-Port
9.现在,在设置窗口中单击OK。这个动作将带回到KToolbar。
10.在步骤9后,如果你观察由以上步骤产生的jad文件,C:/WTK22/apps/MySamplePushRegistryProject/bin/MySamplePushRegistryProject.jad(假设J2ME Wireless Toolkit 2.2被装在目录C:/WTK22中),你会找到在前面步骤中设定的完整配置。所有条目中最重要的一个如下:MIDlet-Push-1: sms://:50001, com.smaple.MySamplePushRegistry, *. 此条目确保你的应用程序监听50001端口上的SMS消息。
现在,让我们着眼于MIDlet应用程序的代码。这里,我仅仅提供MIDlet的一部分代码片断。 请看资源来下载在此应用程序中使用的所有代码。
public class MySamplePushRegistry extends MIDlet
implements CommandListener, Runnable, MessageListener {
//....
public void startApp() {
smsPort = getAppProperty("SMS-Port");
String smsConnection = "sms://:" + smsPort;
if (smsconn == null) {
try {
smsconn = (MessageConnection)
Connector.open(smsConnection);
smsconn.setMessageListener(this);
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
display.setCurrent(resumeScreen);
}
public void notifyIncomingMessage(MessageConnection conn) {
if (thread == null) {
thread = new Thread(this);
thread.start();
}
}
public void run() {
try {
msg = smsconn.receive();
if (msg != null) {
if (msg instanceof TextMessage) {
content.setString(((TextMessage)msg).getPayloadText());
}
display.setCurrent(content);
}
} catch (IOException e) {
e.printStackTrace();
}
}
//other methods to follow
}
在这个片断中,如下要点值得注意:类MySamplePushRegistry集成了MIDlet。这个类实现了CommandListener,Runnable和MessageListener接口。在MIDlet的startApp()方法中,我们使用getAppProperty()方法得到SMS-Port属性的值。我们也使用Generic Connection Framework(GCF)创建了一个连接来监听SMS消息。GCF的Connector类的open()方法创建了SMS连接。如果一个消息到达了,notifyIncomingMessage()方法会被调用,它会创建和启动一个线程(如果还没有被创建)。在线程的run()方法中,程序等待一条消息(smsconn.receive())。当消息被接受到,我们得到原始的消息并在Alert类中设定消息。在现实生活的应用程序中,用户通常打开一个屏幕来处理消息。
一旦你的代码准备好了,使用KToolbar中的创建图标来创建它。在编译和无错误预验证创建之后,你需要把应用程序打包成一个JAR。为了完成这个任务,选择工程菜单,然后包,然后创建包。
在把应用程序部署到真实设备之前,我们先测试程序是否在模拟器中运转正确。选择工程菜单,然后通过OTA运行。这个步骤实际上是模拟MIDlet的over-the-air安装。图9的顺序图描述了全部的过程。保持此模拟器窗口打开。
图9. 通过over the air方法在模拟器中安装应用程序
现在,在KToolbar中进入文件菜单,并选择Utilities。得到一个弹出窗口,如图10所示。
图10. 打开Utilities
单击WMA,然后打开控制台。打开了另一个弹出窗口:
图11. 打开WMA Utilities 控制台
在窗口中选择发送SMS按钮。窗口的内容将会改变,如图12所示。在选择客户端区域内选择+5550000作为电话号码。在端口文本框中,输入50001。在消息域中,输入Hi Test Message。现在单击发送按钮。
图12. 从Utilities发送SMS消息
如果在以上步骤中,每一件事都运行良好,那么模拟器窗口,如图13所示,将会显示一个消息到达了模拟移动电话,告诉用户消息到达到了,并等到批准。
图13. 模拟器MIDlet被SMS消息激活
如果你在模拟器上选择Yes,MIDlet会自动启动,”Hi Test Message”会显示在模拟器中,如图14所示。
图14. 模拟器MIDlet接受到SMS
接下来的步骤事在真实设备上安装MIDlet。你可以使用OTA安装应用程序。详细内容请查阅Sun的工具包文档。除了OTA,我们可以电缆/红外线/蓝牙技术来安装MIDlet,如果设备支持那些选项。作为一个客户端设备,我使用Nokia 6600,这是支持红外线技术的,并且因为我有一个红外线适配器,我使用红外线技术在Nokia 6600中安装MIDlet。
如果以上步骤运行良好,你的客户端应用程序就准备就绪了。现在是时候来开发服务器端应用程序了,实际上,它将发送SMS消息给监听50001端口的MIDlet。
开发服务器端应用程序来将SMS消息发送到特定的设备端口
如前所述,为了开发服务器端与GSM调制解调器交互的的代码,我使用开源的SMSLib for Java, 它使用关注指令(AT指令)与GSM调制解调器进行交互。它同时也使用Java通信API或RxTx与使用的操作系统通信,并与外部设备(GSM调制解调器)交谈来发送AT指令。
为了发送消息到指定的端口,用户数据和协议数据单元(PDU)的用户数据标题指示(UDHI)域都必须被修改。SMSLib在内部完成了它,所以发送SMS消息引起的复杂度都被包装在SMSLib的代码内。如果你对观察消息是如何被正确发送的感兴趣,你可以独自仔细检查SMSLib的代码。
按照如下的步骤来完成服务器的编码和部署:
1.从SMSLib网站下载SMSLib代码。下载时,确保你下载的是SMSLib-Java-v1.0.1.zip。SMSLib for Java可以跟Java通信API或RxTx一起使用。最近,Sun撤消了了对Java通信API Windows版的支持,所以使用RxTx会更好。但如果你已经有了Java通信API,你同样可以用它和SMSLib一起运行。在这篇文章中,我将详述以上两种运行实例程序的方法。
2.以Java通信API 2.0作为开始,首先,确保你已经正确安装了API。解压javacomm20-win32.zip。在commapi子目录中,你将找到如下文件:
 javax.comm.properties
 win32com.dll
 comm..jar
把javax.comm.properties拷贝到你的Java运行时环境的lib目录中。把win32com.dll拷贝到你的JRE的bin目录中。当运行任何使用SMSLib的程序时,确保comm.jar在classpath中。
为了保证Java通信API被正确的安装了,从命令框中进入到commapi\samples\BlackBox目录中。按如下方式设置PATH变量:以我为例,JRE的家目录为C:\j2sdk1.4.2_03\jre。根据你的JRE设置做相应的改变。
设置PATH=.;c:\j2sdk1.4.2_03\jre\bin;。现在,使用如下命令来运行Java 黑盒程序:
java -classpath .;../../comm..jar;BlackBox.jar; BlackBox。
如果Java通信API被正确的安装了,那么如图15所示,会出现一个显示你机器的可用串口(COM端口)的Swing窗口。关闭窗口之后,命令框将会包含一些跟你可用的COM端口相关的行,如下面的示例命令提示输出所示。记住,依赖于你的PC上的可用端口,Swing窗口和命令行提示的内容可能会有变化。但重要的是,如果你能够看到GUI和在命令提示框中的如下行(就象下面所示),你可以假设Java通信API已经正确安装了。在这个测试之后,关闭Swing窗口来中止黑盒程序。
图15. 测试Java通信API的安装
实例命令提示框输出:
COM1: PORT_OWNED
COM2: PORT_OWNED
Closing port 1 (COM2)
Closing COM2
Closing port 0 (COM1)
Closing COM1
3.现在,是时候把GSM调制解调器连接到你的电脑上了。我使用一部Nokia 6600移动电话作为GSM调制解调器。Nokia 6600没有串口连接器(COM端口连接器)。但是,它提供了红外线技术连接到电脑,然后作为GSM调制解调器。如果你有一部有串口直接连接器的电话,那样使用起来会更简单。一般地, 实际的GSM调制解调器会提供串口连接器。但是如果你的设备缺少串口连接器,但包含一个内置的GSM调制解调器并能使用红外线或蓝牙技术连接,这种选择也能起作用。
为了在没有物理COM端口的情况下,在功能上实现COM端,你必须将一个虚拟的COM端口映射到你的红外线或蓝牙连接上。SMSLib需要一个兼容的GSM电话或GSM调制解调器。如果提供了调制解调器的能力,大多数GSM电话都能被使用。SMSLib使用串行连接(物理或模拟,比如蓝牙,红外线,USB等)来与GSM调制解调器通信。通过Nokia 6600, 你可以使用SMSLib来发送SMS消息,但是因为Nokia 6600把传入的消息储存在记忆卡里而不是SIM卡中,使用这种模式将不能接受到这些消息。同样,Nokia 6600不允许从记忆卡中使用AT指令来读取消息。但是,我们仅仅需要发送SMS消息来激活我们的MIDlet,Nokia 6600能够成功的完成这些(不需要接收SMS消息)。对于使用红外线或蓝牙技术连接的电话来说,最重要的是将红外线或蓝牙连接映射到一个虚拟COM端口。参考你的移动电话的文档,查明它是否支持虚拟COM端口映射。
以我为例,我在我的PC上安装了Nokia PC 套件。为了从我的PC连接Nokia 6600,我使用一个外部USB红外适配器。因此,为了这种情况,我也在PC上安装了红外线驱动。
现在,我用PC上的USB端口连接到外部红外适配器。在Nokia 6600设备上,我选择菜单,然后连接,然后调制解调器。通过红外线连接的选项在调制解调器下面。现在,我选择选项“连接”,并把移动电话放在红外适配器的有效范围之内。
4.下一步是验证从PC,我们能够使用AT指令访问GSM调制解调器。为此,选择开始菜单,然后程序,然后附件,然后通信,然后超级终端。会打开一个对话框,并请求一个逻辑名称。提供任何你愿意提供的名字。为了方便,我提供名字“GSM Modem”。选择OK。
图16. 打开超级终端
5.另一个弹出窗口出现。在使用连接的下拉菜单中,选择COM端口名称(虚拟的或实际的),GSM调制解调器将通过此端口连接到PC。以我为例,是COM4。
图17. 在超级终端中选择COM端口
6.在下一个对话框中(COM端口的属性),只需单击OK。
7.现在,你会被带到一个窗口,你将在此窗口中输入一些命令(以我们为例,我们将输入AT指令)。输入如下命令来测试连通性-但是记住,当你输入指令的时候,不会在控制台中写入任何东西:AT+CPMS=?。
要点:不要在超级终端中输入任何未知的命令。这可能会永久性损坏你的移动设备或擦去所有的数据。
如果一切顺利,你将能看到一些输出,如图18所示。输出可能会不尽相同,但是没有输出,输出错误,或终端没有响应都代表这连通性的错误。
图18. 在超级终端中执行AT指令
8.现在从呼叫菜单,使用断开连接命令从超级终端断开连接。
9.完成了如上的步骤后,我们可以准备写Java示例程序了。此程序将发送SMS消息到我们刚才配置的的GSM调制解调器的指定端口上:
import org.smslib.*;
public class SendMessageWithPortsSMSLib {
public static void main(String[] args) {
CService srv = new CService("COM4", 9600, "", "");
System.out.println("SendMessage(): sample application.");
System.out.println(" Using " + srv._name + " v" + srv._version);
try {
srv.setSimPin("0000");
srv.connect();
srv.setSmscNumber("");
COutgoingMessage msg =
new COutgoingMessage("+9198301...", "Message from smslib API.");
msg.setMessageEncoding(CMessage.MESSAGE_ENCODING_7BIT);
msg.setSourcePort(0);
msg.setDestinationPort(50001);
srv.sendMessage(msg);
srv.disconnect();
}
catch (Exception e) {
e.printStackTrace();
}
System.exit(0);
}
}