BlackBerry平台是一个Java平台,支持标准的Java ME,所以,对于Java ME开发人员来讲,进入BlackBerry开发世界的一个直接方法就是将现有的MIDlet移植到BlackBerry平台上。将MIDlet移植到BlackBerry上有不同的方法,从简单的程序转换到复杂的项目重写,可以适合不同的开发人员和不同的项目。本文将逐一介绍把MIDlet程序移杆到BlackBerry平台上的各种方法,让读者可以轻松进入BlackBerry开发世界。
注意,本文针对在Java ME开发方面有一定经验并且有需要将现有的MIDlet应用移植到BlackBerry平台上的开发人员。如果你只是希望开发一个新的BlackBerry应用,请参考其它BlackBerry入门文章。
使用rapc直接转换MIDlet
在BlackBerry手机上运行MIDlet的最简单方法是将jar文件直接安装到BlackBerry手机上,BlackBerry手机自身有能力将jar文件转换成cod,然后安装运行。具体操作方式如下:
1.将MIDlet对应用的jad文件和jar文件拷贝到BlackBerry手机的媒体卡上
2.打开BlackBerry手机上的文件浏览器
3.找到第一步拷贝的jad文件,并打开这个文件
4.此时系统会出现类似于以下界面的应用下载界面,选择“下载”即可开始安装
当然,有条件的开发人员可以将MIDlet对应用的jad文件和jar文件放在网上,BlackBerry用户可以通过BlackBerry浏览器打开jad文件开始OTA安装该应用。
然而,很多情况下开发人员需要将MIDlet的jar文件转换成cod文件,让用户可以使用更为正式的方法部署应用。将MIDlet应用转换成cod文件可以使用RIM提供的编译工具rapc。rapc应用是一个命令行编译工具,可以将java文件或者是jar文件转换成cod文件。其实BlackBerry开发环境最终也是调用rapc将开发人员编写的代码编译成cod文件。所以,要获得rapc这个应用,其中一个方法就是安装BlackBerry开发环境,不管是Blackberry JDE还是Eclipse Plug-in For BlackBerry都会带有这个程序。如何安装的是Elipse Plug-in For BlackBerry,你可以在安装目录的“\plugins\net.rim.ejde.componentpackxxxxxx\components\bin”子目录中找到rapc应用。如果读者没有安装BlackBerry开发环境,也可以尝试在网上直接查找rapc的下载地址,有一些第三方网站直接提供了rapc这个应用的下载。
如上所述,rapc应用是一个命令行工具,使用时需要配置相关参数。rapc应用的的参数列表如下:
◆limport=(用于指定RIMAPIs对应用包和其它应用需要使用的包)
◆lcodename=(用于指定编译出来的应用名,一般使用JAR文件相同的名字)
◆l-MIDlet(如果是MIDlet程序的话一定要使用这个参数,如果不是MIDlet,比如只是一个工具包,则不需要使用这个参数)
◆ljad=(指定JAD文件名)
◆l
◆l
结合以上参数描述,将一个MIDlet程序转换成cod文件的命令如下:
rapc–import=
其中
下面是一个rapc使用的实例:
- rapcimport="c:\ProgramFiles\ResearchinMotion\Blackberry JDE 4.5\lib\net_rim_api.jar"
- codename=virca-MIDlet jad=Virca.jad Virca.jar
该例中使用的是BlackBerryJDE开发环境中的net_rim_api.jar,转换的MIDlet应用名为virca。如果读者使用的Eclipse Plug-In for BlackBerry,则net_rim_api.jar文件在安装目录的以下目录中:“\plugins\net.rim.ejde.componentpackxxxxxx\components\bin”。
命令运行后所生成的cod文件可以通过另一个命令行工具javaloader直接加载到BlackBerry手机上。Javaloader工具同样可以在目录“\plugins\net.rim.ejde.componentpackxxxxxx\components\bin”中找到,加载的命令如下:Javaloader–uloadvirca.cod注意加载时需要将Blackberry手机通过USB线连接到PC上。
当然,对于很多最终用户来讲,javaloader并不是一个可选的方案,对于大多数最终用户而言都会通过BlackBerryDesktopManager加载应用。而通过BlackBerryDesktopManager加载应用则需要提供对应的alx文件。公网上也有一些非官方的alx文件生成器,可以根据cod文件生成对应用的alx文件。其实alx文件是一个xml描述文件,对于开发人员来讲完全可以自己编写一个。下面列出一个基本的alx文件样例,开发人员可以根据自己应用的相关信息修改该样例从而生成一个alx文件。
- <loader version=" 1 .0" >
- <app licationid=" MyVClient " >
- <name > </name >
- <description > </description>
- <version > </version>
#p#
左右软键的处理
很多Java ME MIDlet应用都使用左右软键响应用户操作,在屏幕左下方和右下方分别显示左软键和右软键的标签,用户根据左右软键的标签提示决定点击左软键还是右软键,由程序响应左右软键事件从而响应用户操作。
但是,如上一节所述,在BlackBerry手机上并没有左右软件,所以需要对这样的操作进行适配。首先要明确的是如果开发人员使用“Command Action”添加操作,这些操作会被自动添加到BlackBerry的菜单中,用户点击菜单键的时候可以看到这些“Command”。对于这种情况则不需要调整原来的代码。
如果开发人员自行绘制左右软键,使用键盘事件侦听左右软键的话,则需要对程序进行调整,因为在BlackBerry手机上没有左软键或者是右软键的事件发生。现在在公网上看到的一种简单的调整方法是将全键盘中的“Q”键映射为左软键,将“P”键映射为右软件。使用这种方法开发人员只需要修改键盘侦听代码,当侦听到“Q”键点击事件时执行左软键响应代码,当侦听到“P”键点击事件时执行右软键响应代码。这种方法虽然简单可行,但是有一定的限制,比如在一个可编辑的域被选中时不能工作,否则用户将无法输入Q字母和P字母。另外,这种方法也不符合BlackBerry用户的操作习惯。
一个较为合适的方法是将菜单键映射成左软键,将退出键映射为右软键。使用这种方法的做法是获取菜单键点击事件和退出键单点事件,在这两个事件的响应代码中执行左软键的
代码或者是右软键的代码。有一些开发人员尝试使用标准的Java ME方式获取BlackBerry上的菜单键事件和退出键事件,发现这两个键无法通过标准的Java ME方式获取。在这里菜单键事件和退出键事件的获取成为关键。
在BlackBerry手机上,菜单键事件和退出键事件需要通过KeyListener来实现。开发人员可以为一个应用添加一个KeyListener作为侦听器,当这个应用在运行同时有按键被点击时,该KeyListener侦听器的keyDown方法会被执行,开发人员可以实现这个keyDown方法,获取当前被点击的按键的键值,通过键值的比较判断是哪个键被按下,如果是菜单键或者是退出键被按下,则执行左软键或者是右软键的响应逻辑。
所开发的KeyListener需要实现接口net.rim.device.api.system.KeyListener,如以下代码所示:
- public class MyKeyListener implements
- net.rim.device.api.system.KeyListener
该接口有几个键盘相关的方法,其中最主要的是方法keyDown,对应按键事件,代码如下:
- public boolean keyDown( int arg0, int arg1) {
其中第一个参数是所按下的按键的键值,第二个值数是与上一次按键相隔的时间,不做重复按键处理的情况下,一般只需要使用第一个参数。使用键值的方法是通过调用Keypad的key方法将键值转换成标准值,然后与Keypad类中对应用静态成员进行比较,如菜单键的标准值为Keypad.KEY_MENU,退出键的标准值为Keypad.KEY_ESCAPE。其中类KeyPad在包net.rim.device.api.ui中,虽然是ui包中的类,但是我们只使用静态方法和成员,并不使用ui包中的类进行展现,不会和MIDlet的UI有冲突。
此外,方法keyDown有布尔类型的返回值,返回true的话表明事件已经被消费,系统不会再响应该事件,返回false的话表明事件没有被消费,系统会继续响尖该事件。如果开发人员希望点击菜单键响应左软键,同时系统不再处理该按键事件,则判断到菜单事件时需要返回true作为返回值。
具体请参考以下侦听器的样例代码:
- package cn.searb.keymenu;
- import net.rim.device.api.ui.Keypad;
- public class MyKeyListener implements
- net.rim.device.api.system.KeyListener {
- public boolean keyChar( char arg0, int arg1, int arg2) {
- // TODO Auto -generated method stub
- return false ;
- }
- public boolean keyDown( int arg0, int arg1) {
- if (Keypad. key (arg0) == Keypad. KEY_MENU ) {
- // 执行菜单响 应代码 , 即 左软键响 应代码
- return true ;
- } else if (Keypad.key (arg0) == Keypad. KEY_ESCAPE ) {
- // 执行退出键 响应代 码 , 即右软键 响应代 码
- return true ;
- }
- return false ;
- }
- public boolean keyRepeat( int arg0, int arg1) {
- // TODO Auto -generated method stub
- return false ;
- }
- public boolean keyStatus( int arg0, int arg1) {
- // TODO Auto -generated method stub
- return false ;
- }
- public boolean keyUp( int arg0, int arg1) {
- // TODO Auto -generated method stub
- return false ;
- }
- }
开发了侦听器类以后在系统启动时需要将侦听器添加到应用中,添加侦听器需要调用应用的addKeyListener方法。可以在MIDlet的构造函数中加入以下语句添加侦听器。
- App li ca t io n. g et App li ca t io n() .a dd K e yList ene r( n ew M yK e yList ener ( ) ) ;
其中方法Application.get Application()用于获取当前应用,add Key Listener则用于添加键盘侦听器。
添加以上代码后MIDlet就可以通过菜单键和退出键响应左右软键了。有一点需要注意的是BlackBerry用户习惯使用退出键完成“退出”,“取消”,“关闭”等否定性操作。所以在移植MIDlet的时候尽量在右软键位置放置“退出”,“取消”或者是“关闭”等操作,而不
要放置“进入”,“打开”,“开始”等肯定性的操作。
滚轮操作的映射
如第一小节描述的,在较早的BlackBerry机型上,用户通过滚轮进行导航,在这些机型上没有单独的菜单键,用户通过点击滚轮激活菜单。
在这种机型上,要响应滚轮点击事件需要实现额外的侦听器,因为滚轮不属于键盘的一部分,在KeyListener中无法侦听到。对于其它按键,如退出键,各个字母键,虽然键盘分布和新型的机型有所不同,但是都可以使用上一节描述的KeyListener进行侦听。
响应滚轮事件的侦听器为net.rim.device.api.system.TrackwheelListener,开发人员需要实现这个接口。该接口有滚轮滚动,滚轮点击等方法,要响应滚轮点击事件需要实现的方法为trackwheelClick。
一个滚轮侦听器的样例代码如下:
- import net.rim.device.api.system .TrackwheelListener ;
- public class MyTrackWheelListener implements TrackwheelListener {
- public boolean trackwheelClick( int status, int time) {
- // 执行滚轮点 击响应 代码
- return true ;
- }
- public boolean trackwheelRoll( int amount, int status, int time) {
- // TODO Auto -generated method stub
- return false ;
- }
- public boolean trackwheelUnclick( int status, int time) {
- // TODO Auto -generated method stub
- return false ;
- }
- }
发了滚轮侦听器后可以通过应用程序的addTrackwheelListener方法为应用添加滚轮侦听器,样例代码如下:
- Application.getApplication ().addTrackwheelListener ( new
- MyTrackWheelListener());
其中Application.getApplication()用于获取当前应用实例,addTrackwheelListener用于添加侦听器。
其它按键操作
在blackBerry手机上还有一些其它的按键,如静音键,音量键等,这些按键事件可以通过标准的Java ME方法,比如Canvas的keyPressed方法获取。
开发人员可以通过keyPressed方法对这些按键进行处理:
- protected void keyPressed( int keyCode)
参数keyCode为当时用户按下的按键,开发人员可以通过keyCode==判断用户按下的是哪个按键。在BlackBerry包中有类net.rim.device.api.system.Characters,包含了很多常量,其
中包括了这些键的键值,如Characters.CONTROL_VOLUME_UP为增加音量键的键值,Characters.CONTROL_VOLUME_DOWN为减少音量键的键值,开发人员可以使用这些常量进行判断,避免在代码中直接使用键值进行比较。不过测试发现的问题是这些常量和实际的健值不符,导致有些判断失效。所以,使用健值直接进行比较更加稳妥一些,比如增大音量键的键值为-150,减少音量键的键值为-151,左快捷键的键值为-21,右快捷键的键值为-19。
当然,一个稳妥和简单的方法是在开发过程中通过System.out.println()将键值打印到输出界面上,从而测试不同键的键值。下面是按键检测的代码片段:
- protected void keyPressed( int keyCode)
- {
- if (keyCode== -150)
- {
- System. out .println( "volume up " );
- }
- else if (keyCode== -151)
- {
- System. out .println( "volume down " );
- }
- }
使用以上方法也可以对一般的字母键进行处理。不过,对于字母键的处理,建议是尽量使用高级界面自带的功能,从而避免直接对键盘事件进行响应。比如多利用输入框编辑框等输入文字,可以更好地利用平台的功能,如自带的输入法等,不需要开发人员响应键盘事件。对于特定的低级界面必须响应键盘事件时则需要考虑不同的键盘类型,主要是全键盘类型和
“SureType”键盘类型的差别,在不同机型上可能需要对不同的键进行响应。
提醒用户
手机应用的一个特点是充分和手机的功能集成,其中一个重要的功能就是通过手机的功能对用户进行提醒。如一般常用的响铃,振动,闪灯等提醒。在BlackBerry平台上还可以通过修改应用图标,增加主屏幕提醒标记等方式提醒用户。
通过响铃提醒用户
响铃是常用的用户提醒方式,一般Java ME开发人员使用Player类加载一段声音,当有事件发生需要提醒用户通过播放声音的方式提醒用户。因为BlackBerry平台支持标准的Java ME,这种使用Player提醒用户的方法可以在BlackBerry平台上使用,不需要对代码进行修改。此时播放声音的大小,时间长短都由程序决定。给用户带来的影响是即使用户将场景设置为“静音”,即既不振动也不响铃,当该程序有提醒时仍会“响铃”。要改善这一点可以在应用内部为用户提供一个设置,设置这个应用本身是否启用声音,是否启用振动等。
通过振动提醒用户
在标准的Java ME中,通过Display.vibrate()的调用可以让设备振动,该方法在BlackBerry设备上同样支持,有关振动的代码可以不加修改直接在BlackBerry手机上使用。此时设备是否振动,振动时间多长完全由程序控制,不受系统设置影响。所以,这种方法与上一小节中提到的响铃提示的方法有同样的问题,也同样可以通过相同的方法改善这个问题。
通过状态灯提醒用户
在BlackBerry平台上开发人员还可以通过闪烁状态灯来提醒来户,开发人员可以通过
API打开状态灯、关闭状态灯、闪烁状态,此外,如果设备支持的话,还可以设置状态灯的颜色。
BlackBerry手机上的状态灯位置如下图:
用于控制状态灯的类为net.rim.device.api.system.LED,它包括几个静态方法,主要的方法为setState,用于改变状态灯的状态。setState方法接收一个参数,即希望改变成的目标状态,该参数为int类型,但是只能接受以下常量中间的一个:LED.STATE_ON,LED.STATE_OFF,LED.STATE_BLINKING,LED.STATE_AUDIO_SYNC。常用的为◆LED.STATE_ON,LED.STATE_OFF,LED.STATE_BLINKING,它们的效果为:
◆lLED.STATE_ON将状态灯打开
◆lLED.STATE_OFF将状态灯关闭
◆lLED.STATE_BLINKING使状态灯闪烁
在调用setState之前,最好先通过LED.isSupported方法判断当前设备是否支持状态灯变化,判断是否支持状态灯变化的语句为if(LED.isSupported(LED.LED_TYPE_STATUS))
另外要注意,setState方法和isSupported方法只能接受固定的几个参数,如果开发人员没有控制好代码,传入了一些不能接受的参数,该方法会抛出IllegalArgumentException异常。
下面为打开状态灯的代码片段:
- if (LED.isSupported (LED. LED_TYPE_STATUS )) { LED.setState (LED. STATE_ON );
- }
通过主屏幕标记提醒用户
在BlackBerry手机上可以通过主屏幕的标记提醒用户。如下图所示,在电池图标右边的是主屏幕标记,第一个是系统的邮件提示,表示有1封未读邮件,第二个也是系统提示,表示有两条新短信。开人员可以根据应用需要添加应用自己的提示,如图中第三个标记是由程序添加的标记,开人人员可以指定标记的图标图片,可以指定什么时候显示该标记,还可以指定标记旁边的数字。
主屏幕标记通过ApplicationIndicatorRegistry和ApplicationIndicator进行操作。首先要通过ApplicationIndicatorRegistry注册本应用的标记,然后通过ApplicationIndicator控制标记是否显示,显示什么图标,显示什么数字。
具体的标记注册过程如下:
1.通过ApplicationIndicatorRegistry的静态方法getInstance()获得
ApplicationIndicatorRegistry的实例。
2.调用ApplicationIndicatorRegistry实例的register方法注册标记。
ApplicationIndicatorRegistry实例的register方法接受三个参数,第一个参数为ApplicationIcon,用于指定标记所使用的图标,这个图标在以后可以通过程序更换;第二个参数为布尔类型,指定该标记是否只有图标;第三个参数也是布尔类型,用于指定该标记是否可见,标记是否可见在以后可以通过程序进行修改。
下面是注册标记的代码片段:
- EncodedImage readimage = EncodedImage
- .getEncodedImageResource ( "cn/searb/demo/icon/indicator.png" );
- ApplicationIndicato rRegistry indicatorRegistry = ApplicationIndicatorRegistry. getInstance();
- ApplicationIcon icon = new ApplicationIcon( readimage );
- indicatorRegistry.register(icon, false , false );
在注册完标记后,应用程序在需要的时候就可以对标记进行操作,如修改图标,或者是修改显示的数字等。标记的操作过程如下:
1.通过Application Indicator Registry的静态方法get Instance()获得
Application Indicator Registry的实例。
2.通过Application Indicator Registry实例的get Application Indicator方法获得当前应用的
ApplicationIndicator实例。
3.通过Application Indicator实例的set Value方法设置要显示的数字
4.通过Application Indicator实例的set Icon方法设置要显示的图标
5.通过Application Indicator实例的set Visible方法设置该标记是否可见
其中第3,4,5步没有依赖关系,开发人员可以独立调用其中的一个或者是多个方法。下面是标记操作的代码片段:
- ApplicationIndicatorRegistry indicatorRegistry = ApplicationIndicatorRegistry. getInstance ();
- ApplicationIndicator indicator =
- indicatorRegistry.getApplicationIndicator() ;
- ApplicationIcon icon = new ApplicationIcon( readimage );
- indicator.setValue(number); indicator.setIcon(icon); indicator.setVisible( true );
通过应用图标提醒用户
除了使用主屏幕标记以后,开发人员还可以使用改变应用图标的方式提醒用户。假如下图为正常的应用图标:
开发人员可以通过API修改图标,提示用户该应用有新的事件发生,如下图:
修改应用图标相对比较简单,只需要调用net.rim.blackberry.api.homescreen.HomeScreen
的静态方法updateIcon就可以更改本应用的图标,参数为一个Bitmap图片。下面是修改应用图标的代码片段:
- Bitmap icon=Bitmap. getBitmapResource ( " cn/searb/test/unread.gif" );
- net.rim.blackberry.api.homescreen.HomeScreen. updateIcon (icon);
#p#
网络连接调整
BlackBerry接口使用Java ME标准的方法实现Http连接或者是Socket连接,在MIDlet中的网络连接程序可以不加修改地在BlackBerry平台上运行。然而,因为BlackBerry提供更多的网络连接选择,开发人员可以对网络连接代码进行调整,使用应用的网络连接更加稳定,更加符合用户的网络选择。
因为BlackBerry需要支持标准的Java ME,所以还是通过javax.microedition.io.Connector类实现各种连接。为了提供更多的网络选择,BlackBerry平台在Connector的open方法中对URL进行了特殊处理,开发人员可以在URL后面加上一些特殊的参数以标明不同的网络连接。比如,下面的语句在URL后面加上了“deviceside=false”,表明程序希望通过BESMDS连接网络:Connector.open(“http://www.myserver.com;deviceside=false”);
需要了解不同参数的详细情况,可以参考API文档中javax.microedition.io.Connector类的说明。
下面我们根据不同的链路从整体上简单介绍BlackBerry上的不同连接方式。
BlackBerryEnterpriseServer(BES)方式
这种方式通过使用BES的BlackBerryMDSServices来进行网络连接,BlackBerryMDSServices负责处理所有的浏览器请求或者连接请求、并负责数据加密,这是黑莓手机的默认连接方式,如下:
(HttpConnection)Connector.open("http://www.testserver.com");
以上代码会自动将BlackBerryMDSServices作为它的默认连接路径。实际开发中,如果要确保应用程序使用usesBlackBerryMDSServices作为它的连接路径,需要在URL最后加上参数“deviceside=false”,这也是我们推荐的方式,如下:
(HttpConnection)Connector.open(“http://www.testserver.com;deviceside=false”);
BlackBerryInternetServie方式
这种方式是为第三方提供的连接接口,它对数据不进行加密,用户可以通过使用HTTPS
和SSL来进行安全的连接。注:目前仅对加入了BlackBerryAllianceProgram的第三方开放合作伙伴开放,详情参考:http://na.blackberry.com/eng/partners/alliance.jsp。
DirectTCP方式
这种方式允许在没有使用BlackBerryMDS的黑莓手机上直接TCP连接。为了能启用directTCP方式,用户需要在手机的“选项-高级选项-TCP/IP”中设置APN,以及相应的用户名和密码。
运行在iDEN网络上的黑莓手机(包括6510、7510、7520和7100i),如果不指定deviceside参数,默认的连接是directTCP;非运行在iDEN网络上的其他黑莓手机,如果不指定deviceside参数,默认的连接是BlackBerryMDS。
如果连接时BlackBerryMDS不存在,黑莓手机也会自动采用directTCP方式。因此,如果要将directTCP作为黑莓手机的默认连接方式,我们建议在URL中加入
“deviceside=true”参数,如下:(StreamConnection)Connector.open("socket://testserver:600;deviceside=true");
Wi-Fi
如果需要在Wi-Fi上创建网络连接,不需要在应用程序中考虑特别的底层逻辑,可以在
URL中加入参数“interface=wifi”就能实现Wi-Fi连接,如下:(StreamConnection)Connector.open(“socket://testserver:600;interface=wifi”);
WAP1.x
并不是所有的移动运营商都支持通过WAP网关进行连接的,所以如果要创建WAP连接,
开发者需要和移动运营商联系,获取是否提供这种支持、并且获取他们的WAP网管参数。以下是一个基于WAP网关进行HTTP连接的例子:(HttpConnection)Connector.open("http://wap.google.com;WAPGatewayIP=127.0.0.1;WAPGatewayAPN=carrier.com.gprs");
其中WAPGatewayIP和WAPGatewayAPN这两项参数必须指定,参数之间用“;”隔开,
以下是所有的WAP参数列表,实际开发时请根据运营商提供的信息决定哪些参数需要设置。
参数描述
参数
|
描述
|
W apGa t eway I P
|
网关地址
|
W apGa t ewayAP N
|
AP N的名称
|
W apGa t ewayPo r t
|
网关端口号, 如果使用端口 号 920 3的话, 会自动启用 “W i r e le s s T r an s po r tLa y e rSe c u r it y ” ( W TLS ) , 除 非 指 定 了 W apE n ab l e WT LS= f a ls e 参数。
|
W apSou rc e I P
|
W a p服 务器的地址。
|
W apSou rc ePo r t
|
W a p服 务器的端口号 。
|
T u n ne lA u t hU s e r na me
|
AP N的用户名,当网关启 用 Pa ss wo r dAu t hen t ic a t io nP r o t o c o l
( P AP ) 或 者 是 Cha lleng e H an d s ha k e A p p lic a t io n P r o t o c o l
( CHAP ) 时才需要提 供。
|
T u n ne lA u t hPa ss wo r d
|
AP N密码,当网关启用 P A P或 者是 CHA P 时才需要提 供。
|
W apEnab le W TL S
|
指定是否启用 W TL S , 如果没有 指定该参数, 当端 口号为 9203
时会自动应用 W TL S 。
|
如果手机上的ServiceBook上有WAP2.0的网关记录,可以通过APN=
具体的做法是先通过ServiceBook类的getSB方法获得ServiceBook,然后通过ServiceBook实例的findRecordsByCid方法查找在Cid中带“WPTCP”字样的记录。这些记录里可能包含Wifi或者是mms对应的记录,所以要排除这些记录,使用的方法是判断在Uid,查找Uid里不带“Wifi”和“mms”字样的记录。
代码片段如下:
- ServiceBook sb = ServiceBook. getSB ();
- ServiceRecord[] records = sb.findRecordsByCid( "WPTCP" ); String uid = null ;
- for ( int i = 0; i < records. length ; i++) {
- if (records[i].isValid() && !records[i].isDisabled()) {
- if (records[i].getUid() != null
- && records[i].getUid().length() != 0) {
- if ((records[i].getUid().toLowerCase().indexOf( "wifi" ) == -1)
- && (records[i].getUid().toLowerCase()
- .indexOf( "mms" ) == -1)) {
- uid = records[i].getUid();
- break ;
- }
- }
- }
- }
- if (uid != null ) {
- // 在参数中指 定 Co nn ec t io nU ID
- Connector.open (_url + ";ConnectionUID=" + uid);
- } else {
- // 没找到 Wap 2.0 的 se r vi ce bo ok , 做其它处 理 .
- }
数据存储与共享
使用RMS进行存储
Java ME提供了RMS存储机制,MIDP规范定义了持久的、基于记录的存储功能,叫记录管理存储(RMS)。一个MIDlet套件可以使用RMS创建一个或多个记录存储,每个记录由一个唯一的名字标识。在javax.microedition.rms包中可以找到必要的类和接口对RMS进行操作,其中关键是的RecordStore。RecordStore提供了打开,关闭,读取,写入和更新操作,也提供方法删除单个记录或者整个存储。这个包包含各种接口用于列举、排序和筛选RMS内容。
BlackBerry平台提供了对标准Java ME的支持,所以也支持标准的RMS存储机制,开发人员在MIDlet中使用的RMS存储代码可以不加修改直接在BlackBerry上运行。
在MIDP1.0时候,每个RMS存储只属于创建它的MIDlet套件,不能用于不同程序之间的数据共享。MIDP2.0规范给RMS包增加了一个非常有用的能力:它允许一个MIDlet套件和另一个MIDlet套件共享记录存储。
共享一个RMS记录存储需要两个或者多个参与者:一个拥有者和一个或者多个消费者。拥有者负责创建和命名存储,建立授权模式指定存诸为“共享”或者“不共享”,同时可以指定它的访问模式“可写”或者“不可写”。消费者通过名字获得记录存储的访问。消费者不能访问没有共享的存储,也不能修改不可写的存储。
一个共享的RMS被一个三元组标识(提供者名字,MIDlet套件名字,记录存储名字):
l◆提供者名字是JAD或者manifest文件里面的MIDlet-Vendor属性的值。
l◆MIDlet套件名字是JAD文件里面MIDlet-name字段的值
l◆记录存储名字是一个1到32位长的区分大小写的Unicode字符串,是当你创建记录存储时候用的名字。
为了支持共享,MIDP2.0标准在javax.microedition.rms中添一些方法:第一个方法用于打开一个已经存在的记录存储,或者创建一个新的记录存储并设置它的授权和可写特性。
- s t a t ic R ec o r dS t o r e Op enR ec o r dS t o r e ( St rin g r ec o r d S t o r eN am e , boo le an c re a t e, i n t aut hmo de, boo le an w rit ab le ) ;
参数说明如下:
l◆recordStoreName设置记录存储的名字
l◆create,是否强制创建,如果为true,存储不存在时会强制创建
l◆authmode,用于指定授权模式:使用RecordStore.AUTHMODE_PRIVATE话表示不允许共享,使用RecordStore.AUTHMODE_ANY则允许共享,如果存储已经存在这个参数会被忽略。
lwriteable,是否可写,如果为true,指定其他MIDlet套件可以修改这个记录存储,如果存储已经存在这个参数会被忽略。
第二个方法用于打开共享记录存储:
- s t a t ic R ec o r dS t o r e o penR ec o r dS t o r e( St rin g r ec o r dS t o r eN am e, St rin g v endo r Nam e , St rin g
- s ui t eN am e)
参数说明如下:
◆lrecordStoreName是要打开的共享记录存储的名字
◆lvendorName是拥有者MIDlet套件的MIDlet-Vendor属性的值
◆lsuiteName是拥有者MIDlet套件的名字
仅当记录存储的拥有者设置了automode为AUTHMODE_ANY时这个方法才能成功打开记录存储。需要注意的是你不能直接检测存储的可写属性。要发现一个记录存储是否可写的唯一方法是试着向记录存储写入,如果不可写就捕获相应的异常。
记住在JAD或者manifestMIDlet-Version,并不在参数列表中。这意味着拥有者套件无法在影响消费者的前提下改变共享的存储的记录格式。
第三个方法用于修改存诸的属性:
- V o id s et Mo de( in t aut hmo de, boo le an w rit ab le ) ;
参数说明如下:
◆lauthmode指定记录存储的新的授权模式,可以设置为AUTHMODE_PRIVATE或者是AUTHMODE_ANY。
◆lwritable指定存储的信息是否可写。
◆注意只有存诸的拥有者才能改变authmode和writable属性。另外,没有方法能够用来查询这些设置。
如上所述,BlackBerry平台提供Java ME支持,所以开发人员可以在BlackBerry平台上使用RMS方法完成数据的存诸和共享。
使用RunTimeStore进行存储
相对于静态的RMS的繁琐配置和不灵活,BlackBerry提供了RunTimeStor(e运行时存储),使用非常灵活和方便。
RunTimeStore在平台级别提供了数据存储方式和共享方式,它提供了一个中间区域让不同应用可以共享对象,任何通过数字签名的应用程序都可以访问运行时存储。
对于RunTimeStore的使用需要注意的是:
◆对象可以添加到运行时存储或者从运行时存储中替换掉
◆运行时存储必须以一个独一无二的ID创建
◆任何类型的对象都可以放到运行时存储中
◆设备掉电的时候RuntimeStore中的数据会丢失。
下面是创建运行时存储和写入简单数据的代码示例:
- class CreateStoreDemo extends UiApplication {
- // 通过包名 生成的 唯一的长 整型 ID
- public static long STORE_ID = 0x23ad23489a243L;
- public CreateStoreDemo() {
- String msg = “S ha re d te xt f or a no th er a pp li ca ti on ” ; RuntimeStore st or e = RuntimeStore .getRuntimeStore(); try { store.put(S TORE_ID, msg);
- } catch (Exception ex) {}
- }
- }
创建好了以后就可以通过相应代码获取其中的数据,下面是读取RunTimeStore的代码示例:
- class ReadStoreDemo extends UiApplication {
- public ReadStoreD emo() {
- RuntimeStore st ore = RuntimeStore.getRuntimeStore();
- try {
- //cast the r eturned object to a string
- String msg = (String)store.get( CreateStoreDemo.STORE _ID);
- } catch (E xc ep t io n ex ) {
- //handle exc eption
- }
- }
- }
#p#
使用PersistentStore进行存储
在BlackBerry平台上还可以通过PersistentStore存储数据,这种方法可以将对象直接保存在设备内存中,需要使用的时候可以通过API从设备内存中直接读取出来,读取出来看获得的是一个object对象,需要开发人员对该对象进行强制转换。
相比RunTimeStore,PersistentStore的好处是可以持久保存数据,即使设备掉电数据也不会丢失。不过,使用PersistentStore要求被保存的数据必须实现Persistable接口。如下面代码所示,如果希望将MyData实例保存在PersistentStore中,MyData类需要实现Persistable接口:
- public class MyData implements Persistable {
- // 其中为 MyData 的 方法和 属性定义 。
- }
在PersistentStore中保存的对象以一个长整型的ID作为标记,保存或者是获取该对象都以这个长整型的ID作为参数。为了保证所保存的对象和其它应用保存的对象不冲突,可以通过hash算法通过包名生成一个长整型ID。BlackBerryEclipsePlug-in环境也提供了一个方法将字符串转换成长整型,选中某一行字符串,点击右键,选择“ConvertStringtolong”,可以将选中的字符串转换成长整数。
无论用什么方式生成长整数,对象ID的定义语句都类似于以下代码:
- public static long PersistentID = 0x815402392d453a9d L;
定义了PersistentID后,可以通过PersistentStore的静态方法getPersistentObject获得所保存的持久化对象,getPersistentObject方法只有一个参数,为保存对象的ID,本例使用上面定义的PersistentID。
获得持外化对象以后,可以通过该实例的getContents方法获得真正保存在设备内存的对象,可以通过setContents将内存中的对象保存到持久化对象中。在获得对象的过程中记得要将返回的Object实例强制转换为你使用的类。在保存对象时记得要调用PersistentSotre的commit方法完成保存动作。
PersistentStore使用的代码片段如下:
- PersistentObject persistentStore ;
- persistentStore = PersistentStore. getPersistentObject ( PersistentID );
- synchronized ( persistentStore ) {
- if ( persistentStore .getContents() == null ) { myData = new MyData(); persistentStore .setContents( myData ); persistentStore .commit();
- } else {
- myData = ( MyData) persistentStore .getContents();
- }
- }
- }
使用SQLite进行存储
在BlackBerry5.0以上的平台上提供了对Sqlite的支持,使开发人员可以在BlackBerry手机上使用关系型数据库。对于将MIDlet移植到BlackBerry上的开发人员而言,这无疑是一个好消息,使用关系型数据库保存数据可以让程序更加简单有效。
当然,决定是否将数据存储方式由之前的RMS转变为关系型数据库,具体要看应用的规模和数据类型。一般而言,如果应用规模不大,数据类型更接近树状的文档结构,则不建议使用关系型数据库。反之,如果应用规模较大,而且需要存储的数据是大批量的规整的数据,则使用关系型数据库比较有利。
从具体实现上讲,在BlackBerry平台上有DatabaseFactory可以用于创建或者是连接数据库,所创建的数据库以一个文件的形式保存在设备中,可以是设备内存,也可以是媒体卡。
创建或者是连接数据库以后获取了Database实例,可以通过Database实例的createStatement方法创建一句SQL语句,通过Statement实例的prepare方法准备执行,然后通过该实例的execute方法执行。如果是查询语句,可以在prepare方法执行后,通过getCursor获得查询结果的光标,再通过光标操作获得所查询的内容。
下面是创建或者是连接数据库的语句:
- String dbLocation = "/SDCard/databases/SQLite Demo /" ; URI uri = URI.create (dbLocation + "mydb " );
- Database db = DatabaseFactory. openOrCreate (uri, new
- DatabaseSecurityOptions( false )) ;
下面是执行查询语句的代码片段:
- Statement statement = _db .createStatement( "SELECT * FROM Category" );
- statement.prepare();
- Cursor cursor = statement.getCursor();
下面是查询后遍历结果的代码片段:
- Row row;
- int id; String nam e;
- while (c ur s or .n ex t( ))
- {
- ro w = c ur so r. ge tR ow () ;
- id = r o w. ge tI nt eg er(0);
- na me = ro w. ge tS tr in g( 1) ;
- }
使用完之后记得要将Statement实例和Cursor实例关闭,如:
- statement. close();
- cursor.clo se();
对于SQLite的使用,关键点是SQLite是一个轻量级的SQL数据库,不能支持所有的SQL功能。一方面是在数据格式上只支持简单的类似Integer,Text这样的类型,另一方面是所支持的SQL语句也有限,不支持复杂的查询操作。
有关SQLite的更多信息可以在下面的网站中找到:http://www.sqlite.org/
使用全局事件来进行应用交互
对于应用通讯,只能共享数据是不足够的,在共享数据之后需要通知相应的应用,让目标应用可以对数据变化进行响应。
BlackBerry平台提供了事件模型,用来在不同的应用之间通信。BlackBerry上的任何应用程序都可以发布或者监听全局事件,发布全局事件的时候需要指定一个事件ID号,一个应用所发布的事件会被所有监听全局事件的应用获取,不同的应用需要通过ID号判断是否对该事件进行处理。另外,在发送全局事件的过程中可以同时发送一些简单的数据,使用起来会更简单一些。但是对于大量的数据,一般采用的方式是先将数据保存到特定的共享空间中,然后通过事件通知目标应用。
首先需要定义一个全局事件,对于全局事件的定义,BlackBerry有自己的定义规范:
◆定义一个ID变量
◆把ID变量定义为静态的,从而使得其他的类也可以引用到
◆通对包名做HASH产生ID,使ID变得独一无二示例代码如下
- public static long GLOBAL_ID = 0xba4b84944bb7429eL;
定义了全局事件后可以将该事件发布,通过把事件ID传递到postGlobalEvent()方法中,我们可以发布一个全局事件.BlackBerry提供了有四种不同的方法来发布一个事件,简单的方法是只传入事件ID不带数据,相对复杂的方法在发布事件时可以传入两个整数作为随事件发布的内容,更复杂的可以在发布事件时传入对象作为发布的内容。开发人员可以根据自己程序的复杂程序决定使用什么方法发布一个事件。
最简单的发布事件的示例代码如下:
- ApplicationManager.getApplicationManager()
- .postGlobalEvent(GLOBAL_ID);
对于事件接受者来说,需要考虑和实现的关键点如下
◆全局监听应用程序必须要是一个自动启动应用程序
◆监听程序需要有类实现一个GlobalEventListener接口
◆监听程序需要添加GlobalEventListener实例
实现GlobalEventListener和添加ClobalEventListener的示例代码如下:
- class GlobalEventListenerApp extends UiApplication i mp le me nt s
- GlobalEventListener {
- public GlobalEventListenerApp() {
- addGlobalEventListener ( this );
- }
- }
对于实现GlobalEventListener接口的类,必然实现eventOccured方法以响应全局事件的发生。需要注意的一点是不管事件由谁发布,也不管事件是系统事件还是程序发布的事件,只要有事件发生,一个注册过的GlobalEnventListener的eventOccured都会被调用,这意味着开发人员需要自己通过事件ID进行判断,如果事件ID属于需要处理的范围才对事件进行响应。示例代码如下:
- public void eventOccured( long guid, int data0, int dat1,
- Object object0, Object object1 ) {
- // 注意这里需要检 查事 件 ID 是不是 需要响 应的事件 。
- if (guid == Gl ob al E v e n t F i r i n g A p p .GLOBAL_ID) {
- // 在这里完成事件响 应
- }
- }
接收推送数据
数据推送是BlackBerry平台的一大优势,当服务器端有数据更新时,应用服务器可以将数据推送到手机上,不需要手机上的应用通过轮询的方式检查服务器上是否有需要更新的数据。有关数据推送的基本架构与服务器的推送代码,请参考BlackBerry推送的相关文档,文小节只描述如何在MIDlet应用中加上手机上侦听推送数据的方法。
应用自启动
如果希望在手机端侦听从服务器上推送的数据,一般而言需要自动启动该侦听应用,否则有可能该侦听应用没有启动,导致推送数据没有被客户端程序接收。
在BlackBerry平台上要自动启动一个应用程序比较简单,可以直接通过设置完成,在BlackBerry项目中双击打开“BlackBerry_App_Descriptor.xml”,在左边“GeneralInformation”栏下方选中“Auto-runonstartup”就可以让该项目中的应用在手机启动过程中自动启动。
需要注意的是选项“Auto-runonstartup”只有在应用类型为“BlackBerryApplication”
时可用,也就是说如果你选择应用类型为“MIDlet”的话就不能使用“Auto-runonstartup”选项。
所以需要创建一个BlackBerry应用进行侦听,而不是使用标准的MIDlet。
有关BlackBerry应用项目的创建在这里不做详细描述,需要了解具体的步骤请参考相关文档。创建了BlackBerry项目后首先要考虑应用的启动代码,BlackBerry应用与普通的java应用一样以main方法作为入口。
为了更好地对应用实例进行控制,这里采用单例模式,为类PushedDataListener创建一个静态的单例获取方法,在main函数中进行调用。
- public static void main(String[] args) { PushedDataListener. waitForSingleton ().start();
- }
用于获取单例的静态方法实现如下,一如普通的单例获取方法,该方法的返回值为PushedDataListener本身。在该方法中,使用了RuntimeStore将实例保存起来,如果在RuntimeStore中已经有实例的话则从RuntimeStore中获取,没有则创建一个新的实例并返回。
- public static PushedDataListener waitForSingleton() {
- // make sure this is a singleton instance
- RuntimeStore store = RuntimeStore. getRuntimeStore (); Object o = store.get( RTSID_MY_APP );
- if (o == null ) {
- store.put( RTSID_MY_APP , new PushedDataListener());
- return (PushedDataListener) store.get( RTSID_MY_APP );
- } else {
- return (PushedDataListener) o;
- }
- }
推送侦听
从上一节的启动代码可以看到,在应用实例得到后调用了start方法。PushedDataListener类本身不是一个线程,所以这个start方法需要自己实现,在这个方法中创建了一个ListenerThread的实例,并启动该线程。
ListenerThread实例的实现主要是创建侦听连接,并通过一个不结束的循环不断从该连接中获取服务器上推送下来的数据。
首先需要定义StreamConnectionNotifier实例,StreamConnectionNotifier用于创建连接。其次需要定义StreamConnection和InputStream,从连接中获取到输入流,用于侦听数据侦听。
- StreamConnectionNotifier notify = null ; StreamConnection stream = null ; InputStream input = null ;
定义StreamConnectionNotifier实例后通过Connector的open方法打开连接。Connector是一个标准的类,用于连接不同连接,如http,socket等。连接不同网络的时候都是使用了open方法,在参数中传入URL打开指定的地址。网络的不同类型通过URL参数的不同进行区分,如http的URL以“http://”开头,而socket的URL是以“socket://”开头。推送数据不属于其它标准的协议,所以它的URL格式比较特殊,格式为:“http://:
- notify = (StreamConnectionNotifier) Connecto r.open ( LISTEN_URL );
获得连接后需要将连接强制转换成StreamConnectionNotifier。获得Stream Connection Notifier后就通过一个循环不断侦听获取推送数据。
循环的第一句为notify.acceptAndOpen,注意这一句语句执行后该线程会开始等待,不再执行,直到有推送数据到达。
推送数据到达后accept And Open方法返回一个Stream Connection实例,通过Stream Connection实例的open Input Stream方法可以获得输入流,然后通过输入流获得推送数据。
注意操作完成后需要将输入流和StreamConnection关闭,关闭后重新开始侦听,等待下一个推送数据。
侦听的代码片段如下:
- for (;;) {
- stream = notify.acceptAndOpen();
- input = stream.openInputStream();
- // 在这里通 过对 input 的操 作获得推 送数据
- input.close(); stream.close(); stream = null ;
- }
另外需要注意的是异常处理,因为连接的建立和输入流的处理可能会因为一些原因抛出异常。一种方法是在循环内处理异常,处理完了继续循环,但是这无法对Connector.open方法进行处理。所以需要在循环外对异常进行处理,将Connector.open包含进来,本文的例子就是使用这种方法。但是,实际而言这种处理方式也有问题,当输入流处理有问题的时候会跳出循环,不再侦听推送数据。
最终建议的方法是建立两重循环,在两重循环都加上异常捕获。外层循环中的异常处理负责处理Connector.open的异常,出现异常的话处理后重新通过Connector.open打开连接。内层循环对输入流异常进行处理,处理后继续侦听数据,而不需要重新通过Connector.open打开连接。具体代码请参考开发环境附带的样例。本文为简化代码没有采用双重循环的方式。
侦听程序与主程序的交互
获取侦听数据后需要做的工作是通知主程序有新数据到达,由主程序对数据进行处理。因为侦听程序为后台程序,不负责界面更新等操作,界面更新的操作由MIDlet主程序完成。可以看到关键是作为侦听程序的BlackBerry应用与作为主程序的MIDlet如何交互,这可以使用上一章节介绍的方法,通过GlobalEvent通知主程序,然后将数据写入共享数据中。主程序在接收到事件通知后共享数据中获得推送数据再进行数据处理和界面更新处理。具体方法不再详细描述。
推送侦听的完整代码
下面是推送数据侦听的完整代码,不包括应用交互部分:
- package cn.searb;
- import java.io.IOException;
- import java.io.InputStream;
- import javax.microedition.io.Connector;
- import javax.microedition.io .StreamConnection;
- import javax.microedition.io.StreamConnectionNotifier;
- import net.rim.device.api.system.RuntimeStore;
- import net.rim.device.api.ui.UiApplication;
- public class PushedDataListener extends UiApplication {
- public static final long RTSID_MY_APP = 0x68b31bd292413108L;
- private static final String LISTEN_URL = " ht tp :/ /: 91 1" ; // th e li st en port
- private ListenerThread myThread ;
- public static void main(String[] args) { PushedDataListener. waitForSingleton ().start();
- }
- public PushedDataLi stener() {
- myThread = new ListenerThread();
- }
- public static PushedDataListener waitForSingleton() {
- // make sure this is a singleton instance
- RuntimeStore store = RuntimeStore. getRuntimeStore (); Object o = store.get( RTSID_MY_APP );
- if (o == nu ll ) {
- store.put( RTSID_MY_APP , new PushedDataListener());
- return (PushedDataListener) store.get( RTSID_MY_APP );
- } else {
- return (PushedDataListener) o;
- }
- }
- public void start() {
- invokeLater( new Runnable() {
- public void run() {
- myThre ad .start();
- }
- });
- this .enterEventDispatcher();
- }
- class ListenerThread extends Thread {
- public void run() {
- System. out .println( "DemoOA BackGroundThread -- running" ); StreamConnectionNotifier notify = null ;
- StreamConnection stream = null ; InputStream input = null ;
- try {
- sleep (1000);
- } catch (Exception e) {
- }
- try {
- notify = (StreamConnectionNotifier) Connector.open ( LISTEN_URL );
- for (;;) {
- stream = notify.acceptAndOpen();
- input = stream.openInput Stream();
- //
- stream.close();
- stream = null ;
- }
- } catch (IOException e) { System. err .println(e.toString());
- } finally {
- try {
- if (stream != null ) {
- stream.close();
- }
- } catch (Exception ex) {
- }
- try {
- if (notify != null ) {
- notify.close();
- }
- } catch (Exception ex) {
- }
- }
- }
- }
- }
小结
通过本文的介绍读者可以发现,将MIDlet程序移植到BlackBerry上并不困难,同时开发人员也可以根据项目的时间要求和应用的特点决定移植的程度。
不过,无论如何,移植的MIDlet程序都不能够充分地发挥BlackBerry平台的优势,如果希望最大地发挥BlackBerry平台的优势,还是需要根据BlackBerry平台的特点重新对应用进行实现。
【编辑推荐】