BES推送應用實例演示與分析
本文通過一個實際的應用案例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測試即可

