HTTP 使用詳解
在你瀏覽互聯網的時候,絕大多數的數據都是通過 HTTP 協議獲取到的,也就是說如果你想要實現一個能上網的 App,那么就一定會和 HTTP 打上交道。當然 Android 發展到現在這么多年,已經有很多非常好用,功能非常完善的網絡框架了,比如 Volley、OkHttp、retrofit等,但是底層邏輯都是一樣的。本節我們來學習 Android 原聲支持的 HTTP 接口,相比那些第三方框架,它的封裝更好,也更適合我們了解底層原理。
1. Http 協議
Http 底層基于 TCP 協議,分為請求和響應。請求和響應分別有各自的 Header 和 Body 組成。Header 里面通常是本次請求 / 響應的描述信息,比如版本號、長度、UA、Content-Type 等等,而 Body 里面通常就是我們要傳遞的業務數據了,下面分別瀏覽一下請求和響應的內容。
1.1 Http 請求
請求頭的內容有很多,這里給大家做一個記錄當做資料,不需要都記住,在實際使用中用到可以過來查閱即可。
Header | 解釋 | 示例 |
---|---|---|
Accept | 指定客戶端能夠接收的內容類型 | Accept: text/plain, text/html |
Accept-Charset | 瀏覽器可以接受的字符編碼集。 | Accept-Charset: iso-8859-5 |
Accept-Encoding | 指定瀏覽器可以支持的web服務器返回內容壓縮編碼類型。 | Accept-Encoding: compress, gzip |
Accept-Language | 瀏覽器可接受的語言 | Accept-Language: en,zh |
Accept-Ranges | 可以請求網頁實體的一個或者多個子范圍字段 | Accept-Ranges: bytes |
Authorization | HTTP授權的授權證書 | Authorization: Basic QWxhZIRpbjpvcGAuIHNlc2FtZQ== |
Cache-Control | 指定請求和響應遵循的緩存機制 | Cache-Control: no-cache |
Connection | 表示是否需要持久連接。(HTTP 1.1默認進行持久連接) | Connection: close |
Cookie | HTTP請求發送時,會把保存在該請求域名下的所有cookie值一起發送給web服務器。 | Cookie: $Version=1; Skin=new; |
Content-Length | 請求的內容長度 | Content-Length: 348 |
Content-Type | 請求的與實體對應的MIME信息 | Content-Type: application/x-www-form-urlencoded |
Date | 請求發送的日期和時間 | Date: Tue, 15 Nov 2010 08:12:31 GMT |
Expect | 請求的特定的服務器行為 | Expect: 100-continue |
From | 發出請求的用戶的Email | From: [email protected] |
Host | 指定請求的服務器的域名和端口號 | Host: http://www.xianlaiwan.cn/wiki/androidlesson/ |
If-Match | 只有請求內容與實體相匹配才有效 | If-Match: “737060cd8c284d8af7ad3082f209582d” |
If-Modified-Since | 如果請求的部分在指定時間之后被修改則請求成功,未被修改則返回304代碼 | If-Modified-Since: Sat, 29 Oct 2010 19:43:31 GMT |
If-None-Match | 如果內容未改變返回304代碼,參數為服務器先前發送的Etag,與服務器回應的Etag比較判斷是否改變 | If-None-Match: “737060cd8c284d8af7ad3082f209582d” |
If-Range | 如果實體未改變,服務器發送客戶端丟失的部分,否則發送整個實體。參數也為Etag | If-Range: “737060cd8c284d8af7ad3082f209582d” |
If-Unmodified-Since | 只在實體在指定時間之后未被修改才請求成功 | If-Unmodified-Since: Sat, 29 Oct 2010 19:43:31 GMT |
Max-Forwards | 限制信息通過代理和網關傳送的時間 | Max-Forwards: 10 |
Pragma | 用來包含實現特定的指令 | Pragma: no-cache |
Proxy-Authorization | 連接到代理的授權證書 | Proxy-Authorization: Basic QWxhZGbpbjpAcGVuIHNlc2FtZQ== |
Range | 只請求實體的一部分,指定范圍 | Range: bytes=500-999 |
Referer | 先前網頁的地址,當前請求網頁緊隨其后,即來路 | http://www.xianlaiwan.cn/wiki/androidlesson/ |
TE | 客戶端愿意接受的傳輸編碼,并通知服務器接受接受尾加頭信息 | TE: trailers,deflate;q=0.5 |
Upgrade | 向服務器指定某種傳輸協議以便服務器進行轉換(如果支持) | Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11 |
User-Agent | User-Agent的內容包含發出請求的用戶信息 | User-Agent: Mozilla/5.0 (Linux; X11) |
Via | 通知中間網關或代理服務器地址,通信協議 | Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1) |
Warning | 關于消息實體的警告信息 | Warn: 199 Miscellaneous warning |
1.2 Http 響應
響應就是服務器收到我們的 request 之后給我們返回的數據,同樣記錄一下響應頭:
Header | 解釋 | 示例 |
---|---|---|
Accept-Ranges | 表明服務器是否支持指定范圍請求及哪種類型的分段請求 | Accept-Ranges: bytes |
Age | 從原始服務器到代理緩存形成的估算時間(以秒計,非負) | Age: 12 |
Allow | 對某網絡資源的有效的請求行為,不允許則返回405 | Allow: GET, HEAD |
Cache-Control | 告訴所有的緩存機制是否可以緩存及哪種類型 | Cache-Control: no-cache |
Content-Encoding | web服務器支持的返回內容壓縮編碼類型 | Content-Encoding: gzip |
Content-Language | 響應體的語言 | Content-Language: en,zh |
Content-Length | 響應體的長度 | Content-Length: 348 |
Content-Location | 請求資源可替代的備用的另一地址 | Content-Location: /index.htm |
Content-MD5 | 返回資源的MD5校驗值 | Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ== |
Content-Range | 在整個返回體中本部分的字節位置 | Content-Range: bytes 21010-47021/47022 |
Content-Type | 返回內容的MIME類型 | Content-Type: text/html; charset=utf-8 |
Date | 原始服務器消息發出的時間 | Date: Tue, 15 Nov 2010 08:12:31 GMT |
ETag | 請求變量的實體標簽的當前值 | ETag: “737060cd8c284d8af7ad3082f209582d” |
Expires | 響應過期的日期和時間 | Expires: Thu, 01 Dec 2010 16:00:00 GMT |
Last-Modified | 請求資源的最后修改時間 | Last-Modified: Tue, 15 Nov 2010 12:45:26 GMT |
Location | 用來重定向接收方到非請求URL的位置來完成請求或標識新的資源 | Location: http://www.xianlaiwan.cn/wiki/androidlesson/ |
Pragma | 包括實現特定的指令,它可應用到響應鏈上的任何接收方 | Pragma: no-cache |
Proxy-Authenticate | 它指出認證方案和可應用到代理的該URL上的參數 | Proxy-Authenticate: Basic |
2. HttpUrlConnection 接口
Android 系統為我們提供了 HttpUrlConnection 接口用于實現 Http 請求。自從 Android API
9 開始,HttpUrlConnection 就成為了 Android App 推薦使用的內置 Http 庫。使用它無需添加任何依賴,打開網絡權限:
<uses-permission android:name="android.permission.INTERNET" />
就可以訪問 Http 資源了,可以說相比第三方框架
3. HttpUrlConnection 的使用步驟
首先還是引用一下 Google 官方的使用文檔:
A URLConnection with support for HTTP-specific features. See the spec for details.
Uses of this class follow a pattern:
- Obtain a new
HttpURLConnection
by calling[URL#openConnection()](https://developer.android.com/reference/java/net/URL#openConnection())
and casting the result toHttpURLConnection
.- Prepare the request. The primary property of a request is its URI. Request headers may also include metadata such as credentials, preferred content types, and session cookies.
- Optionally upload a request body. Instances must be configured with
[setDoOutput(true)](https://developer.android.com/reference/java/net/URLConnection#setDoOutput(boolean))
if they include a request body. Transmit data by writing to the stream returned by[URLConnection.getOutputStream()](https://developer.android.com/reference/java/net/URLConnection#getOutputStream())
.- Read the response. Response headers typically include metadata such as the response body’s content type and length, modified dates and session cookies. The response body may be read from the stream returned by
[URLConnection.getInputStream()](https://developer.android.com/reference/java/net/URLConnection#getInputStream())
. If the response has no body, that method returns an empty stream.- Disconnect. Once the response body has been read, the
HttpURLConnection
should be closed by calling[disconnect()](https://developer.android.com/reference/java/net/HttpURLConnection#disconnect())
. Disconnecting releases the resources held by a connection so they may be closed or reused.
官方文檔沒有對 Http 協議本身做什么解釋(如果對 Http 協議不太了解的同學,可以參考慕課網上網絡相關課程),主要是圍繞 HttpUrlConnection 的用法展開了一步步的描述,結合官網的解釋以及我個人的總結,大體上可以分為一下幾步:
- 通過
openConnection()
方法創建一個HttpURLConnection
:
URL url = new URL(https://www.baidu.com);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
首先創建一個 URL 對象,參數就是我們要打開的地址,然后使用 url 對象的openConnection()
來打開 Http 連接,拿到一個HttpURLConnection
對象。
2. 設置 Http 請求類型
設置本次 Http 請求的方法類型,Http 有以下幾種類型:
- GET
- POST
- HEAD
- CONNECT
- OPTIONS
- TRACE
- PATCH
- PUT
- **DELETE
這里就不做詳細的解釋了,可自行百度。最常用的就是前兩種:GET
和POST
:
conn.setRequestMethod("GET");
- 設置 Http 相關參數
這一步主要是設置請求頭的參數,我們前面那張大表就可以派上用場了。此時可以設置 Cookie、Content-Type、超時時間等等參數。比如設置超時時間為 3 秒:
conn.setConnectTimeout(3*1000);
conn.setWirteTimeout(3 * 1000);
- 獲取輸入流
通過getInputStream()
方法獲取網絡輸入流,此后可以通過此對象獲取網絡數據,如下:
InputStream in = conn.getInputStream();
- 關閉流
網絡流比較消耗資源,在使用完畢之后一定要將 Http 連接關掉:
conn.disconnect();
4. HttpURLConnection使用示例
還記得前面將現場的時候提到過,Android 系統規定只能在主線程操作 UI,這里再加上一條:
Android 系統不能在 UI 線程訪問網絡
所以我們需要開啟一個子線程處理 Http 請求,這一節我們使用 40 節學習的 AsyncTask 來執行網絡請求,拉取慕課網 Android 教程的首頁信息。
4.1 后臺網絡任務
首先開啟一個 AsyncTask,在后臺打開一個 HttpURLConnection,地址是http://www.xianlaiwan.cn/wiki/androidlesson,接著設置相應參數,然后獲取網絡數據輸入流,即可讀到頁面信息了。
package com.emercy.myapplication;
import android.os.AsyncTask;
import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class HttpTask extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... params) {
HttpURLConnection urlConnection = null;
try {
URL url = new URL("http://www.xianlaiwan.cn/wiki/androidlesson");
urlConnection = (HttpURLConnection) url.openConnection();
int code = urlConnection.getResponseCode();
if (code != 200) {
throw new IOException("Invalid response from server: " + code);
}
BufferedReader rd = new BufferedReader(new InputStreamReader(
urlConnection.getInputStream()));
String line;
while ((line = rd.readLine()) != null) {
Log.d("HttpTask", line);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
return null;
}
}
4.2 首頁布局
HTTPURLConnection 需要一個觸發時機,所以在首頁布局上我們放置一個 Button 用于觸發 http 請求:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/start_http"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="100dp"
android:text="發起 Http 請求" />
</LinearLayout>
4.3 MainActivity編寫
核心邏輯是在 HttpTask 里面,MainActivity 里面主要是設置觸發按鈕監聽器,然后在點擊事件中啟動 HttpTask 即可:
package com.emercy.myapplication;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.start_http).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new HttpTask().execute();
}
});
}
}
這里要記得,雖然在 HttpTask 中我們的代碼是運行在doInBackground()
方法中,但是啟動 AsyncTask 的方法是調用eexecute()
。
編譯運行界面如下:
點擊“發起 HTTP 請求”啟動 AsyncTask 啟動 http 連接,然后輸入接收到的數據到 Logcat,過濾“HttpTask”字段,觀察日志:
以上是日志的片段,可以看到 Logcat 中的內容就是http://www.xianlaiwan.cn/wiki/androidlesson頁面的源代碼,到此就獲取成功了。
5. 小結
本節講述了如何在 Android 中發起 http 請求,HttpURLConnection 的使用方式非常簡單,但是需要你對 http 協議本身有一定的了解,作為開發者這個是必備的知識點。當然對于大型項目而言,需要涉及到 DNS、緩存、安全校驗等等高級操作,這時候 HttpURLConnection 會顯得有點輸出乏力,這就需要第三方的框架出場了,萬變不離其宗,所有的框架到了底層其實都是一樣的基本原理,所以還是希望大家能夠熟悉 http 協議,并掌握 HttpURLConnection 的用法。