1前言
Android将會在最新版本(API level 23,Android M)移除對HttpClient的支援,改為使用HttpURLConnection來代替HttpClient。因為HttpURLConnection因為使用了壓縮傳輸和響應封包緩存的技術讓它有了更少的流量和電量消耗。是以我們在這裡探讨一下HttpURL的詳細内容。
2Http封包詳解
既然要使用HttpURLConnection代替HttpClient行使網絡資料互動的作用首先肯定要了解HTTP封包格式。掌握了HTTP的封包格式,在使用代碼組包的時候會更清楚的了解将會發送到伺服器的資料内容。 關于HTTP封包詳細格式參考:OSChina HTTP封包詳解 和 CSDN HTTP請求封包和響應封包
簡單來說HTTP請求封包分三部分,分别是請求行,請求頭,請求體,同樣的響應封包也分三部分:響應行,響應頭和響應體。我們要做的是通過code組織可以讓伺服器識别的請求并且接收和解析伺服器的響應内容。
下圖表示Http請求封包和響應封包的示例:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2QvwVe0lmdhJ3ZvwFM38CXlZHbvN3cpR2Lc1TPB10QGtWUCpEMJ9CXsxWam9CXwADNvwVZ6l2c052bm9CXUJDT1wkNhVzLcRnbvZ2LcZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39DO0cDN1UjM1EjMxATM1EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
圖檔引用自網絡,侵删。
3,示例代碼
一段簡單的同伺服器互動的代碼,這段代碼的功能是通過手機号查詢歸屬地:
urlConnection = (HttpURLConnection) new URL("http://webservice.webxml.com.cn/WebServices/MobileCodeWS.asmx/getMobileCodeInfo").openConnection();
//擷取HttpURLConnection執行個體,這裡根據協定應該是擷取sun.net.www.protocol.http.HttpURLConnection執行個體
String parms = "mobileCode=18633483157&userID=";//等待被寫入請求體的參數
urlConnection.setDoOutput(true);
urlConnection.setDoInput(true);
urlConnection.setRequestMethod("POST");//設定請求方法
urlConnection.setRequestProperty("Content-Length", parms.length()+"");//設定請求頭,這裡請求頭必選字段内容以及含義需要和伺服器做好約定
urlConnection.connect();//建立與伺服器的連接配接
OutputStream outputStream = urlConnection.getOutputStream();
outputStream.write(parms.getBytes());
outputStream.flush();//寫入參數
InputStream inputStream = urlConnection.getInputStream();//擷取伺服器的傳回結果,注意在這個時候放在outputstream的參數才會正式上行到達伺服器。
int resCode = urlConnection.getResponseCode();
綜合上面封包示例,我們可以總結如下:
urlConnection.setRequestMethod("POST");
//設定請求方法
urlConnection.setRequestProperty("Content-Length", parms.length()+"");
//設定請求頭
urlConnection.setDoOutput(true);
OutputStream outputStream = urlConnection.getOutputStream();
outputStream.write(parms.getBytes());
outputStream.flush();
//設定請求封包内容
urlConnection.getResponseCode();
//擷取響應碼(比如200請求處理成功等)
urlConnection.getResponseMessage();
//擷取響應狀态描述
urlConnection.getHeaderField("");
//擷取響應頭内容,根據響應頭域名稱擷取對應值
urlConnection.getInputStream();
//擷取響應消息體,一般需要解析内容并做顯示
4,HttpURLConnection的類結構與資料結構(code來源:openjdk 7)
4.1 類結構圖
URLConnection是所有urlconnection的頂層接口,不同子類根據自己的協定實作對應的邏輯,是以一定要在初始化階段搞明白目前協定類型以及對應的URLConnection子類。
4.2 資料結構
URLConnection::MessageHeaders request;通過兩個String數組分别儲存請求頭資料中的字段名和字段值,通過set方法和grow方法存儲值和保持自增長。同時在針對協定的具體實作中(比如sun.net.www.protocol.http.HttpURLConnection.java針對http協定的實作)定義了MessageHeader response成員,通過parseHeader方法從伺服器響應資料(InputStream)中解析響應頭并存入String數組中。
我們在代碼中通過setRequestPropertity方法設定請求頭的内容。
MessageHeaders定義:
class MessageHeader {
private String keys[];
private String values[];
private int nkeys;
/** grow the key/value arrays as needed */
private void grow() {
if (keys == null || nkeys >= keys.length) {
String[] nk = new String[nkeys + 4];
String[] nv = new String[nkeys + 4];
if (keys != null)
System.arraycopy(keys, 0, nk, 0, nkeys);
if (values != null)
System.arraycopy(values, 0, nv, 0, nkeys);
keys = nk;
values = nv;
}
}
public synchronized void set(String k, String v) {
for (int i = nkeys; --i >= 0;)
if (k.equalsIgnoreCase(keys[i])) {
values[i] = v;
return;
}
add(k, v);
}
/** Parse a MIME header from an input stream. */
public void parseHeader(InputStream is) throws java.io.IOException {
synchronized (this) {
nkeys = 0;
}
mergeHeader(is);
}
}
設定請求頭
public void setRequestProperty(String key, String value) {
if (connected)
throw new IllegalStateException("Already connected");
if (key == null)
throw new NullPointerException ("key is null");
if (requests == null)
requests = new MessageHeader();
requests.set(key, value);
}
響應資訊:
responseCode和responseMessage以及針對協定的HttpURLConnection實作中定義的response成員都是用來儲存解析過的伺服器傳回資訊。
一個典型的傳回資訊如下:
HTTP/1.1 200 OK\r\n
Server: nginx/1.4.7\r\n
Date: Thu, 14 May 2015 06:47:26 GMT\r\n
Content-Type: text/html;charset=UTF-8\r\n
Content-Length: 883\r\n
其中第一行200會被解析作為responseCode,OK被作為responseMessage内容。下面各個字段以key-value對的形式存入到response中。
互動控制資訊:
通過定義如下資訊控制互動過程中的行為,我們在code中也可以通過setXXX的形式設定這些控制選項。
protected URL url;
protected boolean doInput = true;
protected boolean doOutput = false;
protected boolean allowUserInteraction;
protected boolean useCaches = defaultUseCaches;
protected long ifModifiedSince = 0;
protected boolean connected = false;
private int connectTimeout;
private int readTimeout;
private static boolean fileNameMapLoaded = false;