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设置成为线程安全。