BlackBerry 应用程序开发者指南 第一卷:基础--第10章 创建Client/Server Push应用程序
作者:Confach 发表于2006-04-28 21:45
版权信息:可以任意转载, 转载时请务必以超链接形式标明文章原始出处 和作者信息.
http://www.cnblogs.com/confach/articles/387911.html
10
第10章 创建Client/Server Push应用程序
Push应用程序 Client/Server push请求 编写一个客户端push应用程序 编写一个服务器端push应用程序 Push应用程序疑难解答 |
Push应用程序
注:Push应用程序需要3.5以及后续版本的Microsoft Exchange BES,或2.0以及后续版本的IBM Lotus Domino BES,它们需启用BlackBerry MDS服务。
Push应用程序将新的web内容和alert发送到指定的用户。用户不必请求下载数据,因为当信息可用时push应用程序递送这个信息。
有2种push应用程序:
1. 浏览器 push应用程序:将Web内容发送到BlackBerry设备上。BlackBerry 浏览器配置支持MDS服务push应用程序。WAP浏览器配置支持WAP push应用程序。Internet浏览器配置不支持push应用程序。参看BlackBerry浏览器开发指南获取更多关于编写一个浏览器push 应用程序的信息。
2. Client/Server push应用程序:将数据push到一个BlackBerry设备上的客户Java应用程序。Client/Server push应用程序由一个BlackBerry设备上的客户Client应用程序和一个push内容给它的服务器端应用程序组成。和浏览器push应用程序相比,这种方法对这种你可以发送出去的内容以及数据是如何处理并且显示在BlackBerry设备上提供了更多的控制。
Client/Server push请求
应用程序可以使用下面的2种方法将内容push到BlackBerry设备:
1. Push Access Protocol(PAP,push访问协议),它是WAP 2.0里的一部分。
2. RIM push。
注:MDS服务仅支持1000个 push请求,包括了RIM和PAP push请求。如果它接收超过1000个请求,MDS服务回应服务器一个错误。
这2种push服务的实现都支持下面的任务:
- 发送一个服务器端push提交(submission)。
- 为push提交指定一个信任的模式。
- 为push提交指定一个传递前(deliver-before)的时间戳。
- 请求一个push提交的结果通知。
PAP的实现还支持下面额外的任务:
- 为push提交指定一个传递后(deliver-after)时间戳。
- 取消一个push请求提交。
- 查询一个push请求提交的状态。
存储push
PAP push存储在数据库中,但是RIM push则存储在RAM中。如果服务器重启,没有递送的RIM push可能会丢失。
代码转换(Transcoding)
如果可用,BlackBerry MDS Data Optimization(BlackBerry MDS数据优化)根据它的代码转换器规则将一个代码转换应用到push请求。
Push请求可以覆写这些规则并使用transfer-encoding头来请求一个指定的代码转换器转化。例如,如果设置了HTTP头transfer-encoding:vnd.wap.wml,那么在它把数据push到BlackBerry设备之前,MDS数据优化服务运行wml代码转换器。
参看190页的“代码转换器”获得更多信息。
信任模式
信任模式 |
描述 |
透明层信任模式 |
当push到达BlackBerry设备时,BlackBerry MDS连接服务启动一个push请求指定的URL连接来通知传送的服务器。手持设备软件3.6或以后版本支持透明层确认。 |
应用程序级信任模式 |
当push到达BlackBerry设备时,应用程序确认内容。MDS连接服务启动一个push请求时指定的URL连接来通知传送的服务器。如果遇到错误,MDN连接服务发送一个错误消息到服务器。手持设备软件4.0或以后版本支持应用程序级确认 RIM push提供一个优先应用程序(Application-preferred)的选项,在手持设备软件4.0或以后版本中,它使用应用程序级确认,否则就是透明层确认。 注:3.8以及更早版本的MDS 服务不可以为BlackBerry设备特征查询BES。为了得到BlackBerry设备的特征,MDS连接服务必须在它接收到一个push请求之前接收到一个HTTP请求。 为了提供必要的请求,使用BlackBerry浏览器和一个MDS服务浏览器配置浏览一个web页面。 |
发送一个RIM push请求
为了使用RIM push将数据push到BlackBerry设备上,使用下面的格式发送一个HTTP POST请求,在这里<destination>是目的PIN或者internet消息地址,<port>是目的端口,<uri>是发送到BlackBerry的URI,<content>是字节流:
/push?DESTINATION=<destination>&PORT=<port>&REQUESTURI=<uri><headers><content>
对于RIM push请求,下列的头是有效的:
HTTP头 |
描述 |
X-RIM-Push-ID |
这个头指定了一个唯一的消息ID,它可以用来取消或检查消息的状态。典型地,以一个值组合指定URL,例如123@blackberry.com.如果忽略这个头,MDS服务生成一个唯一的消息ID. 注:Push标识符不得以@ppg.rim.com结束。 |
X-RIM-Push-NotifyURL |
这个头指定一个URL来发送一个结果通知。这个结果通知包含了指定的消息ID的X-RIM-Push-ID头,以及指定HTTP响应代码的X-RIM-Push-Status头。 |
X-RIM-Push-Reliability-Mode |
这个头指定了内容透明级(TRANSPORT),应用程序级(APPLICATION)或优先应用程序(APPLICATION-PREFERRED)的传送信任模式。 |
X-RIM-Push-Deliver-Before |
这个头指定将内容传送给BlackBerry设备的日期和时间。在这个时间之前没有传送的内容不会被传送。 |
X-RIM-Push-Priority |
这个头指定了频道push消息的优先级。允许的字符串有none(缺省),low,medium,以及high.如果优先级为low,medium或high,用户接收频道更新的通知。如果优先级为high,一个状态对话框会伴随着通知。 |
发送一个PAP push请求
为了使用PAP将数据push到BlackBerry设备,使用下面的格式发送一个HTTP POST请求:
/pap
这个请求是一个MIME多部分(multipart)的消息,它由下面的项组成:
- 一个指定控制实体(entity)XML文档。
- push内容。
例如,控制实体可能包含BlackBerry设备地址,消息ID,以及传送时间戳信息。
使用PAP DTD(Document Type Definition)指定下面的属性:
XML控制实体属性 |
描述 |
实例 |
X-Wap-Application-Id |
这个实体属性指定了RIM push的REQUEST URI HTTP同等体。 |
“/” |
push-id |
指定唯一的消息ID.另外,这个控制实体属性可以用来取消或检查消息的状态。建议你在一个值的组合里使用一个URL,例如,123@BlackBerry.com. |
123@wapforum.org |
ppg-notify-requested-to |
指定发送结果通知的URL. |
ReceivePAPNotification. |
deliver-before-timestamp |
指定日期和时间,通过它将内容传送到BlackBerry设备。这个时间之前的没有发送的内容将会丢失。 以UTC格式显示时间: YYYY-MM-DDThh:mm:ssZ 在这里:
|
2004-01-20T22:35:00Z |
deliver-after-timestamp |
指定日期和时间,在这个时间之后的内容发送到BlackBerry设备。这个时间前的内容不会被传送。 以UTC格式显示日期与时间. |
2004-01-20T21:35:00Z |
address-value |
指定将PUSH内容发送到BlackBerry设备的地址。destination是目的internet 消息地址或PIN. |
WAPPUSH=destionation%3Aport1/ TYPE=USER@blackberry.com |
Delivery-method |
指定内容,透明级,或应用程序级的传送信任模式 |
Confirmed;uncomfirmed |
参看Push Access Protocol(WAP-247-PAP-20010429-a)文档获取更多关于使用PAP编写服务器端push用用程序。参看PAP 2.0 DTD得到关于WAP Push DTD的信息。
例: PAP push request
Content-Type: multipart/related; type="application/xml"; boundary=asdlfkjiurwghasf
X-Wap-Application-Id: /
--asdlfkjiurwghasf
Content-Type: application/xml
<?xml version="1.0"?>
<!DOCTYPE pap PUBLIC "-//WAPFORUM//DTD PAP 2.0//EN" "http://www.wapforum.org/DTD/pap_2.0.dtd">
<pap>
<push-message push-id="a_push_id" ppg-notify-requested-to="http://foo.rim.net/ReceiveNotify">
<address address-value="WAPPUSH=aisha.wahl%40blackberry.com%3A7874/TYPE=USER@rim.net"/>
<quality-of-service delivery-method="unconfirmed"/>
</push-message>
</pap>
--asdlfkjiurwghasf
Content-Type: text/html
<html><body>Hello, PAP world!</body></html>
--asdlfkjiurwghasf--
发送PAP push取消请求
使用下面的头取消一个已经发送到MDS 服务的push提交。
头 |
描述 |
实例 |
cancel-message push-id |
取消前面提交的push消息。 |
<cancel-message push-id=123@wapforum.org> |
address address-value |
指定push消息提交的地址。这个标记是 必需的。 |
<address address-value=WAPPUSH=aisha.wah1%40bl1ackbe rry.com%3A7874/TYPE=UER@rim.net/> |
例:PAP push取消请求
Content-Type: application/xml
<?xml version="1.0"?>
<!DOCTYPE pap PUBLIC "-//WAPFORUM//DTD PAP 2.0//EN"
"http://www.wapforum.org/DTD/pap_2.0.dtd">
<pap>
<cancel-message push-id=“a_push_id">
<address address-value=
“WAPPUSH=aisha.wahl%40blackberry.com%3A7874/TYPE=USER@rim.net“/>
</cancel-message>
</pap>
发送一个PAP push查询请求
为了查询已经发送到MDS服务的push提交的状态,使用下面的头:
XML控制实体属性 |
描述 |
实例 |
statusquery-message push-id |
指定push消息的哪个状态是需要的。 返回的响应是下面消息状态之一:delivered,pending,undelivered,expired,rejected,timeout,cancelled,aborted或unknown.你必须在请求里包含地址属性。 |
<statusquery-message push-id=123@wapforum.org> |
address ddress-vue |
指定一个提交的push消息地址。这个标记是必需的。 |
<address address-value=”WAPPUSH=aisha.wahl%40blackberry.com%3A7874/TYPE=USER@rim>net”/> |
Content-Type: application/xml
<?xml version="1.0"?>
<!DOCTYPE pap PUBLIC "-//WAPFORUM//DTD PAP 2.0//EN"
"http://www.wapforum.org/DTD/pap_2.0.dtd">
<pap>
<statusquery-message push-id="a_push_id">
<address address-value="WAPPUSH=aisha.wahl%40blackberry.com%3A7874/TYPE=USER@rim.net"/>
</statusquery-message>
</pap>
决定BlackBerry设备是否在push覆盖范围内
为了从MDS服务接收某个特定BlackBerry设备的网络覆盖信息,指定下面的头:
XML控制实体属性 |
描述 |
实例 |
x-rim-use-coverage |
设置true来接收网络覆盖信息,push消息的状态是必要的。 注:这个头典型用来决定网络覆盖,优先于发送一个push请求。 |
<rim-push-use-coverage=”true”> |
address address-value |
指定BlackBerry设备的地址来决定网络覆盖。 |
<address address-value=”WAPPUSH=aisha.wahl%40blackberry.com %3A7874/TYPE=USER@rim>net”/> |
例:RIM网络状态查询请求
Content-Type: application/xml
<?xml version="1.0"?>
<!DOCTYPE pap PUBLIC "-//WAPFORUM//DTD PAP 2.0//EN"
"http://www.wapforum.org/DTD/pap_2.0.dtd">
<pap>
<rim-push-use-coverage="true" push-id="a_push_id" \>
</pap>
例:RIM网络状态查询响应
Content-Type: application/xml
<?xml version="1.0"?>
<!DOCTYPE pap PUBLIC "-//WAPFORUM//DTD PAP 2.0//EN"
"http://www.wapforum.org/DTD/pap_2.0.dtd">
<pap>
<x-rim-device-state="true">
<!-- a response of true means the device is in network coverage -->
<address address-value="WAPPUSH=aisha.wahl%40blackberry.com%3A7874/TYPE=USER@rim.net "/>
</rim-push-use-coverage>
</pap>
编写一个客户端push应用程序
创建一个监听线程
在独立线程上发送和接收数据,这样你不会在主事件线程上阻塞。
打开一个输入连接
调用Connector.open(String),并让http://作为协议,选择大端口号。将返回的对象转化为一个StreamConnectionNotifier。
为避免和其他程序冲突,选择一个大的端口号,端口号必须是1到65535。端口7874为BlackBerry浏览器保留。
StreamConnectionNotifier _notify = (StreamConnectionNotifier)Connector.open("http://:6234"); // open a server-side socket connection StreamConnection stream = _notify.acceptAndOpen(); // open an input stream for the connection InputStream input = stream.openInputStream(); |
关闭流连接通知
调用流连接通知上的close()方法.
_notify.close(); |
代码实例
HTTPPushDemo.java实例描述了如何编写一个应用程序监听来自服务器的进入数据。创建一个监听线程监听指定端口的图像数据,当数据到达时,然后显示它。
例: HTTPPushDemo.java
/**
* HTTPPushDemo.java
* Copyright (C) 2001-2005 Research In Motion Limited. All rights reserved.
*/
package com.rim.samples.docs.httppush;
import java.io.*;
import javax.microedition.io.*;
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;
import net.rim.device.api.i18n.*;
import net.rim.device.api.system.*;
import net.rim.device.api.util.*;
import com.rim.samples.docs.baseapp.*;
public class HTTPPushDemo extends BaseApp {
// Constants.
private static final String URL = "http://:6234";
private static final int CHUNK_SIZE = 256;
// Fields.
private ListeningThread _listeningThread;
private MainScreen _mainScreen;
private RichTextField _infoField;
private BitmapField _imageField;
public static void main(String[] args) {
HTTPPushDemo theApp = new HTTPPushDemo();
theApp.enterEventDispatcher();
}
/**
* * Create a separate listening thread so that you do not
* * block the application’s main event thread.
* */
private class ListeningThread extends Thread {
private boolean _stop = false;
private StreamConnectionNotifier _notify;
public synchronized void stop() {
_stop = true;
try {
_notify.close(); // Close the connection so thread returns.
}
catch (IOException e) {
System.err.println(e.toString());
}
catch (NullPointerException e) {
// The notify object likely failed to open, due to an IOException.
}
}
public void run() {
StreamConnection stream = null;
InputStream input = null;
try {
synchronized(this)
{
// Open the connection once or re-open after an IOException.
_notify = (StreamConnectionNotifier)Connector.open(URL);
}
while (!_stop) {
// NOTE: This method blocks until data is received.
stream = _notify.acceptAndOpen();
input = stream.openInputStream();
// Extract the data from the input stream.
DataBuffer db = new DataBuffer();
byte[] data = new byte[CHUNK_SIZE];
int chunk = 0;
while ( -1 != (chunk = input.read(data)) ) {
db.write(data, 0, chunk);
}
input.close();
data = db.getArray();
updateBitmap(data);
}
}
catch (IOException e) {
System.err.println(e.toString()); // It is likely the stream was closed.
}
}
}
// Constructor.
public HTTPPushDemo() {
_mainScreen = new MainScreen();
_mainScreen.setTitle(new LabelField(“Latest Logos”, LabelField.USE_ALL_WIDTH));
_infoField = new RichTextField();
_mainScreen.add(_infoField);
_mainScreen.add(new SeparatorField());
_imageField = new BitmapField(null, BitmapField.HCENTER|BitmapField.TOP);
_mainScreen.add(_imageField);
_mainScreen.addKeyListener(this);
_mainScreen.addTrackwheelListener(this);
_listeningThread = new ListeningThread();
_listeningThread.start();
_infoField.setText("Application is listening...");
pushScreen(_mainScreen);
}
private void updateBitmap(final byte[] data) {
Application.getApplication().invokeLater(new Runnable() {
public void run() {
// Query the user to load the received image.
String[] choices = {"OK", "CANCEL"};
if ( 0 != Dialog.ask("Do you want to display latest logo?",choices, 0) )
{
return;
}
_infoField.setText("Image received. Size:"+ data.length);
_imageField.setBitmap(Bitmap.createBitmapFromPNG(data, 0,data.length));
}
});
}
protected void onExit() {
// Stop the listening thread.
_listeningThread.stop();
try {
_listeningThread.join();
}
catch (InterruptedException e) {
System.err.println(e.toString());
}
}
}
编写一个服务器端push应用程序
任何能够建立HTTP连接的编程语言都可以用来创建一个push应用程序。下面章节使用标准Java来描述一个服务器端的push应用程序。
构造push URL
像下面格式化RIM push请求:
/push?DESTINATION=<destination>&PORT=<port>&REQUESTURI=<uri><headers><content>
参看139页的“发送一个RIM push请求”获得更多关于构造RIM push请求的URL.
如下构造PAP push请求:
/pap
参看139页的“发送一个RIM push请求”获得更多关于构造PAP push请求的URL.
连接BES
在push URL上调用openConnection(),然后将返回的对象转化为一个HttpURLConnection.一个HttpURLConnection代表了一个远程对象的连接。
HttpURLConnection conn =(HttpURLConnection)url.openConnection(); |
为HTTP POST请求设置属性
服务器端push应用程序使用一个POST请求方法。
conn.setRequestMethod("POST"); // Post to the BlackBerry Enterprise Server. |
为了接收确认,设置doInput(Boolean)为true,这描述了应用程序打算从URL连接读取数据。
conn.setDoInput(true).
为了发送数据,设置doOutput(Boolean)为true,这描述了应用程序打算发送数据到URL连接。
conn.setDoOutput(true).
写数据到服务器连接
调用getOutputStream()来访问一个输出流。写数据到输出流上,然后关闭它。
OutputStream out = conn.getOutputStream(); out.write(data); out.close(); |
读取服务器响应
调用getInputStream()来访问一个输入流。决定内容的大小,如果它是非零,打开一个数据输入流,然后读取内容。
InputStream ins = conn.getInputStream(); int contentLength = conn.getContentLength(); if (contentLength > 0) { byte[] someArray = new byte [contentLength]; DataInputStream dins = new DataInputStream(ins); dins.readFully(someArray); System.out.println(new String(someArray)); } ins.close(); |
断开连接
调用disconnect(),它描述了应用程序对服务器不会再有请求。
conn.disconnect().
代码实例
HTTPPush.java实例,它由标准Java编写,发送单个.png图像到一个BlackBerry设备上的监听客户端程序。应用程序push基于一个internet消息地址的数据。为了用模拟器测试push应用程序,定义一个internet消息地址和模拟器PIN(2100000A)之间的映射。
下面的代码使用J2SE 1..4.2编译:
例: HTTPPush.java
/*
* HttpPushServer.java
* Copyright (C) 2001-2004 Research In Motion Limited. All rights reserved.
*/
package com.rim.docs.samples.httppush;
import java.io.*;
import java.net.*;
import java.util.*;
public class HTTPPushServer {
//constants
private static final String HANDHELD_EMAIL = "scott.tooke@rim.com";
private static final String HANDHELD_PORT = "6234";
private static final String BES_HOST = "localhost";
private static final int BES_PORT = 8080;
private static final String CONTENT = "com/rim/docs/samples/httppush/logo.png";
//constructor
public HTTPPushServer() {
}
private static URL getPushURL(String HandheldEmail) {
URL _pushURL = null;
try {
if ((HandheldEmail == null) || (HandheldEmail.length() == 0)) {
HandheldEmail = HANDHELD_EMAIL;
}
_pushURL = new URL("http", BES_HOST, BES_PORT,
"/push?DESTINATION="+ HandheldEmail+"&PORT="
+HANDHELD_PORT+"&REQUESTURI=/");
}
catch (MalformedURLException e) {
System.err.println(e.toString());
}
return _pushURL;
}
public static void postData(byte[] data) {
try {
URL url = getPushURL(HANDHELD_EMAIL);
System.out.println(“Sending to” + url.toString());
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setDoInput(true); //for receiving the confirmation
conn.setDoOutput(true); //for sending the data
conn.setRequestMethod(“POST”); //post the data to the BES
OutputStream out = conn.getOutputStream();
out.write(data); //write the data
out.close();
InputStream ins = conn.getInputStream();
int contentLength = conn.getContentLength();
System.out.println("Content length:"+ contentLength);
if (contentLength > 0) {
byte[] someArray = new byte [contentLength];
DataInputStream dins = new DataInputStream(ins);
dins.readFully(someArray);
System.out.println(new String(someArray));
}
ins.close();
conn.disconnect();
}
catch (IOException e) {
System.err.println(e);
}
}
public static void main (String args[]) {
try {
File f = new File(CONTENT);
if ( f == null ) {
throw new RuntimeException("Unable to Open File");
}
FileInputStream fi = new FileInputStream(f);
if ( null == fi ) {
throw new RuntimeException("Unable to open file");
}
int size = fi.available();
byte[] imageData = new byte[size];
int bytesRead = fi.read(imageData);
fi.close();
postData(imageData);
}
catch (IOException e) {
System.err.println(e.toString());
}
}
}
Push应用程序疑难解答
注:下面的情况适用于Microsoft Exchange BES.
Push应用程序对基于internet消息地址的BlackBerry设备进行标识,当用户换到另外一台不同的BlackBerry设备时,如果用户停止接收一个push应用程序的数据,
可能意味着用户internet消息地址和BlackBerry设备PIN之间的映射已经过期了。验证BES是否正确运行。
Microsoft Exchange BES的MDS服务特征使用了一个数据库一致工具(Database Consistency tool),dbconsistency.exe,进行维护internet消息地址和BlackBerry设备PIN之间的映射。管理员可以配置这个工具运行的频率,并且也可以手动运行它。为获得更多信息,参看Microsoft Exchange BES维护指南。
- Last Updated:2007年1月12日
- Last Updated:2006年4月28日 created