10.1 Web通信

作为互联网行业的翘楚,Google为Android注入的精神,就是随时随地保持与互联网的连接,使得通过Android移动设备与互联网进行通信不仅简单,而且“必须”。

对于开发者而言,基于互联网来构建应用,成了顺理成章的事情。应用可以通过Web来提供核心服务,也可以将用户数据存储到“云端”,或通过网络将不同设备上的用户连接在一起。总而言之,了解Android如何实现与Web的通信,已然是所有Android开发者的必修课。

10.1.1 基于HTTP的网络连接

实现与Web的通信,需要利用HTTP协议建立与Web服务的通信连接。Android中有多种方式来构建HTTP连接,比如:基于java.net包、使用org.apache.http包,等等。在本节中,所有示例都会使用org.apache.http包中的相关对象,基于java.net包的实现在原理上是一致的,恕不赘述。

通过org.apache.http包来实现网络通信,分成三个步骤,是一个同步处理流程:

(1)实例化派生自org.apache.http.client.HttpClient接口的对象,用来发起和管理所有的网络请求;

(2)实例化派生自org.apache.http.methods.HttpUriRequest接口的子对象,包括HttpGet、HttpPost、HttpHeader,等等,它们用来表示不同类型的HTTP请求;

(3)通过构造出来的HttpClient对象发送HttpUriRequest对象,并等待服务器响应,获取一个派生自org.apache.http.HttpResponse接口的子对象,通过该对象可获取Web服务返回的响应信息和数据。

比如,通过get请求来获取网络服务提供的数据,可以发起如下请求:


//构造HttpClient对象,来管理请求

HttpClient client=new DefaultHttpClient();

//构造HttpGet对象,向服务器发起请求

HttpGet request=new HttpGet("http://flyvenues.net");

//发起请求,获得HttpResponse对象,从中读取数据

HttpResponse response=client.excute(request);


相比get请求,post请求的构造会稍显复杂,需要将应用提交至服务器的数据打包并放入请求对象中。这些需要提交的数据,可以通过org.apache.http.entity.AbstractHttpEntity的子对象来封装,比如,表单格式数据可以通过org.apache.http.client.entity.UrlEncodedFormEntity对象来构造,二进制的数据流则可以使用org.apache.http.entity.ByteArrayEntity对象来封装,等等。一个发送post请求的示例如下:


//构造HttpClient对象,来管理请求

HttpClient client=new DefaultHttpClient();

//构造HttpPost对象,并将用户名、密码数据按照表单格式封装

HttpPost request=new HttpPost(

"http://flyvenues.net/login");

List<NameValuePair>data=new LinkedList<NameValuePair>();

data.add(new BasicNameValuePair("username","my_name");

data.add(new BasicNameValuePair("password","my_pass");

request.setEntity(

new UrlEncodedFormEntity(data, HTTP.UTF_8);

//发起请求,获得HttpResponse对象

HttpResponse response=client.excute(request);


此外,通过HttpUriRequest.setHeader等方法,开发者可以在HTTP请求头中添加相关数据,比如,需要在请求网络时添加cookie信息,可如下实现:


request.setHeader("cookie",myCookie);


通过HttpClient.excute方法,开发者可以发送请求,同步等待服务器执行完成,并获得包含服务器返回信息的HttpResponse对象。开发者从HttpResponse对象中可以读取到和响应数据相关的数据,比如:通过HttpResponse.getStatusLine方法,可以获得服务器的状态码(Status Code),来判断此次请求是否被正确执行;而利用HttpResponse.getEntity函数,可以获得服务器返回的数据流。解析HttpResponse的示例如下:


//检查状态码,并读取其中的数据

HttpResponse response=client.excute(request);

if(checkCode(response.getStatusLine().getStatusCode()){

readData(response.getEntity().getContent());

}

//消耗掉返回数据,结束连接

response.getEntity().consumeContent();


值得注意的是,开发者通常需要在请求结束后调用HttpEntity.consumeContent方法,显性地释放网络连接占据的缓冲区内容,表示该请求已经处理完成,被占用的这些缓冲区资源可以被继续复用。

HttpClient在发送请求之前,会对请求相关的参数进行调整和设置,通过设置HttpClient的org.apache.http.params.HttpParams对象,可以调整网络连接相关的参数,实现连接管理的功能。举个例子,在网络连接过程中,开发者为了在网络状况不好或服务器无法响应时及时终止连接,保证用户体验,往往需要控制连接超时的时长:


HttpClient client=new DefaultHttpClient();

//设置连接超时为10s

HttpConnectionParams.setConnectionTimeout(

client.getParams(),10000);

//设置通信超时也为10s

HttpConnectionParams.setSoTimeout(

client.getParams(),10000);


通过org.apache.http.params.HttpConnectionParams类提供的静态方法,可以调整HttpParams对象中与连接超时、缓冲区大小等连接控制密切相关的参数,而通过类org.apache.http.client.params.HttpClientParams中的静态方法,可以调整转跳、cookie策略等与连接数据和行为密切相关的参数。

比如,HttpClient在默认设置下,是不会根据服务器返回的状态码进行自动转跳的,而是需要通过HttpClientParams.setRedirecting函数进行开启:


HttpClient client=new DefaultHttpClient();

HttpClientParams.setRedirecting(true);


在默认情况下,DefaultHttpClient对象是线程非安全的,开发者不能在多个线程中同时调用该DefaultHttpClient对象的方法。要保证HttpClient的线程安全性,可以为它设置线程安全的管理对象:


HttpParams params=new BasicHttpParams();

SchemeRegistry schemeRegistry=new SchemeRegistry();

ClientConnectionManager manager=

new ThreadSafeClientConnManager(params, schemeRegistry);

HttpClient client=new DefaultHttpClient(ccm, params);


在实际开发中,如果有线程安全、超时控制等需求,可以选择使用android.net.http.AndroidHttpClient对象来替代DefaultHttpClient。本质上,AndroidHttpClient是对DefaultHttpClient进行了封装和初始化,预先设置了超时、转跳等参数,并将HttpClient设置成为线程安全。