如何在OPhone平台编写网络应用
网络编程, 2009-05-15 10:11:22
标签 : 网络 OPhone 王卫
1. 概要
本文主要介绍在OPhone平台上开发网络应用的一些常用接口,同时指出其在Android 平台的异同之处。由于OPhone平台支持多 PDP 复用,本文还介绍了相关的数据连接管理接口。
2. OPhone平台上可以使用的几种网络接口
和Android平台一样,在OPhone平台上开发的绝大部分网络应用都是基于Java编程接口的,这意味着开发者至少可以使用以下4种接口来编写网络应用:
这部分常用的接口主要包括 java.net.*,主要提供了访问 HTTP 服务的基本功能,使用这些接口时基本只能通过同步的方式获得网络侧数据。
使用这部分接口的基本操作主要包括:
- 创建 URL 以及 URLConnection / HttpURLConnection 对象
- 设置连接参数
- 连接到服务器
- 向服务器写数据
- 从服务器读取数据
实例代码如下:
- import java.io.InputStream;
- import java.io.OutputStream;
- import java.net.URL;
- import java.net.URLConnection;
- import java.net.HttpURLConnection;
- try {
- // 创建一个 URL 对象
- URL url = new URL(your_url);
- // 创建一个 URL 连接,如果有代理的话可以指定一个代理。
- URLConnection connection = url.openConnection(Proxy_yours);
- // 对于 HTTP 连接可以直接转换成 HttpURLConnection,
- // 这样就可以使用一些 HTTP 连接特定的方法,如 setRequestMethod() 等
- //HttpURLConnection connection =
- (HttpURLConnection)url.openConnection(Proxy_yours);
- // 在开始和服务器连接之前,可能需要设置一些网络参数
- connection.setConnectTimeout(10000);
- connection.addRequestProperty("User-Agent",
- "Mozilla/5.0 (compatible; MSIE 6.0;)");
- // 连接到服务器
- connection.connect();
- // 往服务器写数据,数据会暂时被放到内存缓存区中
- // 如果仅是一个简单的 HTTP GET,这一部分则可以省略
- OutputStream outStream = connection.getOutputStream();
- ObjectOutputStream objOutput = new ObjectOutputStream(outStream);
- objOutput.writeObject(new String("this is a string..."));
- objOutput.flush();
- // 向服务器发送数据并获取应答
- InputStream in = connection.getInputStream();
- // 处理数据
- ...
- } catch (Exception e) {
- // 网络读写操作往往会产生一些异常,所以在具体编写网络应用时
- // 最好捕捉每一个具体以采取相应措施
- }
import java.io.InputStream; import java.io.OutputStream; import java.net.URL; import java.net.URLConnection; import java.net.HttpURLConnection; try { // 创建一个 URL 对象 URL url = new URL(your_url); // 创建一个 URL 连接,如果有代理的话可以指定一个代理。 URLConnection connection = url.openConnection(Proxy_yours); // 对于 HTTP 连接可以直接转换成 HttpURLConnection, // 这样就可以使用一些 HTTP 连接特定的方法,如 setRequestMethod() 等 //HttpURLConnection connection = (HttpURLConnection)url.openConnection(Proxy_yours); // 在开始和服务器连接之前,可能需要设置一些网络参数 connection.setConnectTimeout(10000); connection.addRequestProperty("User-Agent", "Mozilla/5.0 (compatible; MSIE 6.0;)"); // 连接到服务器 connection.connect(); // 往服务器写数据,数据会暂时被放到内存缓存区中 // 如果仅是一个简单的 HTTP GET,这一部分则可以省略 OutputStream outStream = connection.getOutputStream(); ObjectOutputStream objOutput = new ObjectOutputStream(outStream); objOutput.writeObject(new String("this is a string...")); objOutput.flush(); // 向服务器发送数据并获取应答 InputStream in = connection.getInputStream(); // 处理数据 ... } catch (Exception e) { // 网络读写操作往往会产生一些异常,所以在具体编写网络应用时 // 最好捕捉每一个具体以采取相应措施 }
Apache HttpClient 是一个开源项目,弥补了 java.net.* 灵活性不足的缺点,为客户端的HTTP编程提供高效、最新、功能丰富的工具包支持。该项目最早源于2001年开始的 Commons HttpClient ,是 Apache Jakarta Commons 下的子项目,目前版本为3.x。
HttpComponents 则是从2005年开始的脱胎于 Commons HttpClient ,旨在用来替换后者的一个项目,其中也包括了 HttpClient 组件,且大部分接口与 Commons HttpClient 相同。按照官方网站(http://hc.apache.org/)的说法,在 Apache HttpClient 4 稳定之后HttpComponents 将可以完全替换 Commons HttpClient。
Android 平台引入了 Apache HttpClient 的同时还提供了对它的一些封装和扩展,例如设置缺省的HTTP超时和缓存大小等。早期的 Android 曾同时包括 Commons HttpClient (org.apache.commons.httpclient.*) 和 HttpComponents (org.apache.http.client.* ),不过当前版本 (1.5) 中开发者只能使用后者,也就是说类似以下的一些类:
- org.apache.commons.httpclient.methods.GetMethod
- org.apache.commons.httpclient.methods.PostMethod
org.apache.commons.httpclient.methods.GetMethod org.apache.commons.httpclient.methods.PostMethod
需要改成对应的
- org.apache.http.client.methods.HttpGet
- org.apache.http.client.methods.HttpPost
org.apache.http.client.methods.HttpGet org.apache.http.client.methods.HttpPost
使用这部分接口的基本操作与 java.net.* 基本类似,主要包括:
- 创建 HttpClient 以及 GetMethod / PostMethod, HttpRequest 等对象
- 设置连接参数
- 执行 HTTP 操作
- 处理服务器返回结果
- import org.apache.http.HttpEntity;
- import org.apache.http.HttpHost;
- import org.apache.http.HttpResponse;
- import org.apache.http.auth.AuthScope;
- import org.apache.http.auth.UsernamePasswordCredentials;
- import org.apache.http.client.methods.HttpGet;
- import org.apache.http.conn.params.ConnRoutePNames;
- import org.apache.http.params. HttpConnectionParams;
- import org.apache.http.client.params. HttpClientParams;
- try {
- // 创建 HttpParams 以用来设置 HTTP 参数(这一部分不是必需的)
- HttpParams params = new BasicHttpParams();
- // 设置连接超时和 Socket 超时,以及 Socket 缓存大小
- HttpConnectionParams.setConnectionTimeout(params, 20 * 1000);
- HttpConnectionParams.setSoTimeout(params, 20 * 1000);
- HttpConnectionParams.setSocketBufferSize(params, 8192);
- // 设置重定向,缺省为 true
- HttpClientParams.setRedirecting(params, true);
- // 设置 user agent
- HttpProtocolParams.setUserAgent(params, userAgent);
- // 创建一个 HttpClient 实例
- // 注意 HttpClient httpClient = new HttpClient(); 是Commons HttpClient
- // 中的用法,在 Android 1.5 中我们需要使用 Apache 的缺省实现 DefaultHttpClient
- HttpClient httpClient = new DefaultHttpClient(params);
- // 创建 HttpGet 方法,该方法会自动处理 URL 地址的重定向
- HttpGet httpGet = new HttpGet ("http://www.test_test.com/");
- HttpResponse response = client.execute(httpGet);
- if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
- // 错误处理,例如可以在该请求正常结束前将其中断
- httpGet.abort();
- }
- // 读取更多信息
- Header[] headers = response.getHeaders();
- HttpEntity entity = response.getEntity();
- Header header = response.getFirstHeader("Content-Type");
- } catch (Exception ee) {
- //
- } finally {
- // 释放连接
- client.getConnectionManager().shutdown();
- }
import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.methods.HttpGet; import org.apache.http.conn.params.ConnRoutePNames; import org.apache.http.params. HttpConnectionParams; import org.apache.http.client.params. HttpClientParams; try { // 创建 HttpParams 以用来设置 HTTP 参数(这一部分不是必需的) HttpParams params = new BasicHttpParams(); // 设置连接超时和 Socket 超时,以及 Socket 缓存大小 HttpConnectionParams.setConnectionTimeout(params, 20 * 1000); HttpConnectionParams.setSoTimeout(params, 20 * 1000); HttpConnectionParams.setSocketBufferSize(params, 8192); // 设置重定向,缺省为 true HttpClientParams.setRedirecting(params, true); // 设置 user agent HttpProtocolParams.setUserAgent(params, userAgent); // 创建一个 HttpClient 实例 // 注意 HttpClient httpClient = new HttpClient(); 是Commons HttpClient // 中的用法,在 Android 1.5 中我们需要使用 Apache 的缺省实现 DefaultHttpClient HttpClient httpClient = new DefaultHttpClient(params); // 创建 HttpGet 方法,该方法会自动处理 URL 地址的重定向 HttpGet httpGet = new HttpGet ("http://www.test_test.com/"); HttpResponse response = client.execute(httpGet); if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { // 错误处理,例如可以在该请求正常结束前将其中断 httpGet.abort(); } // 读取更多信息 Header[] headers = response.getHeaders(); HttpEntity entity = response.getEntity(); Header header = response.getFirstHeader("Content-Type"); } catch (Exception ee) { // } finally { // 释放连接 client.getConnectionManager().shutdown(); }
以下例子以 HttpGet 方式通过代理访问 HTTPS 网站:
- try {
- HttpClient httpClient = new HttpClient();
- // 设置认证的数据
- httpClient.getCredentialsProvider().setCredentials(
- new AuthScope("your_auth_host", 80, "your_realm"),
- new UsernamePasswordCredentials("username", "password"));
- // 设置服务器地址,端口,访问协议
- HttpHost targetHost = new HttpHost("www.test_test.com", 443, "https");
- // 设置代理
- HttpHost proxy = new HttpHost("192.168.1.1", 80);
- httpClient.getParams().setParameter
- (ConnRoutePNames.DEFAULT_PROXY, proxy);
- // 创建一个 HttpGet 实例
- HttpGet httpGet = new HttpGet("/a/b/c");
- // 连接服务器并获取应答数据
- HttpResponse response = httpClient.execute(targetHost, httpGet);
- // 读取应答数据
- int statusCode = response.getStatusLine().getStatusCode();
- Header[] headers = response.getHeaders();
- HttpEntity entity = response.getEntity();
- } catch (Exception ee) {
- //
- }
try { HttpClient httpClient = new HttpClient(); // 设置认证的数据 httpClient.getCredentialsProvider().setCredentials( new AuthScope("your_auth_host", 80, "your_realm"), new UsernamePasswordCredentials("username", "password")); // 设置服务器地址,端口,访问协议 HttpHost targetHost = new HttpHost("www.test_test.com", 443, "https"); // 设置代理 HttpHost proxy = new HttpHost("192.168.1.1", 80); httpClient.getParams().setParameter (ConnRoutePNames.DEFAULT_PROXY, proxy); // 创建一个 HttpGet 实例 HttpGet httpGet = new HttpGet("/a/b/c"); // 连接服务器并获取应答数据 HttpResponse response = httpClient.execute(targetHost, httpGet); // 读取应答数据 int statusCode = response.getStatusLine().getStatusCode(); Header[] headers = response.getHeaders(); HttpEntity entity = response.getEntity(); } catch (Exception ee) { // }
以上例子中都是通过同步的方式来读写服务器,某些情况下出于效率上的考虑,应用可能需要异步的发送和接收数据。HttpClient 同样也为我们提供了相关的接口来满足这一需求,不过在这种情况下可能需要开发者自己根据具体的需要,编写一些额外的代码来调用这些接口,具体可参考 android.net.http 中的相关实现。
由于 HttpClient 工具包提供了相当丰富的接口和不同层次的封装,开发者可以用于实现 HTTP 实现的方法还有很多,并不一定限于以上介绍的两种方式。关于 HttpClient 的更多信息还可以参考其主页。
2.3. Android接口
android.net.* 实际上是通过对 Apache 的 HttpClient 的封装来实现的一个 HTTP 编程接口,同时还提供了 HTTP 请求队列管理, 以及 HTTP 连接池管理,以提高并发请求情况下(如转载网页时)的处理效率,除此之外还有网络状态监视等接口。
以下是一个通过 AndroidHttpClient 访问服务器的最简例子:
- import import android.net.http.AndroidHttpClient;
- try {
- AndroidHttpClient client = AndroidHttpClient.newInstance(“your_user_agent”);
- // 创建 HttpGet 方法,该方法会自动处理 URL 地址的重定向
- HttpGet httpGet = new HttpGet ("http://www.test_test.com/");
- HttpResponse response = client.execute(httpGet);
- if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
- // 错误处理
- }
- // 关闭连接
- client.close();
- } catch (Exception ee) {
- //
- }
import import android.net.http.AndroidHttpClient; try { AndroidHttpClient client = AndroidHttpClient.newInstance(“your_user_agent”); // 创建 HttpGet 方法,该方法会自动处理 URL 地址的重定向 HttpGet httpGet = new HttpGet ("http://www.test_test.com/"); HttpResponse response = client.execute(httpGet); if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { // 错误处理 } // 关闭连接 client.close(); } catch (Exception ee) { // }
另外当我们的应用需要同时从不同的主机获取数目不等的数据,并且仅关心数据的完整性而不关心其先后顺序时,也可以使用这部分的接口。典型用例就是 android.webkit 在转载网页和下载网页资源时,具体可参考 android.webkit.* 中的相关实现。
虽然 HttpClient 提供了相当丰富的功能接口,但是在某些情况下开发者可能还是需要通过 Linux 系统调用直接操作 Socket,例如通过 setsockopt 来设置一些 Socket属性等,这种情况下仍可以为应用写一个 C/C++ 库文件,并提供相关的 JNI 接口,这样应用仍可以和网络侧进行通信。具体可参考 Android 平台相关实现,这里不再赘述。
3. 打开数据连接 - OPhone新增接口 DataConnection 介绍
Android 平台默认仅支持一个PDP,即某个时刻只能打开一个数据连接。比如典型的一个用例是,当用户有 WiFi 接入的时候,可以优先使用 WiFi,否则使用移动数据连接(比如GPRS),当有 MMS 等特殊应用需要时,会自动切换到 MMS 专用的连接(很多运营商都会提供专门的 MMS 连接设置,在实际的 2G 网络中也是一个 GPRS 连接),等 MMS 数据收发完毕后再切换回 WiFi 或者是 GPRS 连接。这种设计可以封装并简化连接管理,应用开发者只需要通过设置一个 listener 或者 receiver 来监听 网络的切换以及网络状态的变化即可。
但是这种设计在某些现实环境中可能并不适用,有时候几个应用可能需要独立运行并且同时通过不同的网关从网络侧收发数据。OPhone 平台实现了多 PDP 复用的功能,不同的应用可以根据自身需要,通过 DataConnection 接口打开不同的网络连接,通过这种方式我们就可以在浏览网页的同时接收 MMS。
下面是一段示例代码,我们在代码注释中说明如何使用 DataConnection 的相关接口。
- // 创建一个 DataConnection 实例,这是一个全局的单件,驻存在每个
- // 进程(注意不是 Activity )空间之内,生命周期与进程相同
- DataConnection connection = DataConnection.getInstance();
- // 注册一个消息监听接口以便处理打开数据连接成功/失败,以及数据
- // 连接状态的变化。注意打开数据连接这一操作是异步的,其结果不会
- // 直接返回而是通过这个监听接口来通知应用
- conn.setNetworkStatusListener(new NetworkListener());
- // 请求系统打开某个数据连接时,需要提供该数据连接的 ID ,也就是这条
- // 记录在数据库表中的 ID 。 OMS 平台有预置的几条数据连接,包括
- // 中国移动的 cmnet 和 cmwap 网关等,用户也可自行添加其它数据连接,
- // 并通过 Settings 提供的相关接口,查询得到某个数据连接记录所对应的
- // 数据库表 ID
- boolean ret = conn.openConnection(your_app_context, data_connection_id);
- // 注意我们在打开数据连接时会关心两种类型的结果,一类是客户端(也就是手机)
- // 报的错误,另一类则是网络侧的错误。前者是同步返回的,也就是下边这段代码
- // 中应该处理的错误,而后者则是异步(通过上边注册的监听接口)返回的。
- if(!ret) {
- switch(connection.getErrorCode()) {
- case DataConnection.ERROR_PROF_NOT_FOUND:
- // 数据库中未找到指定的数据连接 ID
- break;
- case DataConnection.ERROR_NO_SIGNAL:
- // 这个错误的原因与其字面意思可能不是很相符,具体发生这个错误
- // 的原因除了手机无信号之外,可能还包括软件 RIL 层产生的内部逻辑错误
- break;
- case DataConnection.ERROR_AIRPLANE_MODE:
- // 飞行模式下无法打开数据连接
- break;
- default:
- // 未明原因的错误
- break;
- }
- }
// 创建一个 DataConnection 实例,这是一个全局的单件,驻存在每个 // 进程(注意不是 Activity )空间之内,生命周期与进程相同 DataConnection connection = DataConnection.getInstance(); // 注册一个消息监听接口以便处理打开数据连接成功/失败,以及数据 // 连接状态的变化。注意打开数据连接这一操作是异步的,其结果不会 // 直接返回而是通过这个监听接口来通知应用 conn.setNetworkStatusListener(new NetworkListener()); // 请求系统打开某个数据连接时,需要提供该数据连接的 ID ,也就是这条 // 记录在数据库表中的 ID 。 OMS 平台有预置的几条数据连接,包括 // 中国移动的 cmnet 和 cmwap 网关等,用户也可自行添加其它数据连接, // 并通过 Settings 提供的相关接口,查询得到某个数据连接记录所对应的 // 数据库表 ID boolean ret = conn.openConnection(your_app_context, data_connection_id); // 注意我们在打开数据连接时会关心两种类型的结果,一类是客户端(也就是手机) // 报的错误,另一类则是网络侧的错误。前者是同步返回的,也就是下边这段代码 // 中应该处理的错误,而后者则是异步(通过上边注册的监听接口)返回的。 if(!ret) { switch(connection.getErrorCode()) { case DataConnection.ERROR_PROF_NOT_FOUND: // 数据库中未找到指定的数据连接 ID break; case DataConnection.ERROR_NO_SIGNAL: // 这个错误的原因与其字面意思可能不是很相符,具体发生这个错误 // 的原因除了手机无信号之外,可能还包括软件 RIL 层产生的内部逻辑错误 break; case DataConnection.ERROR_AIRPLANE_MODE: // 飞行模式下无法打开数据连接 break; default: // 未明原因的错误 break; } }
以下是一个典型的数据连接监听接口,需要实现两个接口来分别处理打开数据连接的(异步)返回结果,以及数据连接的状态变化。由于这些回调函数都不是运行在应用的主线程(即通常情况下的 UI 线程)中的,所以最好通过 android.os.Handler 的 post 接口将相应的处理函数挂到 Activity 的缺省消息队列中去,通过这种方式就可以让自己的处理函数在主线程中得到执行了。
- Handler mHandler = new Handler {
- ...
- };
- private class NetworkListener implements
- DataConnection.OnNetworkStatusListener {
- public NetworkListener() { }
- public void onOpenResult(boolean success) {
- mHandler.post(your_runnable_handler_1);
- }
- public void onStatusChanged(int status) {
- mHandler.post(your_runnable_handler_2);
- }
- }
- // 比较典型的一个监听接口如下所示,我们在这个接口中通过前边介绍的
- // HttpClient 从网络侧读取数据
- class your_runnable_handler_1 implements Runnable {
- public void run() {
- try {
- HttpClient httpClient = new HttpClient();
- GetMethod getMethod = new GetMethod
- ("http://www.test_test.com/");
- httpClient.getHttpConnectionManager().
- getParams().setConnectionTimeout(3000);
- getMethod.getParams().setParameter
- (HttpMethodParams.RETRY_HANDLER,
- new DefaultHttpMethodRetryHandler());
- int statusCode = client.executeMethod(getMethod);
- if (statusCode != HttpStatus.SC_OK) {
- // 错误处理
- }
- Header[] headers=getMethod.getResponseHeaders();
- byte[] responseBody = getMethod.getResponseBody();
- } catch (Exception ee) {
- //
- } finally {
- // 释放连接
- getMethod.releaseConnection();
- }
- }
- }
- // 当不再使用该数据连接时,应该将其关闭。
- DataConnection.getInstance().closeConnection();
Handler mHandler = new Handler { ... }; private class NetworkListener implements DataConnection.OnNetworkStatusListener { public NetworkListener() { } public void onOpenResult(boolean success) { mHandler.post(your_runnable_handler_1); } public void onStatusChanged(int status) { mHandler.post(your_runnable_handler_2); } } // 比较典型的一个监听接口如下所示,我们在这个接口中通过前边介绍的 // HttpClient 从网络侧读取数据 class your_runnable_handler_1 implements Runnable { public void run() { try { HttpClient httpClient = new HttpClient(); GetMethod getMethod = new GetMethod ("http://www.test_test.com/"); httpClient.getHttpConnectionManager(). getParams().setConnectionTimeout(3000); getMethod.getParams().setParameter (HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler()); int statusCode = client.executeMethod(getMethod); if (statusCode != HttpStatus.SC_OK) { // 错误处理 } Header[] headers=getMethod.getResponseHeaders(); byte[] responseBody = getMethod.getResponseBody(); } catch (Exception ee) { // } finally { // 释放连接 getMethod.releaseConnection(); } } } // 当不再使用该数据连接时,应该将其关闭。 DataConnection.getInstance().closeConnection();
关于数据连接的更详细信息和其它接口更参考 OPhone 的 SDK 文档。在OPhone平台上,某一个数据连接是通过引用计数来进行不同应用之间的复用的,具体的一个用例是,假设有3个应用A,B,C需要打开同一个数据连接,那么系统只会在第一个请求时激活 PDP ,同时在最后一个关闭请求时在去激活这个 PDP ,以此保证3个应用都能无缝使用这个数据连接。当应用忘记通知系统关闭数据连接时,系统会通过 Android binder 的机制在应用的进程退出时自动为其减少一个引用计数,以保证 PDP 被正确去激活。
使用OPhone 数据连接的另外一个作用是能够保证应用在多 PDP 的情况下能通过正确的网络设备(这种情况下一般存在一个以上的网络设备)与网络侧通信。没有调用OPhone数据连接的应用理论上也能在 PDP 已激活的情况下与网络侧通信,只是这些通信只能依赖于平台的缺省路由机制。
由于 Android 平台在装载应用时有相关的权限检查, 所以最后不要忘了在应用的 AndroidManifest.xml 中声明如下权限:
- <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.INTERNET" />
本文介绍的仅是OPhone 平台上开发网络应用的一些最基本的接口,得益于 Android 平台良好的开发框架和调试环境,开发者们会发现以往在 C/C++ 中比较复杂的网络编程到了 Android 平台上已经得到了很大的简化,这样开发者就可以将更多的精力放在如何去设计并实现一个有良好用户体验的应用之上。
举例来说,一个优秀的网络应用往往还应该考虑如下问题:
- 是否有网络状态监听机制,比如连接中断时自动重连等
- 是否有足够以及合适的错误处理机制,因为网络操作中往往会产生各种各样的异常
- 是否考虑到网络延迟,网络带宽,以及负载均衡等问题,比如可以将数据加载放到后台线程执行,同时在 UI 上优先向用户呈现已得到的部分数据
- 此外还有用户信息的本地保存和恢复,安全机制等问题
本文仅是罗列性的介绍了一些 OPhone平台上网络开发基本技术点,希望对刚接触这个平台的开发者有所帮助。随着 3G 时代的到来以及支持 Android 平台的手持终端的普及,相信在不久的将来这个平台上一定会涌现出很多中国开发者原创的受用户喜爱的各种应用。(作者:王卫)
本文介绍了OPhone平台上网络应用开发的基本接口和技术要点,包括标准Java接口、Apache HttpClient、Android特有接口及C/C++接口的使用方法。此外,还详细讲解了OPhone特有的多PDP复用功能及其数据连接管理接口。
147

被折叠的 条评论
为什么被折叠?



