http请求过程 Android,android HTTP网络请求回顾
1.HTTP協(xié)議了解
http是一種應(yīng)用層的協(xié)議,底層通過TCP來進(jìn)行可靠的數(shù)據(jù)傳輸。HTTP是基于TCP的應(yīng)用層協(xié)議,它在更高的層次封裝了TCP的使用細(xì)節(jié),使網(wǎng)絡(luò)請求更加易用,TCP連接是因特網(wǎng)基于流的一種可靠連接,它為HTTP提供了一條可靠的比特傳輸管道。從TCP連接一端填入的字節(jié)會從另一端以原有的順序、正確的傳送過來。
HTTP的7種請求方式
GET
POST
DELETE
PUT
HEAD
TRACE
POTIONS
[圖片上傳失敗...(image-aa15fa-1517135394572)]
HTTP報文格式解析
不同的請求方式,它們的請求格式也是不一樣的,請求格式也就是報文格式。 通常來說一個HTTP請求報文由 請求行(request line)、請求頭部(head)、空行、請求數(shù)據(jù) 4個部分組成。
[圖片上傳失敗...(image-fbc600-1517135394572)]
請求行
報文的第一行就是請求行,這一行說明了這段報文以什么方式請求,包含了HTTP的版本號等一些協(xié)議信息。
請求頭部
請求頭部是以 key:value的形式來說明請求數(shù)據(jù)的,這里面說明了請求服務(wù)器的一些host,content-tye,Encoding的一些說明。
請求數(shù)據(jù)
POST請求的方式才會有請求數(shù)據(jù),如果請求是以POST提交過來,那么這里面就會有post的請求數(shù)據(jù),可以文本形式或者二進(jìn)制數(shù)據(jù)根據(jù)你請求post提交的數(shù)據(jù)類型決定。
GET 請求報文格式
www.jinweime.com?id=2
這個是一個典型的get請求,get請求的參數(shù)會跟在url后面。問號后面作為第一個參數(shù),以&進(jìn)行參數(shù)拼接。
http請求協(xié)議如下
GET /?id=2 HTTP/1.1
Host: jinweime.com
Cache-Control: no-cache
可以看到第一行為請求行,請求方式為GET,子路徑是?id=2 代表參數(shù)id的值為2,HTTP版本為1.1。 后面二行是head區(qū)域,第一個請求頭是主機(jī)地址,第三行也是一個head。GET方式的請求參數(shù)都是附加的URL中所以請求數(shù)據(jù)部分為空。
POST請求報文格式
POST /api/feed. HTTP/1.1
Accept-Encoding: gzip
Content-Length: 225873
Content-Ttpe: multipart/form-data; boundary=Ocxx3329f....
Host: www.myhost.com
Connection: Keep-Alive
--OCxxqFJE...
Content-Dispotition: form-data; name=="username"
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
MrSimple
--Cxxii32F..
Content-Dispotition: form-data; name=="title"
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
TEST
--Ffsaxx......--
這串報代表向 www.myhost.com/api/feed/這個地址發(fā)送了一個POST請求。 請求的數(shù)據(jù)格式為 Content-Type: multipart.form-data,報文有二個參數(shù) username title,username的值為MrSimple。title的值為TEST。 最后一行是結(jié)束行以 -- boundary值 -- 結(jié)束, 如果格式不正確服務(wù)器將會解析不到的你請求。
響應(yīng)報文
HTTP響應(yīng)報文也是由三個部分組成,分別是:狀態(tài)行 消息head 響應(yīng)報文,和請求報文的格式十分相似。
可以看到和請求報文相比,只是把第一行的請求行換成了狀態(tài)行了,狀態(tài)行提供一個狀態(tài)碼來說明此次請求的資源情況。
HTTP-Version Status-Code Reason-Phrase CRLF
其中的HTTP-Version表示服務(wù)器HTTP協(xié)議的版本,Status-Code表示服務(wù)器響應(yīng)請求的狀態(tài)碼;Reason-Phrase表示狀態(tài)碼的文本描述。狀態(tài)碼是一個三位的數(shù)字,第一個數(shù)字定義了響應(yīng)的類別。
常見的狀態(tài)碼
200 OK;客戶端請求成功
400 Bad Request:客戶端請求有語法錯誤,服務(wù)器不能正確的解析
401 Unauthorized;請求未授權(quán)
403 Forbidden; 服務(wù)器收到請求,但是拒絕提供服務(wù)
404 Not Found; 請求的資源不存在, 比如輸入了錯誤的地址;
500 Internal Server Error; 服務(wù)器發(fā)生了錯誤
503 Server Unavailable; 服務(wù)器當(dāng)前不能處理客戶端請求
常見的請求頭部
Content-Type: 請求數(shù)據(jù)的格式
Content-Length; 消息的長度
Host: 請求主機(jī)名
User-Agent; 發(fā)出請求的瀏覽器類型,可以自定義
Accept: 客戶端可識別的內(nèi)容類型
Accept-Encoding: 客戶端可識別的數(shù)據(jù)編碼
Connection: 允許客戶端和服務(wù)器指定請求/響應(yīng)連接有關(guān)的選項,比如設(shè)置Keep-Alive 表示保持連接
Android中執(zhí)行網(wǎng)絡(luò)請求
android中提供了二種執(zhí)行網(wǎng)絡(luò)請求的方式,一種使用 Apache的HttpClient,另一種Java提供的 HttpUrlConnection。這二種方法都提供了完整的 API, 都很很好的實現(xiàn)對網(wǎng)絡(luò)的請求功能,但是某些情況下我們需要做取舍分清楚二種方式的區(qū)別。
HttpClient
android SDK自帶了 Apache的HttpClient,它提供了對HTTP協(xié)議的全面支持,可以使用HttpClient來執(zhí)行 HTTP GET和HTTP POST請求。
HttpUrlConnection
最佳選擇HttpUrlConnection。二者對比來說,在android 2.2版本之前,HttpClient有較少的一些BUG,而HttpURLConnection一直存在一些讓厭煩的BUG,比如在對一個可讀的InputStream 調(diào)用colse()方法時,就有可能導(dǎo)致連接池失敗。因此在 android 2.2版本之前使用 HttpClient是比較好的選擇。 但是在 android2.3及之后 HttpUrlConnect有了進(jìn)一步的更新, 它api 簡單,體積小,因此非常適用于 Android項目中。 HttpUrlConnection的壓縮和緩存機(jī)制可以有效的減少網(wǎng)絡(luò)訪問的流量,這塊在提升手機(jī)省電和流量方面也起來很多的作用。另外在Android 6.0中,HttpClient以及被移除了,所以以后開發(fā)中HttpUrlConnection是我們唯一的選擇了。
使用HttpUrlConnection請求
fun sendHttpClient(url: String) {
val url = URL(url)
var conn = url.openConnection() as HttpURLConnection
//讀取時時間為 2s
conn.readTimeout = 2000
//請求超時時間為 5s
conn.connectTimeout = 5000
//設(shè)置請求方式
conn.requestMethod = "POST"
//接收輸入流
conn.doInput = true
//啟動輸出流,需要傳遞參數(shù)時需要開啟
conn.doInput = true
//添加 Header
conn.setRequestProperty("Connection", "Keep-Alive")
//添加請求參數(shù)
var paramsList = ArrayList()
paramsList.add(BasicNameValuePair("username", "jinwei"))
paramsList.add(BasicNameValuePair("pwd", "pwd.com"))
writeParams(conn.outputStream, paramsList)
//發(fā)起請求
conn.connect()
var input = conn.inputStream
//獲取結(jié)果
var str = convertStreamToString(input)
Log.i(TAG, "Request Data: " + str)
input.close()
}
fun writeParams(outpit: OutputStream, paramsList: List) {
val paramStr = StringBuffer()
for (value in paramsList) {
if (!TextUtils.isEmpty(paramStr)) {
paramStr.append("&")
}
paramStr.append(URLEncoder.encode(value.name, "UTF-8"))
paramStr.append("=")
paramStr.append(URLEncoder.encode(value.value, "UTF-8"))
}
var writer = BufferedWriter(OutputStreamWriter(outpit, "UTF-8"))
//寫入?yún)?shù)寫入輸入流
Log.i(TAG, paramStr.toString())
writer.write(paramStr.toString())
writer.flush()
writer.close()
}
fun convertStreamToString(input: InputStream): String {
var buffer = BufferedReader(InputStreamReader(input))
var sb = StringBuffer()
var line: String
try {
while (true) {
line = buffer.readLine()
if (!TextUtils.isEmpty(line)) {
sb.append(line + "\n")
} else {
break
}
}
} catch (e: IOException) {
e.printStackTrace()
}
return sb.toString()
}
2. Volley使用
安靜
volley的架構(gòu)圖
架構(gòu)圖
volley是2013年 google I/O大會上推出的一款網(wǎng)絡(luò)請求相關(guān)的框架
它有以下好處
網(wǎng)絡(luò)請求的自動調(diào)度。
多個并發(fā)網(wǎng)絡(luò)連接。
具有標(biāo)準(zhǔn)HTTP緩存一致性的透明磁盤和內(nèi)存響應(yīng)緩存。
支持請求優(yōu)先級。
取消請求api。可以取消單個請求,也可以設(shè)置要取消的請求的塊或范圍。
自定義重試
缺點
不適合數(shù)據(jù)量過大的傳輸操作
構(gòu)建一個stringRequest
一般來說我們一個應(yīng)用啟動后只需要全局獲取一個
Volley.newRequestQueue(this)實例就夠了,這樣可以有效的節(jié)省系統(tǒng)資源的消耗。
fun sendStringRequest() {
var request = Volley.newRequestQueue(this)
var url = "http://baidu.com"
var strrequest = StringRequest(Request.Method.GET, url,
object : Response.Listener {
override fun onResponse(response: String?) {
Log.i(TAG, response)
}
},
object : Response.ErrorListener {
override fun onErrorResponse(error: VolleyError?) {
Log.i(TAG, "error")
}
})
request.add(strrequest)
}
很簡單成功的打印了返回的Html數(shù)據(jù)
JsonRequest
這里我們用的JsonRequest的子類JsonObjectRequest來構(gòu)建了一個請求,它支持JSON格式的Request和Response。
fun sendJsonRequest() {
var request = Volley.newRequestQueue(this)
var url = "http://baike.baidu.com/api/openapi/BaikeLemmaCardApi?scope=103&format=json&appid=379020&bk_key=關(guān)鍵字&bk_length=600"
var strrequest = JsonObjectRequest(Request.Method.POST, url, null,
object : Response.Listener {
override fun onResponse(response: JSONObject?) {
Log.i(TAG, response.toString())
}
},
object : Response.ErrorListener {
override fun onErrorResponse(error: VolleyError?) {
Log.i(TAG, "error")
}
})
request.add(strrequest)
}
成功的返回了一串JSON格式的數(shù)據(jù)
{"id":390935,"subLemmaId":390935,"newLemmaId":7105697,"key".......
ImageRequest
Volley還支持對圖片的獲取
fun sendImageRequest() {
var request = Volley.newRequestQueue(this)
var url = "https://img-my.csdn.net/uploads/201603/26/1458988468_5804.jpg"
var strrequest = ImageRequest(url,
object : Response.Listener {
override fun onResponse(response: Bitmap?) {
findViewById(R.id.imageView).setImageBitmap(response)
}
}, 511, 511, Bitmap.Config.ARGB_8888,
object : Response.ErrorListener {
override fun onErrorResponse(error: VolleyError?) {
Log.i(TAG, "error")
}
})
request.add(strrequest)
}
這段代碼成功的Bitmap顯示到了ImageView上面
3. Volley源碼分析
執(zhí)行
var request = Volley.newRequestQueue(this)
最終會到newRequestQueue方法
public static RequestQueue newRequestQueue(Context context, BaseHttpStack stack) {
BasicNetwork network;
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
network = new BasicNetwork(new HurlStack());
} else {
// Prior to Gingerbread, HttpUrlConnection was unreliable.
// See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
// At some point in the future we'll move our minSdkVersion past Froyo and can
// delete this fallback (along with all Apache HTTP code).
String userAgent = "volley/0";
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
}
network = new BasicNetwork(
new HttpClientStack(AndroidHttpClient.newInstance(userAgent)));
}
} else {
network = new BasicNetwork(stack);
}
return newRequestQueue(context, network);
}
可以看到代碼進(jìn)入到Build.VERSION.SDK_INT >= 9的邏輯,network = new BasicNetwork(new HurlStack());創(chuàng)建了一個network對象,我們重點關(guān)系HrlStack()這個對象,這個對象是最終執(zhí)行網(wǎng)絡(luò)請求的地方。
@Override
public HttpResponse executeRequest(Request> request, Map additionalHeaders)
throws IOException, AuthFailureError {
String url = request.getUrl();
// Log.i("jinwei"," ## executeRequest = "+url);
HashMap map = new HashMap<>();
map.putAll(request.getHeaders());
map.putAll(additionalHeaders);
if (mUrlRewriter != null) {
String rewritten = mUrlRewriter.rewriteUrl(url);
if (rewritten == null) {
throw new IOException("URL blocked by rewriter: " + url);
}
url = rewritten;
}
URL parsedUrl = new URL(url);
HttpURLConnection connection = openConnection(parsedUrl, request);
for (String headerName : map.keySet()) {
connection.addRequestProperty(headerName, map.get(headerName));
}
setConnectionParametersForRequest(connection, request);
// Initialize HttpResponse with data from the HttpURLConnection.
int responseCode = connection.getResponseCode();
if (responseCode == -1) {
// -1 is returned by getResponseCode() if the response code could not be retrieved.
// Signal to the caller that something was wrong with the connection.
throw new IOException("Could not retrieve response code from HttpUrlConnection.");
}
if (!hasResponseBody(request.getMethod(), responseCode)) {
return new HttpResponse(responseCode, convertHeaders(connection.getHeaderFields()));
}
return new HttpResponse(responseCode, convertHeaders(connection.getHeaderFields()),
connection.getContentLength(), inputStreamFromConnection(connection));
}
通過代碼可以看到執(zhí)行網(wǎng)絡(luò)請求使用的HttpURLConnection處理的,具體的調(diào)用過程我們繼續(xù)分析。
newRequestQueue方法最好執(zhí)行到了return newRequestQueue(context, network);
private static RequestQueue newRequestQueue(Context context, Network network) {
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}
在這里執(zhí)行了queue.start()方法
public void start() {
stop(); // Make sure any currently running dispatchers are stopped.
// Create the cache dispatcher and start it.
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
// Create network dispatchers (and corresponding threads) up to the pool size.
Log.i("jinwei"," ## mDispatchers.length ## " +mDispatchers.length);
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
這里總共開啟了5個 Thread,一個CacheThread和四個NetWorkThread。
NetWorkThread的run方法
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
try {
Log.i("jinwei"," ## run ##");
processRequest();
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
Log.i("jinwei"," ## mQuit ##");
if (mQuit) {
return;
}
}
}
}
這里是一個while循環(huán),內(nèi)部有一個PriorityBlockingQueue隊列take數(shù)據(jù),如果返回為Null線程就會掛起等待新的隊列進(jìn)來。
重點方法
private void processRequest() throws InterruptedException {
long startTimeMs = SystemClock.elapsedRealtime();
// Take a request from the queue.
Request> request = mQueue.take();
try {
request.addMarker("network-queue-take");
// If the request was cancelled already, do not perform the
// network request.
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
request.notifyListenerResponseNotUsable();
return;
}
addTrafficStatsTag(request);
// Perform the network request.
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
// If the server returned 304 AND we delivered a response already,
// we're done -- don't deliver a second identical response.
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
request.notifyListenerResponseNotUsable();
return;
}
// Parse the response here on the worker thread.
Response> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
// Write to cache if applicable.
// TODO: Only update cache metadata instead of entire record for 304s.
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
// Post the response back.
request.markDelivered();
mDelivery.postResponse(request, response);
request.notifyListenerResponseReceived(response);
} catch (VolleyError volleyError) {
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
parseAndDeliverNetworkError(request, volleyError);
request.notifyListenerResponseNotUsable();
} catch (Exception e) {
VolleyLog.e(e, "Unhandled exception %s", e.toString());
VolleyError volleyError = new VolleyError(e);
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
mDelivery.postError(request, volleyError);
request.notifyListenerResponseNotUsable();
}
}
Request> request = mQueue.take()取出之前request.add(strrequest)的數(shù)據(jù),如果沒有則會一直掛起。
執(zhí)行這里就會執(zhí)行到之前HurlStack類中的executeRequest方法來通過HttpUrlConnection來構(gòu)建一個網(wǎng)絡(luò)請求了
NetworkResponse networkResponse = mNetwork.performRequest(request)
源碼的簡單分析就到這了,具體的設(shè)計思路和實現(xiàn)還需要深入研究了。
總結(jié)
以上是生活随笔為你收集整理的http请求过程 Android,android HTTP网络请求回顾的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c语言turboc图形代码,发个C代码(
- 下一篇: android自动启动某个程序,andr