HTTP 使用詳解
在你瀏覽互聯(lián)網(wǎng)的時(shí)候,絕大多數(shù)的數(shù)據(jù)都是通過(guò) HTTP 協(xié)議獲取到的,也就是說(shuō)如果你想要實(shí)現(xiàn)一個(gè)能上網(wǎng)的 App,那么就一定會(huì)和 HTTP 打上交道。當(dāng)然 Android 發(fā)展到現(xiàn)在這么多年,已經(jīng)有很多非常好用,功能非常完善的網(wǎng)絡(luò)框架了,比如 Volley、OkHttp、retrofit等,但是底層邏輯都是一樣的。本節(jié)我們來(lái)學(xué)習(xí) Android 原聲支持的 HTTP 接口,相比那些第三方框架,它的封裝更好,也更適合我們了解底層原理。
1. Http 協(xié)議
Http 底層基于 TCP 協(xié)議,分為請(qǐng)求和響應(yīng)。請(qǐng)求和響應(yīng)分別有各自的 Header 和 Body 組成。Header 里面通常是本次請(qǐng)求 / 響應(yīng)的描述信息,比如版本號(hào)、長(zhǎng)度、UA、Content-Type 等等,而 Body 里面通常就是我們要傳遞的業(yè)務(wù)數(shù)據(jù)了,下面分別瀏覽一下請(qǐng)求和響應(yīng)的內(nèi)容。
1.1 Http 請(qǐng)求
請(qǐng)求頭的內(nèi)容有很多,這里給大家做一個(gè)記錄當(dāng)做資料,不需要都記住,在實(shí)際使用中用到可以過(guò)來(lái)查閱即可。
Header | 解釋 | 示例 |
---|---|---|
Accept | 指定客戶端能夠接收的內(nèi)容類型 | Accept: text/plain, text/html |
Accept-Charset | 瀏覽器可以接受的字符編碼集。 | Accept-Charset: iso-8859-5 |
Accept-Encoding | 指定瀏覽器可以支持的web服務(wù)器返回內(nèi)容壓縮編碼類型。 | Accept-Encoding: compress, gzip |
Accept-Language | 瀏覽器可接受的語(yǔ)言 | Accept-Language: en,zh |
Accept-Ranges | 可以請(qǐng)求網(wǎng)頁(yè)實(shí)體的一個(gè)或者多個(gè)子范圍字段 | Accept-Ranges: bytes |
Authorization | HTTP授權(quán)的授權(quán)證書 | Authorization: Basic QWxhZIRpbjpvcGAuIHNlc2FtZQ== |
Cache-Control | 指定請(qǐng)求和響應(yīng)遵循的緩存機(jī)制 | Cache-Control: no-cache |
Connection | 表示是否需要持久連接。(HTTP 1.1默認(rèn)進(jìn)行持久連接) | Connection: close |
Cookie | HTTP請(qǐng)求發(fā)送時(shí),會(huì)把保存在該請(qǐng)求域名下的所有cookie值一起發(fā)送給web服務(wù)器。 | Cookie: $Version=1; Skin=new; |
Content-Length | 請(qǐng)求的內(nèi)容長(zhǎng)度 | Content-Length: 348 |
Content-Type | 請(qǐng)求的與實(shí)體對(duì)應(yīng)的MIME信息 | Content-Type: application/x-www-form-urlencoded |
Date | 請(qǐng)求發(fā)送的日期和時(shí)間 | Date: Tue, 15 Nov 2010 08:12:31 GMT |
Expect | 請(qǐng)求的特定的服務(wù)器行為 | Expect: 100-continue |
From | 發(fā)出請(qǐng)求的用戶的Email | From: user@email.com |
Host | 指定請(qǐng)求的服務(wù)器的域名和端口號(hào) | Host: http://idcbgp.cn/wiki/androidlesson/ |
If-Match | 只有請(qǐng)求內(nèi)容與實(shí)體相匹配才有效 | If-Match: “737060cd8c284d8af7ad3082f209582d” |
If-Modified-Since | 如果請(qǐng)求的部分在指定時(shí)間之后被修改則請(qǐng)求成功,未被修改則返回304代碼 | If-Modified-Since: Sat, 29 Oct 2010 19:43:31 GMT |
If-None-Match | 如果內(nèi)容未改變返回304代碼,參數(shù)為服務(wù)器先前發(fā)送的Etag,與服務(wù)器回應(yīng)的Etag比較判斷是否改變 | If-None-Match: “737060cd8c284d8af7ad3082f209582d” |
If-Range | 如果實(shí)體未改變,服務(wù)器發(fā)送客戶端丟失的部分,否則發(fā)送整個(gè)實(shí)體。參數(shù)也為Etag | If-Range: “737060cd8c284d8af7ad3082f209582d” |
If-Unmodified-Since | 只在實(shí)體在指定時(shí)間之后未被修改才請(qǐng)求成功 | If-Unmodified-Since: Sat, 29 Oct 2010 19:43:31 GMT |
Max-Forwards | 限制信息通過(guò)代理和網(wǎng)關(guān)傳送的時(shí)間 | Max-Forwards: 10 |
Pragma | 用來(lái)包含實(shí)現(xiàn)特定的指令 | Pragma: no-cache |
Proxy-Authorization | 連接到代理的授權(quán)證書 | Proxy-Authorization: Basic QWxhZGbpbjpAcGVuIHNlc2FtZQ== |
Range | 只請(qǐng)求實(shí)體的一部分,指定范圍 | Range: bytes=500-999 |
Referer | 先前網(wǎng)頁(yè)的地址,當(dāng)前請(qǐng)求網(wǎng)頁(yè)緊隨其后,即來(lái)路 | http://idcbgp.cn/wiki/androidlesson/ |
TE | 客戶端愿意接受的傳輸編碼,并通知服務(wù)器接受接受尾加頭信息 | TE: trailers,deflate;q=0.5 |
Upgrade | 向服務(wù)器指定某種傳輸協(xié)議以便服務(wù)器進(jìn)行轉(zhuǎn)換(如果支持) | Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11 |
User-Agent | User-Agent的內(nèi)容包含發(fā)出請(qǐng)求的用戶信息 | User-Agent: Mozilla/5.0 (Linux; X11) |
Via | 通知中間網(wǎng)關(guān)或代理服務(wù)器地址,通信協(xié)議 | Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1) |
Warning | 關(guān)于消息實(shí)體的警告信息 | Warn: 199 Miscellaneous warning |
1.2 Http 響應(yīng)
響應(yīng)就是服務(wù)器收到我們的 request 之后給我們返回的數(shù)據(jù),同樣記錄一下響應(yīng)頭:
Header | 解釋 | 示例 |
---|---|---|
Accept-Ranges | 表明服務(wù)器是否支持指定范圍請(qǐng)求及哪種類型的分段請(qǐng)求 | Accept-Ranges: bytes |
Age | 從原始服務(wù)器到代理緩存形成的估算時(shí)間(以秒計(jì),非負(fù)) | Age: 12 |
Allow | 對(duì)某網(wǎng)絡(luò)資源的有效的請(qǐng)求行為,不允許則返回405 | Allow: GET, HEAD |
Cache-Control | 告訴所有的緩存機(jī)制是否可以緩存及哪種類型 | Cache-Control: no-cache |
Content-Encoding | web服務(wù)器支持的返回內(nèi)容壓縮編碼類型 | Content-Encoding: gzip |
Content-Language | 響應(yīng)體的語(yǔ)言 | Content-Language: en,zh |
Content-Length | 響應(yīng)體的長(zhǎng)度 | Content-Length: 348 |
Content-Location | 請(qǐng)求資源可替代的備用的另一地址 | Content-Location: /index.htm |
Content-MD5 | 返回資源的MD5校驗(yàn)值 | Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ== |
Content-Range | 在整個(gè)返回體中本部分的字節(jié)位置 | Content-Range: bytes 21010-47021/47022 |
Content-Type | 返回內(nèi)容的MIME類型 | Content-Type: text/html; charset=utf-8 |
Date | 原始服務(wù)器消息發(fā)出的時(shí)間 | Date: Tue, 15 Nov 2010 08:12:31 GMT |
ETag | 請(qǐng)求變量的實(shí)體標(biāo)簽的當(dāng)前值 | ETag: “737060cd8c284d8af7ad3082f209582d” |
Expires | 響應(yīng)過(guò)期的日期和時(shí)間 | Expires: Thu, 01 Dec 2010 16:00:00 GMT |
Last-Modified | 請(qǐng)求資源的最后修改時(shí)間 | Last-Modified: Tue, 15 Nov 2010 12:45:26 GMT |
Location | 用來(lái)重定向接收方到非請(qǐng)求URL的位置來(lái)完成請(qǐng)求或標(biāo)識(shí)新的資源 | Location: http://idcbgp.cn/wiki/androidlesson/ |
Pragma | 包括實(shí)現(xiàn)特定的指令,它可應(yīng)用到響應(yīng)鏈上的任何接收方 | Pragma: no-cache |
Proxy-Authenticate | 它指出認(rèn)證方案和可應(yīng)用到代理的該URL上的參數(shù) | Proxy-Authenticate: Basic |
2. HttpUrlConnection 接口
Android 系統(tǒng)為我們提供了 HttpUrlConnection 接口用于實(shí)現(xiàn) Http 請(qǐng)求。自從 Android API
9 開(kāi)始,HttpUrlConnection 就成為了 Android App 推薦使用的內(nèi)置 Http 庫(kù)。使用它無(wú)需添加任何依賴,打開(kāi)網(wǎng)絡(luò)權(quán)限:
<uses-permission android:name="android.permission.INTERNET" />
就可以訪問(wèn) Http 資源了,可以說(shuō)相比第三方框架
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.
官方文檔沒(méi)有對(duì) Http 協(xié)議本身做什么解釋(如果對(duì) Http 協(xié)議不太了解的同學(xué),可以參考慕課網(wǎng)上網(wǎng)絡(luò)相關(guān)課程),主要是圍繞 HttpUrlConnection 的用法展開(kāi)了一步步的描述,結(jié)合官網(wǎng)的解釋以及我個(gè)人的總結(jié),大體上可以分為一下幾步:
- 通過(guò)
openConnection()
方法創(chuàng)建一個(gè)HttpURLConnection
:
URL url = new URL(https://www.baidu.com);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
首先創(chuàng)建一個(gè) URL 對(duì)象,參數(shù)就是我們要打開(kāi)的地址,然后使用 url 對(duì)象的openConnection()
來(lái)打開(kāi) Http 連接,拿到一個(gè)HttpURLConnection
對(duì)象。
2. 設(shè)置 Http 請(qǐng)求類型
設(shè)置本次 Http 請(qǐng)求的方法類型,Http 有以下幾種類型:
- GET
- POST
- HEAD
- CONNECT
- OPTIONS
- TRACE
- PATCH
- PUT
- **DELETE
這里就不做詳細(xì)的解釋了,可自行百度。最常用的就是前兩種:GET
和POST
:
conn.setRequestMethod("GET");
- 設(shè)置 Http 相關(guān)參數(shù)
這一步主要是設(shè)置請(qǐng)求頭的參數(shù),我們前面那張大表就可以派上用場(chǎng)了。此時(shí)可以設(shè)置 Cookie、Content-Type、超時(shí)時(shí)間等等參數(shù)。比如設(shè)置超時(shí)時(shí)間為 3 秒:
conn.setConnectTimeout(3*1000);
conn.setWirteTimeout(3 * 1000);
- 獲取輸入流
通過(guò)getInputStream()
方法獲取網(wǎng)絡(luò)輸入流,此后可以通過(guò)此對(duì)象獲取網(wǎng)絡(luò)數(shù)據(jù),如下:
InputStream in = conn.getInputStream();
- 關(guān)閉流
網(wǎng)絡(luò)流比較消耗資源,在使用完畢之后一定要將 Http 連接關(guān)掉:
conn.disconnect();
4. HttpURLConnection使用示例
還記得前面將現(xiàn)場(chǎng)的時(shí)候提到過(guò),Android 系統(tǒng)規(guī)定只能在主線程操作 UI,這里再加上一條:
Android 系統(tǒng)不能在 UI 線程訪問(wèn)網(wǎng)絡(luò)
所以我們需要開(kāi)啟一個(gè)子線程處理 Http 請(qǐng)求,這一節(jié)我們使用 40 節(jié)學(xué)習(xí)的 AsyncTask 來(lái)執(zhí)行網(wǎng)絡(luò)請(qǐng)求,拉取慕課網(wǎng) Android 教程的首頁(yè)信息。
4.1 后臺(tái)網(wǎng)絡(luò)任務(wù)
首先開(kāi)啟一個(gè) AsyncTask,在后臺(tái)打開(kāi)一個(gè) HttpURLConnection,地址是http://idcbgp.cn/wiki/androidlesson,接著設(shè)置相應(yīng)參數(shù),然后獲取網(wǎng)絡(luò)數(shù)據(jù)輸入流,即可讀到頁(yè)面信息了。
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://idcbgp.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 首頁(yè)布局
HTTPURLConnection 需要一個(gè)觸發(fā)時(shí)機(jī),所以在首頁(yè)布局上我們放置一個(gè) Button 用于觸發(fā) http 請(qǐng)求:
<?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="發(fā)起 Http 請(qǐng)求" />
</LinearLayout>
4.3 MainActivity編寫
核心邏輯是在 HttpTask 里面,MainActivity 里面主要是設(shè)置觸發(fā)按鈕監(jiān)聽(tīng)器,然后在點(diǎn)擊事件中啟動(dòng) 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 中我們的代碼是運(yùn)行在doInBackground()
方法中,但是啟動(dòng) AsyncTask 的方法是調(diào)用eexecute()
。
編譯運(yùn)行界面如下:
點(diǎn)擊“發(fā)起 HTTP 請(qǐng)求”啟動(dòng) AsyncTask 啟動(dòng) http 連接,然后輸入接收到的數(shù)據(jù)到 Logcat,過(guò)濾“HttpTask”字段,觀察日志:
以上是日志的片段,可以看到 Logcat 中的內(nèi)容就是http://idcbgp.cn/wiki/androidlesson頁(yè)面的源代碼,到此就獲取成功了。
5. 小結(jié)
本節(jié)講述了如何在 Android 中發(fā)起 http 請(qǐng)求,HttpURLConnection 的使用方式非常簡(jiǎn)單,但是需要你對(duì) http 協(xié)議本身有一定的了解,作為開(kāi)發(fā)者這個(gè)是必備的知識(shí)點(diǎn)。當(dāng)然對(duì)于大型項(xiàng)目而言,需要涉及到 DNS、緩存、安全校驗(yàn)等等高級(jí)操作,這時(shí)候 HttpURLConnection 會(huì)顯得有點(diǎn)輸出乏力,這就需要第三方的框架出場(chǎng)了,萬(wàn)變不離其宗,所有的框架到了底層其實(shí)都是一樣的基本原理,所以還是希望大家能夠熟悉 http 協(xié)議,并掌握 HttpURLConnection 的用法。