本文通过一个实际的应用案例ECL(Emergency Contact List),来介绍经典的BlackBerry通过BES(BlackBerry Enterprise Server)数据推送的功能,包括了服务器端和手机端的源代码。
主要演示的技术点包括:
◆服务端Java程序如何通过调用BES服务器推送功能向手机推送数据
◆手机端Java应用如何侦听并接收数据
◆手机端Java应用如何变换图标,以提醒用户有新的数据到达该手机该代码实例可以运行在模拟器环境(MDS模拟器+BlackBerry手机模拟器),也可以运行在真实的(BES+BlackBerry真机)环境。本文主要介绍了在模拟器环境下代码运行的情况。
注:为方便开发人员学习和使用BlackBerrypush技术,黑莓官方网站上提供了一个样例程序Emergency Contact List(简称ECL),包括Java/ASP.NET/Domino三种语言版本,支持浏览器Push和Java应用程序Push。Java版本的程序源代码和运行脚本在网页www.blackberry.com/go/ecl上面上可以免费下载。在本文中,我们将分析Java版本ECL样例程序。
各语言版本ECL程序的比较:
Database ( s ) Utilized( 数据来源 )
|
Server - Side
Technology
|
|
BlackBerry Client
Implementation
|
|
ECL Version
|
Push Inter face
|
|||
|
|
|||
Java ECL
|
Microsoft Excel
|
Java EE
|
命令行
|
Java Application
Browser Application
|
ASP .NET ECL
|
Microsoft Excel
Microsoft Access
Microsof t SQL Server
|
.NET
|
命令行
GUI Windows®
|
Java Application
Browser Application
|
|
|
|
S e rvic e
P ush Acce ss
P rotoc ol (PAP )
|
|
Domino® EC L
|
I BM L otus Note s
|
J a va EE
|
命令行
L otus Note s 界面
|
J a va Applica ti on
B row s e r A ppli ca ti on
|
模拟业务场景:
ECL(EmergencyContactList)实例模拟了以下业务场景:
1.用户BlackBerry手机端安装ECLJ2ME客户端应用
2.服务器端为一个Excel数据表(代表数据库)
3.当在Excel表中加入新的数据或者修改了数据后,服务器端可以发起一个推送,将更新后的Excel表内容全部推送到BlackBerry手机
4.BlackBerry手机用户注意到手机端应用图标发生变化,表明有新数据到达手机,即可在手机上打开应用,查看数据
***部分演示环境配置
这里描述了在一台电脑上,如何使用模拟器演示该代码实例。需要安装的软件包括:
◆BlackBerry Emailand MDS Services Simulators 4.1.4
◆BlackBerry Smartphone Simulators 5.0.0(也可以使用4.5,4.6,4.7等版本,但需要通
过Eclipse修改载入不同SDK,详细方法见附录)
◆Eclipse SDK 3.5.1(可选,V3.4也可以,用于研究代码用)
◆BlackBerry JDE Plug-infor Eclipse Version:1.1.1.2009 11111641-15
安装以上软件在一台电脑上,一般采用缺省目录安装即可。
解压缩ECL_Java.zip到c:\ECL_Java目录下,下面解释一下主要文件夹内容和其作用:
◆Readme.txt:使用说明文档
◆目录src:代码源文件目录,包括手机端代码和服务器端
◆目录bin:可执行文件目录,用于实际演示用
第二部分演示过程
1.启动BlackBerry Emailand MDS Services Simulators 4.1.4
2.启动BlackBerry Smartphone Simulators 5.0.0
3.安装ECLSample.cod文件到手机模拟器
模拟器:File=>LoadJavaProgram=>选择5.0版本的ECLSample.cod
4.在DOS命令行下输入命令:
C:\ECL_Java\bin\Server>runcatchersimulatoremai.txtxyzlist.xls
运行后显示successful push表示推送成功。
5.手机端应用图标发生变化,然后进入应用查看信息
6.修改Excel表内容,
到C:\ECL_Java\bin\Server目录下修改xyzlist.xls文件,如把内容修改一下,例如将”John Peng”改成“Petter Liang”
7.在DOS命令行下输入命令:
C:\ECL_Java\bin\Server>runcatchersimulatoremai.txtxyzlist.xls
8.手机端应用图标发生变化,然后查看更新后的信息
#p#
第三部分手机端代码导入Eclipse
1.打开Eclipse,
2.File–Import,选择General=>Existing Projects to Workspace
第四部分核心代码分析
BlackBerryPush架构
在分析样例代码之前,首先让我们从整体上了解BlackBerryPush架构。
更加详细的BlackBerry推送机制的分析和介绍,请参考黑莓官方网站,以及参考资料“BES服务器推送机制分析”。
从示意图中,我们可以看到,在BlackBerry应用平台上的数据推送从整体上可以分为六步,按时间顺序分别为:
1.应用服务器向MDS/BES服务器发送推送请求,该请求为HTTPPOST请求。
2/3:MDS/BES服务器做必要的权限和数据监测,告知应用服务器推送请求是否被接受并将被执行。
4:MDS/BES服务器通过BlackBerryInfrastructure和无线网络把数据推送到手持设备端。
5:手持设备收到数据后,向MDS/BES服务器反馈。
6:MDS/BES服务器告知应用服务器其推送数据是否送达手持设备。
ECL系统架构
从BlackBerryPush系统架构上看,从服务器端Push数据到手机端,要经过多个服务器和网络甚至无线基站,整个流程相当复杂。幸运的是,从应用开发的角度,程序员可以不考虑Push的复杂底层实现,而只需简单地发送Push请求并监听Push请求的处理状态,客户端监听并接受推送数据即可:
1.服务器程序向MDS/BES服务器发送推送请求,所发送的请求为HTTPPOST
请求。
2.手持设备上的Java客户端程序接收push过来的数据,做出灯光闪烁/振铃等提示,甚至修改桌面图标提示用户,用户打开程序界面查看数据。
3.服务器程序从MDS/BES服务器获得推送请求是否完成的状态信息。
Java版本ECL服务器代码分析
核心代码说明,Java类列表
Java版本ECL服务器程序是一个命令行程序,根据命令行参数把指定excel表格中的联系人列表contactlist数据推送给指定的手机上。
1.主程序:是eclTimed Update.java,这是一个包含main()方法的控制程序,根据不同参数调用两个Pusher类做Browser Push和Customize Push。
2.核心Push代码:由Browser Channel Pusher.java/Custom App Pusher.java/Pusher.java三个类构成。
3.服务器配置信息:MdsProperties.java等四个类用于读取配置文本文件,让ECL服务器主程序获得MDS/BES服务器IP地址端口号等信息。
4.数据访问:DataReader.java通过JDBC-ODBC接口读取Excel表单数据。
ECL服务器程序各个Java类的功能说明列表如下:
根据参数构造Pusher类(Browser Channel Pusher或者Custom App Pusher)作为整个服务器端推送数据程序的入口,eclTimed Update.java是一个标准的J2SE程序。
在main()方法中首先分析***个参数。如果参数值是channel,则构造pusher为Browser Channel Pusher类;如果参数值是catcher,则构造pusher为Custom App Pusher类。
- public static void main(String[] args)
- …
- // Process command-line arguments
- if (args. length >= 1) {
- if (args[0].equalsIgnoreCase( "channel" )) {
- // Construct a pusher that sends to a browser channel.
- pusher = new BrowserChannelPusher (); //Browser push 也是一个很有 趣的话 题
- } else if (args[0].equalsIgnoreCase( "catcher" )) {
- // Construct a pusher that sends to a custom catcher.
- pusher = new CustomAppPusher ();
- }
- }
根据参数从文本文件中读取BlackBerry手机PIN码列表
例如从simulatoremail.txt文件中读取到的PIN码为黑莓模拟器的PIN码2100000a。
提示:BlackBerryMDS/BES服务器支持以手机PIN码和email地址识别要推送的目标手机。在ECL样例程序中是使用手机PIN码。
- if (args.length >= 2) {
- emailListFile = args[1];
- }
- … …
- // Get the list of emails to push to.
- List recipientEmails = getRecipients(emailListFile);
根据参数从excel表格文件中读取ECL联系人列表
在这里调用Data Reader类访问Excel表,读取每个组和每个人的联系方式,并通过pusher.add Contact(dataFields);方法把数据传递给pusher,***调用pusher.finished Construction();方法告诉pusher数据全部读取完毕。Pusher会把所有联系人方式构造成一个xml字符串保存起来以备下一步调用pusher.send To Hand held(curRecipient);进行发送。
- // Fetch the group names from the spreadsheet.
- Vector groupDescription = dataReader.getGroupList();
- // Assemble the data of all contacts (from all groups) that we
- // will push to handhelds.
- for ( int i = 0; i < groupDescription.size(); i++) {
- // Define the current group.
- pusher.beginGroup((String)groupDescription.elementAt(i));
- // Add all its members.
- Vector groupContactList = dataReader.getContactList(
- (String)groupDescription.elementAt(i));
- for ( int j = 0; j < groupContactList.size(); j++) {
- String[] dataFields = dataReader.getContactData(
- (String)groupContactList.elementAt(j));
- pusher.addContact(dataFields);
- }
- }
- // Indicate that we're done building the contacts list. After,
- // the pusher is ready to send the message multiple times.
- pusher.finishedConstruction();
调用pusher.send To Handheld()推送数据到每一部黑莓手机上面做一个For循环,多次调用pusher.send To Handheld(cur Recipient)通过BES/MDS服务器把数据发送到各个手机上面。
- // Push the message we just built to all recipients.
- for ( int i = 0; i < recipientEmails.size(); i++) {
- String curRecipient = (String)recipientEmails.get(i);
- try {
- System. out .print( "Contacting BES for " + curRecipient);
- pusher.sendToHandheld(curRecipient); System. out .println( " - successful push." );
- } catch (Pusher.MDSConnectionException ex) {
- // Unable to connect to MDS. The condition is unlikely
- // to be temporary so abort.
- System. out .println( " - " + ex.getMessage());
- Pusher.java代码分析
- break ;
- } catch (Pusher.MDSResponseException ex) {
- // Connected OK but MDS responded with error. Log it and
- // try the next email.
- System. out .println( " - MDS responded with "
- + ex.getMessage());
- } catch (Exception ex) {
- // Unexpected error. Log a full stack trace and try the
- // next email.
- System. out .println( " - unexpected error:" );
- ex.printStackTrace();
- }
- }
Pusher.java代码实际上包括两部分内容,一部分是准备要推送的数据,一部分是真正的推送操作。
Begin Group(),add Contact(),finished Construction()三个方法是用来准备要推送的数据。主逻辑程序多次调用这三个方法告诉Pusher有哪些联系人数据要推送。Pusher会把这些数据拼装成一个html或者是一个大文本,并最终推送给手机浏览器或者手机客户端程序。writeTo(OutputStream)方法用来把前面三个方法构造出来的数据写到输出流中。
注意:以上4个方法和push无关,更好的做法应该是把他们分离出去做一个独立的数据构造类。
Pusher.java代码的核心方法是send To Handheld(Stringrecipient Email)。该方法构造标准的http POST请求给MDS/BES服务器,通过参数DESTINATION指定要把数据push给哪些人(参数值可以是email地址或者是手机PIN码),参数PORT告知要把数据推送到手机上面哪个端口(例子代码中是端口911)。
- /**
- * Pushes the already - constructed message to the indicated recipient. May
- * be used many times, but only in the "sending" state.
- */
- public void sendToHandheld(String recipientEmail)
- throws IOException, MDSConnectionException, MDSResponseException
- {
- HttpURLConnection conn = null ;
- OutputStream out = null ;
- try {
- // Build the URL to define our connection to the BES.
- URL url = new URL( "http" , _props .getBesHostName(),
- _props .getBesPushPort(),
- "/push?DESTINATION=" + recipientEmail
- + "&PORT=" + getDevicePort()
- + "&REQUESTURI=/" );
- conn = (HttpURLConnection)url.openConnection();
- conn.setDoOutput(true ); //to post data
- conn.setRequestMethod("POST" );
- establishRequestHeaders(conn);
- //write the data to the http connection
- out = conn.getOutputStream();
- writeTo(out);
- // Check the MDS's response so that we report an error if the push
- // was unsuccessful.
- int responseCode = conn.getResponseCode();
- if (responseCode != HttpURLConnection. HTTP_OK ) {
- String serverMessage = conn.getResponseMessage();
- throw new MDSResponseException(
- "HTTP-" + responseCode + ": " + serverMessage);
- }
核心代码说明,Java类列表
客户端代码由三个Java类,两个图标文件,BlackBerry应用描述文件构成。
ECL客户端程序三个Java类的功能说明列表如下:
ECL客户端程序的两个入口点(AlternateEntryPoints)
ECL客户端程序是如何自动启动监听程序的?又是如何区分点击icon启动GUI界面程序的呢?答案在应用描述文件BlackBerry_App_Descriptor.xml和主程序的main()方法上。
打开BlackBerry_App_Descriptor.xml,在Application标签栏目里面,你会看到Auto-runonstartup被选中。这样,ECLSample程序会在手机启动过程中自动地启动,产生一个后台Java进程监听Push数据。
在Alternate Entry Points栏目中,设置了一个参数Applicationargument为gui,并单独设置了程序Title和Icon,因此程序安装后在“下载”目录里面出现一个应用图标。点击图标将产生一个前台GUI进程来读取并显示Push过来的数据。
#p#
ECL Application客户端主程序说明
在ECL Application的main()方法中,如果没有任何参数传进来,那么就是说程序是在手机启动的时候被初始化调用的,启动Pushed Data Listener线程在后台运行,监听push来的数据。
- class ECLApplication extends UiApplication {
- private MenuItem _copyItem ;
- /******************************************************************************************
- * main() - controls the startup of the application...thread will start i n the background
- * and the GUI starts when user clicks from main menu
- ******************************************************************************************/
- public static void main(String[] args) {
- if ( args != null && args. length > 0) { //entry point 是有参数 的,那么 打开 GUI 窗口
- ECLApplication theApp = new ECLApplication();
- theApp.enterEventDispatcher();
- }
- else { //entry point 是没有参 数的, 那么启 动 Pu sh ed Da ta Li st en er 线程在后 台运 行,监听 push 来的数 据
- PushedDataListener.waitForSingleton ().start();
- }
- }
在ECL Application的main()方法中,如果发现有参数传进来,那么说是有用户点击了应用图标,想打开GUI界面查看数据。
- class ECLApplication extends UiApplication {
- public static void main(String[] args) {
- if ( args != null && args. length > 0) { //entry point 是有参数 的,那么 打开 GUI 窗口
- ECLApplication theApp = new ECLApplication();
- theApp.enterEventDispatcher();
- }
- else { //entry point 是没有参 数的, 那么启 动 Pu sh ed Da ta Li st en er 线程在后 台运 行,监听 push 来的数 据
- PushedDataListener.waitForSingleton ().start();
- }
- }
在ECLApplication构造方法中,首先把应用的图标从未读状态,修改为已读状态;然后调用DataStore类读取PersistentStore里面的数据,***弹出EclScreen窗口以树状组件显示数据。
DataStore和EclScreen代码因为和Push操作无关,这里就不再做代码分析了。
- public ECLApplication () {
- …
- Bitmap icon=Bitmap.getBitmapResource("icon/read.gif");
- net.rim.blackberry.api.homescreen.HomeScreen.updateIcon(icon);
- //call the datastore class
- DataStore dataStore = new DataStore();
- //opens the persistent store and loads in the group list dataStore.loadGroupListFromStore();
- EclScreen screen = new EclScreen(dataStore);
- pushScreen(screen);
- }
Pushed Data Listener代码说明
Push监听代码是客户端最核心的代码。Push.java首先是使用wait For Singleton()方法达到手机上只有一个Pushed Data Listener对象在运行的目的,然后构建并启动Listener Thread类开始监听。
wait For Singleton()方法把唯一的Pushed Data Listener对象实例保存到Runtime Store对象中,从而实现singleton模式。Runtime Store在黑莓手机中是程序间共享的存储空间,或者说所有程序都可以访问其他程序保存在Runtime Store里面的数据。在手机重新启动后,Runtime Store被清空。
- class PushedDataListener extends UiApplication {
- ime
- public static final long RTSID_MY_APP = 0x56b19e51d45ff827L;
- private static final String LISTEN_URL = "http://:911" ; //the listen port
- private ListenerThread myThread ;
- public PushedDataListener() {
- myThread = new ListenerThread();
- }
- public static PushedDataListener waitForSingleton (){
- Push.java Pushed Data Listener的监听代码放在Listener Thread类中。Listener Thread类扩展
- //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;
- }
- }
Thread线程类,调用(Stream Connection Notifier)Connector.open(LISTEN_URL)方法和stream=notify.acceptAndOpen();代码开始监听911端口上push过来的数据。
收到数据后调用Data Store方法保存数据到持久存储中,工ECLGUI进程显示数据;然后调用Home Screen.update Icon(icon,1)代码更新ECL图标为未读提示。
- class ListenerThread extends Thread {
- public void run() {
- System. out .println( "eclBackGroundThread -- running" ); StreamConnectionNotifier notify = null ;
- StreamConnection stream = null ; InputStream input = null ;
- try {
- sleep (1000);
- }
- catch (Exception e){}
- try {
- notify = (StreamConnectionNotifier)Connector. open ( LISTEN_URL );
- for (;;) {
- //NOTE: the following will block until data is received
- stream = notify.acceptAndOpen();
- input = stream.openInputStream();
后记:
- //Extract the data from the input stream
- StringBuffer sb = new StringBuffer();
- int datum = -1;
- while ( -1 != (datum = input.read()) )
- {
- sb.append(( char )datum);
- }
- stream.close();
- stream = null ;
- String contactData = sb.toString(); DataStore dataStore = new DataStore(); dataStore.saveData(contactData);
- Bitmap icon=Bitmap.getBitmapResource ( "icon/unread.gif" );
- net.rim.blackberry.api.homescreen.HomeScreen. updateIcon (icon,1);
- bytes) \n" );
- System. err .println( "Push message received...(" +contactData.length()+ "
- System. err .println(contactData);
常见运行错误处理
1.模拟器无法收到Push数据
解决办法:使用***的BlackberryPluginforeclipse1.1版本,运行项目的时
候选择启动MDS模拟器。启动模拟器后,确认模拟器的网络连接是好的,方法是打开浏
览器访问任意外网网站。
2.服务器端和客户端程序各自的端口不匹配
解决办法:在ECL例子程序中两个端口都是911,注意查看。如果你参考ECL代码编写自己的Push程序,那么建议你把服务器和客户端的端口都修改为非911的端口,以避免在手机上参数监听端口冲突。
3.客户端程序没有autostart,因而没能启动监听程序
解决办法:在ECL例子程序中,编辑BlackBerry_App_Descriptor.xml,选择Auto-runonstartup,并在代码的main()方法中相应处理,启动listenerthread在后台开始监听。
4.客户端收到的文字中文乱码解决办法:首先,服务器端代码在提出Push请求时候要通过Http URL Connection.set Request Property(("Content-Type",…)方法告诉BES/MDS服务器,它将发送什么样的内容。如果是Content-Type是text/plain,那么服务器将以gb2312编码发送数据;为代码清晰起见,建议设置Content-Type为text/plain;charset=utf-8。
- public void sendToHandheld(String recipientEmail) … {
- HttpURLConnection conn = null;
- …
- establishRequestHeaders(conn);
- …
- }
- protected void establishRequestHeaders(HttpURLConnection conn) {
- conn.setRequestProperty("Content-Type", "text/plain ; charset=utf-8 ");
- }
其次,客户端接收数据的时候,建议读取byte[],然后按照服务器Content-Type相同的编码进行转码获得字符串String。
- stream = notify.acceptAndOpen();
- input = stream.openInputStream();
- //Extract the data from the input stream byte[] bytes = loadBytesFromStream(input); stream.close();
- stream = null;
- String contactData = new String(bytes , “UTF-8” );
英文缩写说明
BES–BlackBerry Enterprise Server,是BlackBerry解决方案的核心服务器之一,架设在企业内网。
MDS–BlackBerryMobileDataSystem,是BES服务器的一个重要组件。在Push技术中,
MDS接受Push请求,然后通过BES其他组件把数据推送出去。
参考资料
1.BES服务器推送机制分析
http://www.searchcio.com.cn/whitepaper_1161.htm
2.What Is-Sample applications demonstrating BlackBerry pushtechnology:Emergency ContactList
http://www.blackberry.com/go/ecl
附录:
一.如何更换不同版本的BlackBerry SDK?
由于不同BlackBerry手机使用不同版本的OS,所以在应用编译的时候,需要使用不同的
BlackBerry SDK。
首先可以在Eclipse中获取所有版本的BlackBerry SDK,目前有的包括4.5,4.6和5.0版本:Eclipse=>Help=>InstallNewSoftware=>输入URL:
http://www.blackberry.com/go/eclipseUpdate/3.5/java
然后进行更新即可。
二.如何选择不同版本的BlackBerry SDK?
在上图中选择Edit
然后再次编译即可:
或者:在JRE System Library右击鼠标,属性中选择不同的SDK即可。
三.ECLsampleBrowserpush中文问题的解决
现象:xyzlist.xls数据源里面有中文,通过MDS服务器push到手机里面的网页,中文是乱码,显示不正常。
step1.编辑ECL_Java\src\Server\BrowserChannelPusher.java把con.set Request Property('Content-Type','text/html');
修改为con.setRequestProperty('Content-Type','text/html;charset=gb2312');
step2.重新编译生成ECL_Java\bin\Server\ecl.jar执行ECL_Java\src\Server\build.bat
step3.重新运行pushserver/client测试即可