天天看點

Android 網絡架構初探

文章目錄

    • 0x01 Android 上的網絡架構
        • 1、HttpClient
        • 2、Asynchronous HttpClient
        • 3、AFinal
        • 4、HttpURLConnection
        • 5、xUtils
        • 6、Volley
        • 7、OkHttp
        • 8、Retrofit
    • 0x02 Android 8.1 的網絡架構
        • 1、構造對象
        • 2、建立連接配接
        • 3、關閉連接配接

0x01 Android 上的網絡架構

Android一路走來的網絡架構變更【網絡協定:HTTP】

  • Android 2.2-:HttpClient
  • Android 2.3+:HttpURLConnection
  • Android 3.0:HttpURLConnection 增加了對 HTTPS 的支援
  • Android 4.0:HttpURLConnection 增加了對緩存的支援
  • Android 4.4+:HttpURLConnectio 底層引入OkHttp架構
  • Android 5.0+:OkHttp
在Android 2.2版本之前,HttpClient擁有較少的bug,是以使用它是最好的選擇。而在Android 2.3版本及以後,HttpURLConnection則是最佳的選擇。它的API簡單,體積較小,因而非常适用于Android項目。壓縮和緩存機制可以有效地減少網絡通路的流量,在提升速度和省電方面也起到了較大的作用。對于新的應用程式應該更加偏向于使用HttpURLConnection,因為在以後的工作當中我們也會将更多的時間放在優化HttpURLConnection上面。

1、HttpClient

高效穩定、維護成本高

用法:

  1. 建立 DefaultHttpClient 執行個體(HttpClient 是接口類)
  2. 建立 HttpGet/HttpPost 對象 request,傳入目标網絡位址
  3. 如果是 HttpPost 對象,還需要通過 setEntity() 方法設定請求參數
  4. 調用 client 執行個體的 execute() 方法,傳入 request 對象,傳回 HttpResponse 對象
  5. 通過 response 對象的 getStatusLine() 方法再調用 getStatusCode() 擷取狀态碼
  6. 通過 response 對象的 getEntity() 方法獲得 HttpEntity 對象,再通過 EntityUtils.toString() 方法解析傳回内容

2、Asynchronous HttpClient

基于 Apache HttpClient

請求在子線程中完成,在請求線程中傳回回調

使用了線程池

使用 RequestParams 類封裝了請求參數

持久化 cookies 到 SharedPreferences

支援檔案上傳

支援 json

支援 Http Basic Auth

用法:

  1. 執行個體化一個 AsynHttpClient 對象 client
  2. 編寫一個靜态 HttpClient 類,封裝了 get 和 post 方法
  3. get/post 方法中分别調用了 client 的 get/post 方法,傳入網址和參數,傳回 AsyncHttpResponseHandler 回調
  4. 如果需要儲存 cookie,執行個體化 PersistentCookieStore 類對象 cookieStore,并通過 client 的 setCookieStore() 方法設定
  5. 或者自己定義一個 BasicClientCookie 對象,通過 setXXX() 方法設定完屬性,再通過 cookieStore 的 addCookie() 方法添加
  6. 最後在回調函數中做狀态碼和傳回内容的處理

3、AFinal

基于 HttpClient(網絡層實作為 finalHTTP,是 HttpClient 的再次封裝)

開源的 ORM 和 IOC 應用開發架構

小巧靈活、代碼入侵量少

4、HttpURLConnection

輕便靈活易擴充

支援 Https、緩存

用法:

  1. 執行個體化一個 URL 對象,傳入目标網絡位址
  2. 擷取 HttpUrlConnection 執行個體,通過 URL 對象的 openConnection() 方法擷取
  3. 設定 http 請求的連接配接屬性,如 setRequestMethod() 設定請求方法,setConnectTimeout() 設定逾時時間
  4. 通過 getResponseCode() 擷取狀态碼
  5. 通過 getInputStream() 擷取伺服器傳回的輸入流
  6. 讀取内容并解析
  7. 通過 disconnect() 方法關閉目前連接配接

注意添加網絡通路權限

5、xUtils

3.0- 基于 HttpClient,3.0+ 基于 HttpURLConnection

基于 Afinal 開發的,目前功能比較完善的開源架構

6、Volley

Android 開發團隊2013年推出的一個網絡通信架構,Android 2.2- 使用 HttpClient, Android 2.3+ 使用 HttpURLConnection

通信封裝,支援加載圖檔,性能優化,适合資料量不大、通信頻繁的網絡

優勢在于處理小檔案的 Http 請求

支援處理高分辨率的圖像壓縮

支援使用 Okhttp 作為傳輸層

NetworkImageView 在 GC 的使用模式上更加保守,在請求清理上也更加積極

用法:

  1. 建立一個 RequestQueue 對象
  2. 建立一個 StringRequest 對象,傳入網址,響應消息的監聽 Listener 和 錯誤監聽 ErrorListener
  3. 如果是 post 方法,還需要傳入方法 Request.Method.POST,并重寫 getParams() 方法
  4. 如果是加載圖檔,則使用 ImageRequest 來建立 request 對象,傳入參數分别為網址、消息監聽、圖檔最大寬度和高度、顯示比例、顔色屬性、錯誤監聽
  5. 通過 add() 方法将 StringRequest 對象添加到 RequestQueue 對象中
  6. 在 onResponse(String response) 回調中處理響應消息或在 onErrorResponse(VolleyError error) 回調中處理錯誤

還可以使用 ImageLoader 來實作對圖檔的更多處理,如緩存、過濾,用法:

  1. 建立一個 RequestQueue 對象
  2. 建立一個 ImageLoader 對象 loader,傳入請求隊列、ImageCache 執行個體化對象
  3. ImageCache 是接口類,需要具體實作并重載 getBitmap() 和 putBitmap() 方法
  4. 調用 loader 的 get() 方法加載圖檔,傳入網址和 ImageListener 對象
  5. ImageListener 對象通過 ImageLoader 的 getImageListener() 方法擷取,傳入參數為顯示圖檔的 ImageView 對象,加載中和加載失敗需要顯示的圖檔資源
  6. 如果需要對圖檔大小進行限制,重載 get() 方法即可

自定義 Request,用法:

  1. 繼承自 Volley 的 Request 類,實作構造方法,并重寫 parseNetworkResponse 和 deliverResponse 方法

7、OkHttp

Java 的 Http + SPDY 用戶端開發包,支援 Android 2.3+

高效,支援 SPDY、連接配接池、Gzip、Http 緩存

自動處理網絡問題,如二次連接配接、SSL 握手

Android 4.4 開始 HttpURLConnection 底層實作采用 OkHttp

用法:

  1. 建立一個 OkHttpClient 對象 client
  2. 建立一個 Request 對象,通過 Request.Builder 的 url()、get()、post() 設定網址、請求方式和參數,通過 build() 方法傳回對象
  3. 調用 client 對象的 newCall() 方法傳入 request,然後調用 execute() 執行同步網絡請求,或 enqueue() 執行異步請求,并在回調中處理結果

8、Retrofit

性能最好,處理最快

适用于 REST API

傳輸層預設使用 OkHttp

支援 NIO

預設使用 Gson

用法:

  1. 将請求位址轉為接口,通過注解配置請求頭部、方法、參數、傳回值等資訊
  2. 建立一個 Retrofit 對象,通過 Retrofit.Builder 的 baseUrl() 等方法設定位址等資訊,再通過 build() 傳回對象
  3. 調用 retrofit 對象的 create() 方法傳回接口類執行個體化對象
  4. 通過接口類調用對應方法,擷取 Call 執行個體,調用 Call 對象的 execute() 或 enqueue() 執行同步或異步網絡請求

0x02 Android 8.1 的網絡架構

Android 8.1 使用的網絡架構為 HttpURLConnection,其底層實作依賴于 OkHttp

一個典型的 HttpURLConnection 網絡請求過程為:

HttpURLConnection connection = null;
InputStream inputStream = null;

try {
		// 1. 執行個體化一個 URL 對象,傳入目标網絡位址
    URL url = new URL(urlPath);
    // 2. 通過 URL 對象的 openConnection() 方法擷取 HttpUrlConnection 執行個體
    connection = (HttpURLConnection) url.openConnection();

		// 3. 設定 http 請求的連接配接屬性
    connection.setRequestMethod("GET");
    connection.setUseCaches(false);
    connection.setConnectTimeout(3000);
    connection.setReadTimeout(5000);
    connection.setDoInput(true);
  
  	// 4. 通過 getResponseCode() 擷取狀态碼
    if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
        // 5. 通過 getInputStream() 擷取伺服器傳回的輸入流
      	inputStream = connection.getInputStream();
      	
      	// 6. 讀取内容并解析
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] bytes = new byte[1024*512];
        int length = 0;
        while ((length = inputStream.read(bytes)) > -1) {
        byteArrayOutputStream.write(bytes, 0, length);
    }

    return byteArrayOutputStream.toString();
} catch (Exception e) {
		Log.e(TAG, "catch Exception = " + e.getMessage());
		e.printStackTrace();
} finally {
		if (connection != null) {
      	// 7. 通過 disconnect() 方法關閉目前連接配接
      	connection.disconnect();
      	connection = null;
    }
		if (inputStream != null) {
      	try {
          	inputStream.close();
          	inputStream = null;
        } catch (IOException e) {
          	Log.e(TAG, "catch IOException = " + e.getMessage());
          	e.printStackTrace();
        }
    }
} 
           

分析具體流程前,首先捋一下源碼的路徑和一些類繼承關系

HttpURLConnection 相關源碼對應不同版本的路徑:

  • Android 7.0 - 路徑為 /libcore/luni/src/main/java/java/net/
  • Android 7.0 + 路徑為 /libcore/ojluni/src/main/java/java/net/
  • okhttp 的相關源碼一直在 /external/okhttp/ 路徑下

相關類的繼承關系:

Android 網絡架構初探

接下來具體分析代碼調用流程:

1、構造對象

首先通過 URL 類的構造函數初始化 URL 類執行個體,然後調用這個執行個體的 openConnection() 方法,實際上調用了 openConnection(Proxy proxy)​,傳回 URLConnection 類型對象。

URL 的

openConnection(Proxy proxy)

裡面會調用 h a n d l e r . o p e n C o n n e c t i o n ( t h i s , p ) handler.openConnection(this, p) handler.openConnection(this,p), 其中的 handler 是 URLStreamHandler 類型的對象,通過 g e t U R L S t r e a m H a n d l e r ( S t r i n g p r o t o c o l ) getURLStreamHandler(String protocol) getURLStreamHandler(Stringprotocol) 設定,URLStreamHandler 是一個抽象類, o p e n C o n n e c t i o n ( U R L u , P r o x y p ) openConnection(URL u, Proxy p) openConnection(URLu,Proxyp) 的真正實作在其實作子類中,看一下 URLStreamHandler 是如何設定的

  1. 根據協定查找一個 URLStreamHandler 的哈希表;
  2. 通過 URLStreamHandlerFactory 對象的 createURLStreamHandler() 方法建立;

    【這個方法裡面重寫了 URLStreamHandler 類的 openConnection 方法,并且規定了 http/https 協定的預設端口 80/443】

  3. 拿到系統變量 java.protocol.handler.pkgs 的值通過 classloader 去查找對應類,然後去建立類執行個體;
  4. 根據不同的協定去建立不同的類執行個體
if (protocol.equals("file")) {
   		handler = new sun.net.www.protocol.file.Handler();
   } else if (protocol.equals("ftp")) {
   		handler = new sun.net.www.protocol.ftp.Handler();
   } else if (protocol.equals("jar")) {
   		handler = new sun.net.www.protocol.jar.Handler();
   } else if (protocol.equals("http")) {
   		handler = (URLStreamHandler)Class.
   		forName("com.android.okhttp.HttpHandler").newInstance();
   } else if (protocol.equals("https")) {
   		handler = (URLStreamHandler)Class.
   		forName("com.android.okhttp.HttpsHandler").newInstance();
   }
           

注意,這裡 Android 源碼才用了 jarjar-rules,即将以一個路徑開頭的包在編譯時打包成另一個路徑開頭的包,是以 com.android.okhttp.HttpHandler 對應的包應該是 com.squareup.okhttp.HttpHandler,https 的同理,是以 URLStreamHandler 的子類實作分别在 HttpHandler 和 HttpsHandler 中

HttpsHandler 繼承自 HttpHandler,openConnection(URL url, Proxy proxy) 的實作是調用了 newOkUrlFactory(proxy) 建立一個 OkUrlFactory 對象并調用它的 open(url) 方法。newOkUrlFactory() 裡面首先調用 createHttpOkUrlFactory(proxy) 建立一個 OkUrlFactory 對象,然後通過調用這個對象的 client() 擷取成員對象 OkHttpClient 的執行個體,接着通過調用 OkHttpClient 執行個體的 setConnectionPool() 去設定連接配接池。在 createHttpOkUrlFactory(proxy) 裡面首先建立一個 OkHttpClient 對象,并且設定逾時、重定向、流量加密這些屬性,然後傳入 client 參數建立一個 OkUrlFactory 對象并傳回。

OkUrlFactory 的 open(url) 方法會調用 open(url, client.getProxy()),這裡的 client 就是 OkHttpClient 對象。在 open(URL url, Proxy proxy) 中,首先 url.getProtocol() 擷取協定,是 http 還是 https,然後設定 client 的 proxy,最後根據協定傳回相應的實作類執行個體,HttpURLConnectionImpl 或 HttpsURLConnectionImpl。

URL#openConnection() 最終會傳回 HttpURLConnectionImpl 或 HttpsURLConnectionImpl 對象

Android 網絡架構初探

2、建立連接配接

HttpURLConnection 是一個抽象類,定義了許多配置連接配接屬性方法,定義了擷取相應消息碼和響應消息的方法,定義了 disconnect()、usingProxy() 抽象方法,還定義了傳回消息碼,大緻分為

2XX: generally "OK"
3XX: relocation/redirect
4XX: client error
5XX: server error
           

在建立連接配接的過程,HttpURLConnection 沒有顯式調用 connect(),而是直接去調用 getResponseCode() 方法,而 getResponseCode() 裡面會調用 getInputStream(),getInputStream() 方法中會間接實作了 connect()

看下 getInputStream() 這個方法,首次在 URLConnection 中定義,在 HttpURLConnectionImpl 重寫實作。這個方法裡面首先調用 getResponse() 傳回類型為響應消息的 HttpEngine 對象,然後通過此對象的 getResponse() 擷取傳回消息,然後調用 body() 擷取消息體,最後調用 byteStream() 傳回位元組流格式。

HttpEngine 的 getResponse() 方法傳回的是一個 Response 對象 userResponse(還有一個 cacheResponse 作用不詳),Response 的 body() 方法會傳回 ResponseBody 對象。

重點看下 getResponse() 如何傳回 HttpEngine 對象的。這個方法裡面首先調用 initHttpEngine() 初始化 HttpEngine 對象,接着調用 hasResponse() 判斷這個 HttpEngine 對象是否有響應消息,若有則直接将此對象傳回,否則會循環調用 execute(true),然後分别通過 httpEngine 的 getResponse() 方法擷取 Response 對象和 followUpRequest() 方法擷取 Request 對象,并調用 newHttpEngine(method, streamAllocation, (RetryableSink) requestBody, response)。

initHttpEngine() 裡面會設定連接配接為 true,然後調用 newHttpEngine(method, null, null, null) 建立 HttpEngine 對象并傳回;而 newHttpEngine(String method, …) 方法裡面會進一步構造一系列對象如 Request、OkHttpClient 等然後調用 HttpEngine() 構造函數。

execute(true) 裡面則會開始真正的建立連接配接過程:httpEngine 會調用 sendRequest() 發送請求,然後調用 getConnection() 擷取連接配接,最後調用 readResponse() 讀取相應消息。其中,route 和 handshake 都是通過 connection 的 getRoute() 和 getHandshake() 擷取。另外,此函數中會捕獲異常,然後通過 httpEngine 的 recover(e) 擷取 retryEngine 實作重試功能。

Android 網絡架構初探

再看下 getOutputStream() 這個方法,首先調用 connect(), 然後通過 httpEngine 的 getBufferedRequestBody() 擷取 BufferedSink 對象,最後調用 BufferedSink 對象的 outputStream() 方法傳回 OutputStream 對象。

connect() 裡面做的也是調用 initHttpEngine() 初始化 HttpEngine 和無限重複執行 execute()。

getBufferedRequestBody() 方法中會通過 getRequestBody() 擷取請求消息體,然後封裝到 BufferedSink 對象中傳回。

Android 網絡架構初探

3、關閉連接配接

disconnect() 方法定義在 HttpURLConnection 中實作在 HttpURLConnectionImpl 中,實際上調用了 HttpEngine 對象的 cancel() 方法

繼續閱讀