转眼间,刘关张三人就到了江东。出了火车站就看到一个眼镜男举着块牌子站在那里,牌子上面写着:“要发票吗?”
刘备上前搭讪:“喂,老兄,你跟火车站卖发票,很有创意啊!”
眼镜男听了愣了一下,“擦,亮错面了,我说大家今天对我回头率为什么这么高!”,说着赶紧把牌子翻过来,只见另一面写着:“接刘关张!”
张飞:“原来你就是鲁肃!”
鲁肃:“不才正是在下。”
张飞:“说!你和鲁迅到底是什么关系!”
鲁肃:“有你妹关系啊!”
刘备:“想不到老兄一表人才,竟也做出卖发票这种见不得人的勾当!”
鲁肃:“嗨,业余爱好,业余爱好!三位随我来,我们还是赶紧开工吧!”
四个人屁颠屁颠的来到了鲁肃公司,只见公司写字楼上写着几个大字:“人民公厕!”
关羽:“哇,江东人民好了不起,公厕都盖这么豪华!”
鲁肃:“这不是公厕,就是我们公司啦,写这个名字主要为了迷惑税务局,被查税就不好办了!”
关羽:“还是人民群众有智慧啊!”
进到楼里后四人直奔总裁办公室,只见一个二十多岁年轻人坐在椅子上。鲁肃介绍道:“这就是我们公司老板,孙权。”
孙权:“你们几个就是孔明介绍来的?听说你们做移动开发有两下子哒?”
刘备:“一般一般,世界第三!”
孙权:“HTTP开发很熟练吧?跟我们服务器交互主要通过HTTP来完成噢~”
刘备:“一般一般,世界第三!”
孙权:“哦,世界第三我就放心了,那咱们赶紧开工吧!”
1.1. HTTP通信
HTTP(Hyper Text TransferProtpcol,超文本传输协议)是一种详细规定了浏览器和万维网服务器之间互相通信的规则,通过因特网传送万维网文档的数据传送协议。HTTP采用了请求/响应模型。客户端向服务器发送一个请求,请求包含了请求的方法、URL、协议版本、请求修饰符、客户信息和类似于MIME消息结构的内容。服务器以一个状态行作为响应,响应的内容包括消息协议的版本、成功或错误编码、服务器信息、实体元信息和可能的实体内容。HTTP是一个属于应用层的面向对象的协议,最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。与Socket“三次握手”的连接方式不同,HTTP从建立连接到关闭连接的过程称为“一次连接”。
在Android中有两种使用HTTP通信的方式。一种是使用Java API中的HttpURLConnection接口;另一种是使用Android API当中的HttpClient接口。下面将分别介绍这两种HTTP通信接口的使用方法。
1.1.1.HttpURLConnection接口
HttpURLConnection是Java API中带有的HTTP通信抽象类。HTTP有两种请求方式,一种是Get方式,另一种是Post方式。Get请求可以获取静态页面,也可以把参数放在URL字串后面,传递给服务器。Post与Get的不同之处在于Post的参数不是放在URL字串里面,而是放在HTTP请求的正文内。如果不进行设置的话,HttpURLConnection会默认为Get请求方式。
下面是一个使用Get方式进行请求的HTTP通信的示例代码:
HttpURLConnection Get请求方式代码清单19-1-1:
public staticvoid readContentFromGet(String url) throws IOException {
// 拼凑get请求的URL字串,使用URLEncoder.encode对特殊和不可见字符进行编码
String getURL = url +"?username="
+ URLEncoder.encode("刘备", "utf-8");
/*根据拼凑的URL,打开连接,URL.openConnection函数会根据URL的类型返回不同的URLConnection子类的对象,这里URL是一个HTTP,因此实际返回的是HttpURLConnection
*/
URL getUrl = new URL(getURL);
/*进行连接,但是实际上get request要在下一句的connection.getInputStream()函数中才会真 *正发送到服务器
*/
HttpURLConnection connection = (HttpURLConnection)getUrl.openConnection();
connection.connect();
// 取得输入流,并使用reader读取
BufferedReader reader = newBufferedReader(new InputStreamReader(
connection.getInputStream()));
String lines;
while ((lines = reader.readLine()) != null){
Log.e(lines);
}
reader.close();
// 断开连接
connection.disconnect();
}
上述代码通过Get请求方式与服务器建立连接,返回username为刘备的服务器信息,并将其打印在log当中。Get请求方式将请求内容拼接在getURL字符串里,通过connection.connect()语句建立连接,将服务器返回信息存储在lines字符串中。值得注意的是,在通信结束之后需要调用connection.disconnect()方法断开连接。
下面将要介绍如何使用HttpURLConnection的Post请求方式进行连接,示例代码如下所示:
HttpURLConnection Post请求方式代码清单19-1-1:
public staticvoid readContentFromPost(String url) throws IOException {
// Post请求的url,与get不同的是不需要带参数
URL postUrl = new URL(url);
// 打开连接
HttpURLConnection connection =(HttpURLConnection) postUrl.openConnection();
/*设置是否向connection输出,因为这个是post请求,参数要放在
* HTTP正文内,因此需要设为true
*/
connection.setDoOutput(true);
// 设置是否从连接中读取数据,默认为 true.
connection.setDoInput(true);
// 设置请求模式,这是设置为POST
connection.setRequestMethod("POST");
// Post 请求不能使用缓存
connection.setUseCaches(false);
//是否自动执行重定向,默认为true
connection.setInstanceFollowRedirects(true);
//配置本次连接的Content-type,配置为application/x-www-form-urlencoded
connection.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
// 执行HTTP连接,注意所有配置需要在连接之前完成
connection.connect();
DataOutputStream out = newDataOutputStream(connection.getOutputStream());
//正文内容跟get的URL中'?'后的参数字符串一致
String content = "username=" +URLEncoder.encode("刘备","utf-8");
/* DataOutputStream.writeBytes将字符串中的16位的unicode字符以8位的字符形式写道流 *里面
*/
out.writeBytes(content);
out.flush();
out.close();
BufferedReader reader = newBufferedReader(new InputStreamReader(
connection.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
Log.e(line);
}
reader.close();
connection.disconnect();
}
与Get方法不同的是,上述代码创建的Url对象postUrl中并不包含需要请求的内容拼接字段,而是将请求内容放在了HTTP请求正文内。请求内容同Get请求方式一样,都是向服务器请求username为张飞的信息。并将获得的信息存储在line字段中,打印Log输出。
1.1.2.HttpClient接口
除了可以使用JDK API中所带的HttpURLConnection接口进行HTTP通信外,还可以使用Android SDK自带的HttpClient来实现HTTP连接服务。我们可以把HttpClient想象成一个浏览器,通过它的API可以很方便的发出Get、Post请求。下面是一个HttpClient的示例代码:
HttpClient实例代码清单19-1-2:
public voidhttpClientConnection(String url){
try {
// 创建一个默认的HttpClient
HttpClient httpclient = newDefaultHttpClient();
// 创建一个Get请求
HttpGet request = newHttpGet(url);
// 发送Get请求,并将响应内容转换成字符串
String response =httpclient.execute(request, new BasicResponseHandler());
Log.e("responsetext", response);
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
上述代码创建了一个HttpGet对象,实现了发送Get请求。同样的,如果需要发送Post请求,则只要在HttpGet对象中设置相应的参数即可。上述代码创建了一个BasicResponseHandler对象用来捕获服务器返回的信息,将信息存储在response字符串中并通过Log打印出来。
1.2. JSON简介
在客户端与服务器通信时,往往需要进行复杂的消息传递,而不是仅仅传递一个字符串或文件流。那么如何传递消息呢?容易想到通过定义消息的报文格式来进行消息传递。例如,传递消息的比特数组的0-3位规定为日期,4-7位规定为天气类型。以这种方式进行消息传递需要客户端与服务器统一使用同样的报文解析方式,并且需要人为规定每一位的作用。
本节将要介绍的JSON(JavaScript ObjectNotation)是一种轻量级的数据交换格式,支持Java、C++、C#、Python等语言,可以方便的完成客户端与服务器之间的数据交换。
JSON只有两种结构:
l JSON对象:表示为“{}”扩起来的内容,数据结构为{key:value,key:value,...}的键值对结构。在面向对象的语言中,key为对象的属性,value为对应的属性值,属性值的类型可以是数字、字符串、数组或JSON对象等。
l JSONArray数组:表示为“[]”扩起来的内容,数据结构为["java","javascript","vb",...],通过索引取值,“[]”中的字段值的类型可以是数字、字符串、数组、JSON对象等。
JSON对象数据结构的示例代码如下所示:
{ "firstName":"张","lastName":"飞","email": "jiubugaosuni@haha.com" }
可以看出该JSON对象中“firstName”的值为“张”,“lastName”的值为“飞”,“email”的值为“jiubugaosuni@haha.com”。
JSONArray数组数据结构的示例代码如下所示:
{ "Hero": [
{"firstName": "刘","lastName":"备","email": "yaoniming3000@haha.com" },
{"firstName": "关","lastName":"羽","email": "qiujiaowang@haha.com"},
{"firstName": "张","lastName":"飞","email": "jiubugaosuni@haha.com" }
]
}
在上面名为“Hero”的JSONArray中,包含了三个JSON项,分别是刘关张三兄弟。每个JSON中包含三个键“firstName”、“lastName”、“email”。可以使用getJson(String key)、getJsonArray(String key)方法来获取指定键名下的值。
1.3. HTTP实现JSON数据传输
上面介绍了如何使用HTTP进行网络通信和JSON的基本概念。下面就通过一个实例讲解如何使用HTTP实现JSON数据传输。值得注意的是,需要在AndroidManifest.xml文件中添加访问网络的权限:
<uses-permissionandroid:name="android.permission.INTERNET" />
新建一个静态方法类MyHttpClient,代码如下所示:
MyHttpClient代码清单19-3-0:
/**
* @author关羽:身披一条麻袋,脚踏七彩拖鞋,来到孔明面前说:“行行好,给点零花钱吧。”
*/
public classMyHttpClient {
//通过Get方式获取服务器上的JSON数据
public static JSONObject httpGet(Stringurl) {
//使用HttpClient进行HTTP通信
HttpClient httpclient = newDefaultHttpClient();
HttpGet httpget = new HttpGet(url);
try {
HttpResponse response =httpclient.execute(httpget);
BufferedReader reader =
new BufferedReader(new InputStreamReader(response.getEntity()
.getContent(), "UTF-8"));
String str;
StringBuilder jsonString = newStringBuilder();
//将数据以JSON格式进行解析,返回jsonObject对象
for (str = reader.readLine(); str!= null; str = reader.readLine()) {
jsonString.append(str);
}
if (jsonString != null) {
JSONObject jsonObject = newJSONObject(jsonString.toString());
return jsonObject;
}
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
//为了防止出现JSON格式解析错误,这里必须增加异常抛出
e.printStackTrace();
}
return null;
}
//通过Post方式获取服务器上的JSON数据
public static JSONObject httpPost(Stringurl, List<NameValuePair> params) {
HttpClient httpclient = newDefaultHttpClient();
HttpPost post = new HttpPost(url);
UrlEncodedFormEntity ent;
try {
ent = newUrlEncodedFormEntity(params, HTTP.UTF_8);
post.setEntity(ent);
HttpResponse responsePOST =httpclient.execute(post);
HttpEntity resEntity =responsePOST.getEntity();
if (resEntity != null) {
String response =EntityUtils.toString(resEntity);
return newJSONObject(response);
}
} catch (UnsupportedEncodingExceptione) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
}
上述代码MyHttpClient类是一个通信工具类,将HTTP通信模块单独封装为一个工具类,其好处是方便项目各个模块的调用。
MyHttpClient类分别利用Android API中的Get及Post方法封装了两种HTTP通信方式。在静态方法httpGet()中,输入参数为通信服务器的URL地址,该方法创建了一个HttpClient对象进行HTTP通信。在HTTP连接之后,服务器返回的数据会传递至HttpResponse类中,通过BufferReader类进行缓存,最终将从服务器获取的数据存储在JSONobject中。静态方法httpPost()也进行类似的操作,输入通信服务器的URL地址,返回获取到的JSON数据。
那么实际进行HTTP通信时又该如何解析得到的JSON呢?例如19.2节中的例子:
{ "Hero": [
{"firstName": "刘","lastName":"备","email": "yaoniming3000@haha.com" },
{"firstName": "关", "lastName":"羽", "email": "qiujiaowang@haha.com"},
{"firstName": "张","lastName":"飞","email": "jiubugaosuni@haha.com" }
]
}
首先可以创建一个JSONobject对象用于实现了将服务器传递过来的数据存储到JSON变量中,如下所示:
JSONobject json= MyHttpClient.httpGet (TestUrl);
然后使用optJSONArray()方法,获取JSONArray,如下所示:
JSONArray hero= json.optJSONArray(“Hero”);
在获取到JSONArray对象hero后,就可以通过索引位置找到需要的JSON数据,并再次通过opt()方法获取该项数据下的子字段值。
值得注意的是,这里使用了opt(String key)方法而不是get(Stringkey)方法,原因是get()方法容易因为没有找到匹配的键值而产生异常,而使用opt()方法,如果没有找到匹配的键值则返回空值,不会产生异常。
刘备:这里介绍一个小技巧给大家。实际进行项目开发时,往往需要对很多服务器地址进行HTTP通信,或需要拼接URL地址以获取客户端所需要的信息。这时建议在MyHttpClient工具类的基础上继承多个子通信类,专门用来记录服务器信息he 拼接规则。这样可以使自己的工程结构更加合理有序。
|
1.4. 玄德有话说
刘备:搞不懂啦,这么多通信方式,我怎么知道我需要用HTTP而不是其他Socket通信方式呢?
孔明:是这样的,HTTP通信的优势在于简便、快捷。当需要从服务器获取或上传的数据短时间内只需一次通信,则建议使用HTTP通信。举个例子来说吧,从服务器上获取一下今天的天气信息就可以用HTTP通信,而与服务器进行语音或文件交换就建议用Socket其他通信方式进行。
刘备:为啥我的代码貌似没问题,却怎么也得不到数据?
孔明:赶紧检查下服务器是否运行正常,运行不正常的话就把罪过全怪在写服务器代码人的身上吧!
刘备:服务器运行很正常呀!
孔明:那去查查客户端上请求的IP地址和端口是否与服务器一致。
刘备:哦,大意了,果然是这里填错了!