作者: 王恒进
1. BlackBerry 上连接 HTTP 或 Socket 的五种方式
BlackBerry JDE 的 API 提供了五种方式来进行 HTTP 或者 socket 连接,包括:
1.1. BlackBerry Enterprise Server(BES)方式
这种方式通过使用 BES 的 BlackBerry MDS Services 来进行网络连接,BlackBerry
MDS Services 负责处理所有的浏览器请求或者连接请求、并负责数据加密,这是黑莓 手机的默认连接方式,如下:
例 1: (HttpConnection) Connector.open("http://www.testserver.com");
以上代码会自动将 BlackBerry MDS Services 作为它的默认连接路径。实际开发中, 如果要确保应用程序使用 uses BlackBerry MDS Services 作为它的连接路径,需要在 URL 最后加上参数“deviceside=false”,这也是我们推荐的方式,如下:
例 2: (HttpConnection)Connector.open(“http://www.testserver.com;deviceside=false”);
1.2. BlackBerry Internet Servie(BIS)方式
这种方式是为第三方提供的连接接口,它对数据不进行加密,用户可以通过使用
HTTPS 和 SSL 来进行安全的连接。注:目前仅对加入了 BlackBerry Alliance Program
的第三方开放合作伙伴开放,详情参考:
http://na.blackberry.com/eng/partners/alliance.jsp
1.3. Direct TCP 方式
这种方式允许在没有使用 BlackBerry MDS 的黑莓手机上直接 TCP 连接。为了能启用 direct TCP 方式,用户需要在手机的“选项-高级选项-TCP/IP”中设置 APN,以及相 应的用户名和密码。
注 1:运行在 iDEN 网络上的黑莓手机(包括 6510、7510、7520 和 7100i),如果不指
定 deviceside 参数(如例 1),默认的连接是 direct TCP;非运行在 iDEN 网络上的其 他黑莓手机,如果不指定 deviceside 参数,默认的连接是 BlackBerry MDS。
注 2:如果连接时 BlackBerry MDS 不存在,黑莓手机也会自动采用 direct TCP 方 式。
因此,如果要将 direct TCP 作为黑莓手机的默认连接方式,我们建议在 URL 中加入
“deviceside=true”参数,如下:
例 3-1: (SocketConnection)Connector.open("socket://testserver:600;deviceside=true");
此外,如果应用程序不希使用用户自己设置的 APN,也可以在 URL 中指定自己
APN,以下是指定通过中国移动 CMNET 进行直接 TCP 连接的例子:
例 3-2: (SocketConnection)Connector.open("socket://testserver:600;deviceside=true;APN=cmnet");
1.4. Wi-Fi
如果需要在 Wi-Fi 上创建网络连接,不需要在应用程序中考虑特别的底层逻辑,可以在
URL 中加入参数“interface=wifi”就能实现 Wi-Fi 连接,如下:
例 4:(StreamConnection)Connector.open(“socket:// testserver:600;interface=wifi”);
1.5. WAP
1.5.1. WAP 1.x
并不是所有的移动运营商都支持通过 WAP 网关进行连接的,所以如果要创建 WAP 连 接,开发者需要和移动运营商联系,获取是否提供这种支持、并且获取他们的 WAP 网 管参数。
以下是一个基于中国移动 CMWAP 的 WAP 网关进行 HTTP 连接的例子:
例 5:
(HttpConnection)Connector.open("http://wap.google.com;WAPGatewayIP=10.0.0.172;WAPGatewayAPN=cm wap");
注:其中 WAPGatewayIP 和 WAPGatewayAPN 这两项参数必须指定,参数之间用
“;”隔开,以下是所有的 WAP 参数列表,实际开发时请根据运营商提供的信息决定 哪些参数需要设置。
Parameter |
Description |
WapGatewayIP |
IP address of the gateway. |
WapGatewayAPN |
APN for General Packet Radio Service (GPRS) networks only. For testing purposes, you can use rim.net.gprs |
WapGatewayPort |
Gateway port value. If port 9203 is specified, Wireless Transport Layer Security (WTLS) is used unless WapEnableWTLS=false is specified. |
WapSourceIP |
IP address of the source. |
WapSourcePort |
Source port value. |
TunnelAuthUsername |
User name for APN session, when Password Authentication Protocol (PAP) or Challenge Handshake Application Protocol (CHAP) authentication is used. |
TunnelAuthPassword |
Password for APN session, when PAP or CHAP authentication is used. |
WapEnableWTLS |
Explicitly turns on or turns off WTLS. If this parameter is not specified, WTLS is used by default for connections to port 9203. |
1.5.2. WAP 2.0
如果要连接 WAP2.0 的网关,需要在连接时指定 service record、以及 UID。以下代码说明
了 WAP2.0 的连接时如何工作的:
例 5:
ServiceBook sb = ServiceBook.getSB();
ServiceRecord[] records = sb.findRecordsByCid("WPTCP"); String uid = null;
for(int i=0; i < records.length; i++)
{
//Search through all service records to find the
//valid non-Wi-Fi and non-MMS
//WAP 2.0 Gateway Service Record.
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)
{
//open a WAP 2 connection
Connector.open(_url + ";ConnectionUID=" + uid);
}
else
{
//Consider another transport or alternative action.
}
在这里,基本流程是先从 Service Book 中获得 Service Record,然后再从中获得 UID,
后文会对此进行更具体的分析。
2. BlackBerry 上中国移动的 WAP 网关连接技巧
首先解释一下,中国移动提供 CMNET 和 CMWAP 两个 APN,本质上,这两个 APN 其实本应没 有区别,但是运营商从商业角度出发,从以下两方面来定位这二者:
1,采用不同的计费方式,不包月的情况下,CMNET 有可能收费更高
2,网络接入的授权不同,CMNET 能获得完全网络访问,就如 PC 直接上网一样,而 CMWAP
只能限制在 WAP(基本上可以等同于 HTTP,也就是只能访问 web service)
那么接下来,看黑莓上这两个 APN 有什么区别呢,显然,连 CMNET 能干更多的事,比如:
1, 长连接
2, 对端口的 socket 连接
而 CMWAP 由于计费便宜,因此用户可能的话,往往倾向于用这个 APN。不少应用为了达到 更好的体验,也将自己的应用首选或者甚至绑定 CMWAP 方式。
最后,来罗列一下中国移动的 WAP 情况下,有哪些连接方式。
WAP 1.x(国内的开发者,特别是从 J2ME 转过来的开发者,往往倾向于此)
1, 长连接或者 socket 连接 建议用:
(SocketConnection)Connector.open("socket://testserver:600;deviceside=true;APN=
cmnet");
如果在选项-高级-TCP/IP 中填上了 CMNET,用下面这种方式也能成功
(SocketConnection)Connector.open("socket://testserver:600;deviceside=true");
2, 用 CMWAP 访问 WAP 网站
(HttpConnection)Connector.open(http://wap.google.com;WAPGatewayIP=10.0.0.172;W APGatewayAPN=cmwap);
这里,注意不要写“ WapGatewayPort=80 ” (似乎很多人都容易犯这个错误),原因是系
统会根据关键字 http 自动识别应该用哪个网关端口,实际上在黑莓上这个值也不是 80, 而是 9201
3, 用 CMNET 访问 WAP 网站
(HttpConnection)Connector.open(http://wap.google.com;WAPGatewayIP=10.0.0.172;W APGatewayAPN=cmnet );
或用 CMNET 访问任意网站
(HttpConnection)Connector.open( http://www.google.com;deviceside=true;APN=cmne
t )
WAP 2.0 方式(这是我强烈推荐的)
可能大家也发现了,用 WAP1.x 非常之不灵活,代码也往往会变得很复杂很乱。 而其实黑莓上有更好更简洁的办法,就是通过 WAP 2.0 来访问,在上一节里面写了一个例
子,来说明如何通过 WAP2.0 接入网络。在这里解释一下:
打开选项-高级选项-服务预订中,能看到一项叫做 WAP2 Transport[WPTCP],点进去可以 看到以下信息
名称: WAP2 Transport
UID: WAP2 trans
CID: WPTCP
这些信息从哪里来的呢,这是黑莓上面的一个概念叫做 service book,每一项 service
book 其实就是一套对手机服务进行描述的配置文件,在这里的项目 RIM 和中国移动合作的 时候准备好的配置项,由黑莓手机上市前预加载、或者手机启动时由运营商将配置发送到 手机上。
就这一项而言,UID 是描述服务的关键词,CID 是描述服务走的是什么网络通道。UID 为
WAP2 trans 就表示这是 WAP2.0 的 service book,而且基本上这个关键词是不会变的。这 样就好理解了上一节中对 WAP2.0 那一段的代了。
其实有更简单的办法,用这个 URL 就直接达到效果了:
http://www.google.com;DeviceSide=true;ConnectionUID=WAP2 trans
3. BlackBerry 上中国电信的 WAP 网关连接分析
中国电信已经发布了 BlackBerry 9530,不同的是,这一款是烧号的,因此可以想象开发的时候还 是会遇到一些不同的。
1, C 网和 G 网的差别。中国电信的 CDMA,在 WAP 连接时没有 GPRS 上 APN 的概念。可是可能 有人要说,那么电信的“CTNET”和“CTWAP”是怎么一回事呢。其实这涉及到 CDMA 的底层, CDMA 是建立在 PPP 之上的,也就是手机拨号上网,那么可能经历过 modem 拨号年代的童鞋可能 还记得拨号的时候需要一个号码,还需要用户名和密码。那好,这里的 ctnet 和 ctwap 其实就是 这里的用户名。中国电信的服务端(一个叫做 PDSN 的东东)会对用户不同的用户名做配置,
ctwap 被授予的权限显然被限制在 WAP,而 CTNET 则能获得完全的 TCP/IP 的权限。通过这一手 段,达到了中国移动 cmnet 和 cmwap 类似的效果。为什么这么做呢,我觉得应该是商业模式吧, 让移动转来的用户能更快适应。在国外,通常没有这么复杂。
2,那么 9530 上如何上网呢。如果你打开选项-高级-TCP/IP,那么你会发现 APN 是不可改的,也 就是说你没法配置 CTNET 或者 CTWAP 之类的东东。那么可以猜想,RIM 应该和中国电信有一种 协议,9530 会自动的通过某种帐号上网。通过调试,我发现肯定不会是 CTNET,很可能是类似 CTWAP 的帐号。
最后,好了,现在电信上支持的网络连接方式总结如下:
1,如果你希望通过 WAP 上网,采用 WAP2.0 的方式
2,如果你希望能访问 TCP,只能采用 BES 的方式(虽然国内黑莓用户大多都是纯水货,没有开通 BES,但是在9530上没有这个问题,买到烧号的9530的,应该都通过中国电信开通了企业 服务)
4. BlackBerry 5.0 新提供的 Network API:自动选择网关的终
极解决方案
从前问来看,要想在 BlackBerry 上实现自动选择 WAP 来连接网络,似乎很复杂。但其实在
BlackBerry 5.0 上,这一切都已成为往事。随着 BlackBerry 5.0 的发布,其中包含了不少非常有用的
API,其中 Network API 就是用来提高网络编程效率的。Network API 包括以下类和接口:
包名:net.rim.device.api.io.transport
接口:
ConnectionAttemptListener This interface prescribes methods for a class that listens for connection attempts performed by a ConnectionFactory object.
类:
ConnectionDescriptor
This class stores information about a Connection opened by using a
ConnectionFactory.
ConnectionFactory This class enables you to create HTTP, HTTPS, socket, TLS, and SSL
connections over supported transports.
TransportDescriptor This class encapsulates information related to a specific transport instance.
TransportInfo This class provides methods that provide information about the transport types available on a BlackBerry device.
其中的 ConnectionFactory 可以帮开发人员自动选择多种连接方式,也可以设置重试次数,自动支
持 HTTPS 和 SSL/TLS。以下是一个例子:
根据自动搜索,第一条发现的通道(由系统来决定)来建立 HTTP 连接:
// Create ConnectionFactory
ConnectionFactory factory = new ConnectionFactory();
// use the factory to get a connection
ConnectionDescriptor conDescriptor =
factory.getConnection("http://www.blackberry.com");//不设任何参数
if ( conDescriptor != null ) {
// connection succeeded int transportUsed =
conDescriptor.getTransportDescriptor().getTransportType();
// using the connection
HttpConnection httpCon = (HttpConnection) conDescriptor.getConnection();
...
}
设定一个连接方式列表,按照预设定的优先顺序来建立 HTTP 连接:
// make a list of transport types ordered according to preference (they will be tried in succession)
int[] preferredTransportTypes = {TransportInfo.TRANSPORT_MDS, TransportInfo.TRANSPORT_WAP2};//连接方式列表,第一优先:BES 方式,第二优先:WAP2 方式
// Create ConnectionFactory
ConnectionFactory factory = new ConnectionFactory();
// Configure the factory
factory.setPreferredTransportTypes( preferredTransportTypes );
// use the factory to get a connection
ConnectionDescriptor conDescriptor =
factory.getConnection("http://www.blackberry.com");
if ( conDescriptor != null ) {
// connection suceeded
int transportUsed =
conDescriptor.getTransportDescriptor().getTransportType();
// using the connection
HttpConnection httpCon = (HttpConnection) conDescriptor.getConnection();
...
}
设定一项特定的通道类型来建立 HTTP 连接:
// Create ConnectionFactory
ConnectionFactory factory = new ConnectionFactory();
// use the factory to get a connection
ConnectionDescriptor conDescriptor = factory.getConnection("http://www.blackberry.com", TransportInfo.TRANSPORT_WAP2, null);
if ( conDescriptor != null ) {
// connection over WAP2 succeeded
// using the connection
HttpConnection httpCon = (HttpConnection) conDescriptor.getConnection();
...
}
设定一条具体的通道来建立 HTTP 连接:
// Create ConnectionFactory
ConnectionFactory factory = new ConnectionFactory();
// use the factory to get a connection
ConnectionDescriptor conDescriptor = factory.getConnection("http://www.blackberry.com", TransportInfo.TRANSPORT_MDS, "S109234");
if ( conDescriptor != null ) {
// connection to specific BES succeeded
// using the connection
HttpConnection httpCon = (HttpConnection) conDescriptor.getConnection();
...
}
5. 网络设置解决方案举例 以上介绍了各种网络连接的代码实现,但是国内黑莓开发的初学者在设计的时候,最头疼的一个 问题就是网络设置。
BlackBerry 与其他手机不同的是,多了好几种联网方式,如何把这些整合在你的设置里面,让 用户自己来选择希望的联网通道。国外很多成熟的应用这方面早就已经很完美的解决了。
这里拿国外某股票软件做例子,网络连接上这个应用是基于 Socket 的,而非 HTTP。 我们首先来看它的设置首页,提供四种方式:BIS,BES,TCP,WiFi
补充介绍一下,这个软件底层是基于 TCP/IP 的,否则的话,如果是基于 WAP 的软件,通常应该
再加上 WAP 的 APN 的设置。
另外,根据实际情况,有的软件可能需要加上“自动选择网络”,并将其设为默认选项。
以下这个例子不很完善,因为没有包括 WAP 和 WAP2 连接,但在前文已经做了很充分的 讨论,不过这段代码还是满足绝大部分情况下的需求,而且同时支持 GET 和 POST。
public static final int CONNECTION_DEFAULT = 0;
public static final int CONNECTION_BIS = 1; public static final int CONNECTION_BES = 2; public static final int CONNECTION_TCPIP = 3; public static final int CONNECTION_WIFI = 4;
public boolean get(String url) {
service(url, null);
}
public boolean post(String url, URLEncodedPostData postdata) {
service(url, postdata);
}
private boolean service(String url, URLEncodedPostData postdata) { HttpConnection con = null;
InputStream in = null;
try {
if (CoverageInfo.isOutOfCoverage())
return false;
byte[] data = null;
if (postdata != null) {
data = postdata.getBytes();
}
// If WLAN is active, try wlan
if ((RadioInfo.getActiveWAFs() & RadioInfo.WAF_WLAN) != 0) {
try {
if (CoverageInfo.isCoverageSufficient( CoverageInfo.COVERAGE_CARRIER, RadioInfo.WAF_WLAN, false))
{
con = HttpUtils.makeHttpConnection(
url, new HttpHeaders(), data, CONNECTION_WIFI);
con.getResponseCode();
}
} catch (Exception ex) { Log.error("UP.R.WF: " + ex); con = null;
}
}
// Otherwise try the preferred connection type. if (con == null) {
try {
con = HttpUtils.makeHttpConnection(
url, new HttpHeaders(), data, IOPreferences.getPreferredConnectionType());
con.getResponseCode();
} catch (Exception ex) { Log.error("UP.R1: " + ex); con = null;
}
}
if (con == null) {
// If preferred type doesnot work, find something else. int ctype = getCoverageBasedConnectionType();
if (ctype >= 0) {
try {
con = HttpUtils.makeHttpConnection(url, new HttpHeaders(), data, ctype);
con.getResponseCode();
} catch (Exception ex) { Log.error("UP.R2: " + ex); con = null;
}
}
}
int respcode = con.getResponseCode();
// You can handle HTTP_MOVED_* here if you want to
// We don't because we connect to our own servers
if (respcode == HttpConnection.HTTP_OK) {
in = con.openInputStream();
// Handle incoming data handleData(con, in);
return true;
}
} catch (Exception e) { Log.error("UP.R() : " + e);
} finally {
close(con, in);
}
return false;
}
protected abstract void handleData(HttpConnection con, InputStream in);
private static void close(HttpConnection con, InputStream in) {
if (in != null) {
try {
in.close();
} catch (IOException e2) {
}
}
if (con != null) { try { con.close();
} catch (IOException e) {
}
}
}
public static int getCoverageBasedConnectionType() {
if (CoverageInfo.isCoverageSufficient(CoverageInfoProxy.COVERAGE_CARRIER, RadioInfo.WAF_WLAN))
{
return IOConstants.CONNECTION_WIFI;
}
if (CoverageInfo.isCoverageSufficient(CoverageInfo.COVERAGE_BIS_B)) {
return IOConstants.CONNECTION_BIS;
}
if (CoverageInfo.isCoverageSufficient(CoverageInfo.COVERAGE_MDS)) {
return IOConstants.CONNECTION_BES;
}
return IOConstants.CONNECTION_DEFAULT;
}
Finally the method to make the http connection class HttpUtils {
public static final int CONNECTION_DEFAULT = 0;
public static final int CONNECTION_BIS = 1; public static final int CONNECTION_BES = 2; public static final int CONNECTION_TCPIP = 3; public static final int CONNECTION_WIFI = 4;
/**
* This method opens a HTTP connection to the given url. The method used is
* GET or POST depending on whether postData is null or not. Only the
* provided connType is used. For example, if the connType is
* CONNECTION_BES, the connection is tried using the BES only.
* The only time provided connection type is not used is when the URL
* contains ";deviceside=".
*
* @param url
* The url to connect to.
* @param requestHeaders
* The headers in the request. May be null or empty.
* @param postData
* Data to be posted to the server. If null, the GET method used
* for the http connection.
* @param connType
* The type of transport (BES / BIS / WIFI / Default) to be used
* for opening connection.
* @return Opened HttpConnection object or null if some error occurs.
*/
public static HttpConnection makeHttpConnection(String url, HttpHeaders requestHeaders, byte[] postData, int connType) {
HttpConnection conn = null; OutputStream out = null;
if (StringUtilities.startsWithIgnoreCase(url, "www.")) {
url = "http://" + url;
}
try {
if (url.indexOf(";deviceside=") == -1) {
switch (connType) {
case CONNECTION_BES:
url = url + ";deviceside=false";
break;
case CONNECTION_BIS:
url = url + ";XXXXXXXXXXXXXXXX";
break;
case CONNECTION_TCPIP:
url = url + ";deviceside=true";
break;
case CONNECTION_WIFI:
url = url + ";interface=wifi";
}
}
conn = (HttpConnection) Connector.open(url);
if (requestHeaders != null) {
String referer = requestHeaders.getPropertyValue("referer");
boolean sendReferrer = true;
if (referer != null && StringUtilities.startsWithIgnoreCase(referer, "https:") &&
!StringUtilities.startsWithIgnoreCase(url, "https:")) {
sendReferrer = false;
}
int size = requestHeaders.size();
for (int i = 0; i < size;) {
String header = requestHeaders.getPropertyKey(i);
// remove header if needed
if (!sendReferrer && header.equals("referer")) {
requestHeaders.removeProperty(i);
--size;
continue;
}
String value = requestHeaders.getPropertyValue(i++);
if (value != null) {
conn.setRequestProperty(header, value);
}
}
}
if (postData == null) { conn.setRequestMethod(HttpConnection.GET); conn.setRequestProperty("User-Agent",
"Profile/MIDP-2.0 Configuration/CLDC-1.0");
} else { conn.setRequestMethod(HttpConnection.POST); conn.setRequestProperty(
H ttpProtocolConstants.HEADER_CONTENT_LENGTH,
String.valueOf(postData.length));
conn.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
conn.setRequestProperty("User-Agent",
"Profile/MIDP-2.0 Configuration/CLDC-1.0");
out = conn.openOutputStream();
out.write(postData);
out.flush();
}
} catch (IOException e1) { Log.error("UTIL.HTC " + e1);
close(conn, null); // Close the connection
conn = null;
} finally {
close(null, out); // Close the output, but keep connection open
}
return conn;
}
private static void close(HttpConnection con, OutputStream out) {
if (out != null) {
try {
out.close();
} catch (IOException e2) {
}
}
if (con != null) { try { con.close();
} catch (IOException e) {
}
}
}
}
BlackBerry SDK下载