Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Volley源碼解析與Okhttp+Rxjava組合構建網絡庫

Volley源碼解析與Okhttp+Rxjava組合構建網絡庫

編輯:關於Android編程

概述

之前在討論組裡聽到許多討論okhttp的話題,可見okhttp是一個相對成熟的解決方案,看到android4.4後網絡訪問的源碼中HttpURLConnection已經替換成okhttp實現了,所以當時考慮來支持下,最近項目中新版應用效果良好,所以抽空來談談,順便記錄下.Volley源碼解析,Okhttp介紹,Rxjava組合.

1.使用 OkHttp 作為傳輸層的實現優點 支持SPDY(speedy,一種開放的網絡傳輸協議,用來發送網頁內容,基於傳輸控制協議(TCP)的應用層協議,SPDY協議通過壓縮、多路復用和優先級來縮短加載時間,SPDY當前並不是一個標准協議,但SPDY的開發組已經開始推動SPDY成為正式標准(現為互聯網草案[3]),HTTP/2主要以SPDY技術為主),最主要一點允許連接同一主機的所有請求分享一個socket。如果SPDY不可用,會使用連接池減少請求延遲。使用GZIP壓縮下載內容,且壓縮操作對用戶是透明的。利用響應緩存來避免重復的網絡請求。當網絡出現問題的時候,OKHttp會依然有效,它將從常見的連接問題當中恢復。如果你的服務端有多個IP地址,當第一個地址連接失敗時,OKHttp會嘗試連接其他的地址,這對IPV4和IPV6以及寄宿在多個數據中心的服務而言,是非常有必要的,OkHttp還處理了代理服務器問題和SSL握手失敗問題。

2.明確一點okhttp是與HttpClient,

HttpUrlConnection一樣作為網絡傳輸層的實現

 

 

當前Android提供了這兩種通信實現類,這兩者都支持HTTPS

,流的上傳和下載,配置超時,IPv6和連接池,已經足夠滿足我們大多數Http請求需求,但更高效的使用HTTP可以讓我們的應用運行更快、更節省流量,效率更高.在Android 2.2版本之前,HttpClient擁有較少的bug,因此使用它是最好的選擇。而在Android 2.3版本及以後,HttpURLConnection則是最佳的選擇。它的API簡單,體積較小,因而非常適用於Android項目。壓縮和緩存機制可以有效地減少網絡訪問的流量,在提升速度和省電方面也起到了較大的作用。官方應該是建議4.4後,采用Okhttp來實現,所以基於此認識,完善了當前的網絡庫.

 

3.改善思路

使用 OkHttp 無需重寫您程序中的網絡代碼。OkHttp實現了幾乎和java.net.HttpURLConnection一樣的API。如果用了 Apache HttpClient,則OkHttp也提供了一個對應的okhttp-apache 模塊,OkHttp使用起來不如Volley方便,緩存機制,請求隊列管理策略,異步回調,重試機制等,OkHttp的回調都是在工作線程,所以如果在回調裡面操作View的話,需要自己轉換到UI線程,非常繁瑣,所以需要封裝. Retrofit 2.0後默認使用的是OkHttp,2.0之前是可選的,本來打算用RxJava+Retrofit,當時因為這邊從早期都是基於volley基礎來實現的,包括後面根據項目需求,對volley進行了二次封裝,由於熟悉volley的源碼,底層設計,所以繼續基於當前庫進行擴展,順便打算加入RxJava進行線程調度 \ 上面是Volley的設計圖,相比使用過的都不陌生.RequestQueue類作為volley的核心類,可以說是連接請求與響應的橋梁,啟動一個請求隊列RequestQueue,只需要往這個RequestQueue不斷 add Request 即可,   我們可以基於自己業務需求繼承Request接口實現,ResultMapRequest當前項目架構下在用的,接口文檔實體類規范的模式,之前文章已經介紹過了,就不貼代碼了.在視圖層發起網絡請求,加入管理隊列,管理隊列RequestQueue在啟動的時候會啟動一個緩存調度器線程(CacheDispatcher),遍歷需要讀取緩存的請求隊列,阻塞處理,通過cache接口,獲取數據,以及啟動網絡調度器數組中的多條NetworkDispatcher,遍歷隊列,進行網絡請求操作,關鍵在此處,在這個過程中,調用網絡傳輸層的接口HttpStack去獲取數據,關於HttpStack的擴展實現,我們就是根據支持不同網絡傳輸層來實現.如上圖所示:HttpClientStack基於HttpClient實現,HurlStack基於HttpURLConnection,OkHttpStack就是基於我們的Okhttp來實現的,那麼來貼一下OkHttpStack的實現代碼解析下吧.
/**
 * OkHttp backed {@link HttpStack HttpStack} that does not
 * use okhttp-urlconnection
 * OkHttp的執行器,可用於替換原框架自帶的HttpUrlConnection執行器
 * 參考: https://gist.github.com/bryanstern/4e8f1cb5a8e14c202750
 * https://gist.github.com/ceram1/8254f7a68d81172c1669
 */
public class OkHttpStack implements HttpStack {

    private final OkHttpClient mClient;


    /**
     * Create a OkHttpStack with default OkHttpClient.
     */
    public OkHttpStack() {
        this.mClient=new OkHttpClient();
    }

    public OkHttpStack(OkHttpClient client) {
        this.mClient = client;
    }

    @Override
    public HttpResponse performRequest(Request request, Map additionalHeaders)
            throws IOException, AuthFailureError {

        OkHttpClient client = mClient.clone();
        int timeoutMs = request.getTimeoutMs();
        client.setConnectTimeout(timeoutMs, TimeUnit.MILLISECONDS);
        client.setReadTimeout(timeoutMs, TimeUnit.MILLISECONDS);
        client.setWriteTimeout(timeoutMs, TimeUnit.MILLISECONDS);
        //根據volley的請求request,生成okhttp的builder構建器
        //OkHttpUtils.get().url(url).build().execute(new CallBack());鏈式調用最後執行
        //全部對應操作都在Builder中,所有操作完成後返回當前最新的builder
        com.squareup.okhttp.Request.Builder okHttpRequestBuilder = new com.squareup.okhttp.Request.Builder();
        okHttpRequestBuilder.url(request.getUrl());//設置url
        KLog.i(request.getUrl().toString());
        Map headers = request.getHeaders();//添加request的header
        for (final String name : headers.keySet()) {
            okHttpRequestBuilder.addHeader(name, headers.get(name));
        }
        //添加額外自定義header
        for (final String name : additionalHeaders.keySet()) {
            okHttpRequestBuilder.addHeader(name, additionalHeaders.get(name));
        }
        //根據volley請求的request,設置對應的builder,請求類型
        setConnectionParametersForRequest(okHttpRequestBuilder, request);
        //執行後得到相應response
        com.squareup.okhttp.Request okHttpRequest = okHttpRequestBuilder.build();
        Call okHttpCall = client.newCall(okHttpRequest);//不能被執行2次,每次new
        Response okHttpResponse = okHttpCall.execute();
        //將得到的response,轉換為BasicHttpResponse返回
        return entityFromOkHttpResponse(okHttpResponse);
    }

    private static BasicHttpResponse entityFromOkHttpResponse(Response okHttpResponse) throws IOException {
        final int responseCode = okHttpResponse.code();
        //先判斷相應碼,為-1,IO異常,直接拋出異常
        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.");
        }

        StatusLine responseStatus = new BasicStatusLine(parseProtocol(okHttpResponse.protocol()), okHttpResponse.code(), okHttpResponse.message());
        BasicHttpResponse response = new BasicHttpResponse(responseStatus);

        //生成response相應的body實體
        BasicHttpEntity entity = new BasicHttpEntity();
        ResponseBody body = okHttpResponse.body();
        entity.setContent(body.byteStream());
        entity.setContentLength(body.contentLength());
        entity.setContentEncoding(okHttpResponse.header("Content-Encoding"));//從頭部獲取ContentEncoding
        if (body.contentType() != null) {
            entity.setContentType(body.contentType().type());
        }
        //設置ENTITY
        response.setEntity(entity);
        //遍歷響應消息頭部,將信息加入BasicHttpResponse裡面
        Headers responseHeaders = okHttpResponse.headers();
        for (int i = 0, len = responseHeaders.size(); i < len; i++) {
            final String name = responseHeaders.name(i), value = responseHeaders.value(i);
            if (name != null) {
                response.addHeader(new BasicHeader(name, value));
            }
        }
        KLog.i(response.getStatusLine());
        return response;
    }

    @SuppressWarnings("deprecation")
    private static void setConnectionParametersForRequest(com.squareup.okhttp.Request.Builder builder, Request request)
            throws IOException, AuthFailureError {
        switch (request.getMethod()) {
            case Request.Method.DEPRECATED_GET_OR_POST:
                // Ensure backwards compatibility.  Volley assumes a request with a null body is a GET.
                byte[] postBody = request.getPostBody();
                if (postBody != null) {
                    builder.post(RequestBody.create(MediaType.parse(request.getPostBodyContentType()), postBody));
                }
                break;
            case Request.Method.GET:
                builder.get();
                break;
            case Request.Method.DELETE:
                builder.delete();
                break;
            case Request.Method.POST:
                builder.post(createRequestBody(request));
                break;
            case Request.Method.PUT:
                builder.put(createRequestBody(request));
                break;
            case Request.Method.HEAD:
                builder.head();
                break;
            case Request.Method.OPTIONS:
                builder.method("OPTIONS", null);
                break;
            case Request.Method.TRACE:
                builder.method("TRACE", null);
                break;
            case Request.Method.PATCH:
                builder.patch(createRequestBody(request));
                break;
            default:
                throw new IllegalStateException("Unknown method type.");
        }
    }

    private static ProtocolVersion parseProtocol(final Protocol p) {
        switch (p) {
            case HTTP_1_0:
                return new ProtocolVersion("HTTP", 1, 0);
            case HTTP_1_1:
                return new ProtocolVersion("HTTP", 1, 1);
            case SPDY_3:
                return new ProtocolVersion("SPDY", 3, 1);
            case HTTP_2:
                return new ProtocolVersion("HTTP", 2, 0);
        }

        throw new IllegalAccessError("Unkwown protocol");
    }

    private static RequestBody createRequestBody(Request r) throws AuthFailureError {
        final byte[] body = r.getBody();
        if (body == null) return null;

        return RequestBody.create(MediaType.parse(r.getBodyContentType()), body);
    }
}
裡面有一些是我當時加的備注,在來簡單解析下,首先HttpStack,作為網絡傳輸層統一調用接口,就是在進行網絡操作的時候調用HttpStack.performRequest(Request) 入參我們發起的請求Request,最終處理返回Response,關鍵就是如何實現這個方法: 根據volley的請求request,生成okhttp的builder構建器,發送請求,處理返回結果後拋出. 我們先來了解下okhttp的簡單使用
Request request = new Request.Builder()
        .url("https://github.com/gongjr")
        .header("User-Agent", "OkHttp Headers.java")
        .addHeader("Accept", "application/json; q=0.5")
        .addHeader("Accept", "application/vnd.github.v3+json")
        .build();
    Response response = client.newCall(request).execute();
細心的朋友可能注意到上面使用了
Call okHttpCall = client.newCall(okHttpRequest);
每次new的方式,避免其被再次調用,因為retry機制在volley中,在上一層策略處理. \ 此Okhttp總體設計圖,來源其他博主,對深入了解的朋友可以看看源碼解析 簡單說,okhttp庫主要是通過Diapatcher不斷從RequestQueue中取出請求(Call),根據是否已緩存調用Cache或 Network這兩類數據獲取接口之一,從內存緩存或是服務器,得請求的數據。該引擎有同步和異步請求,同步請求通過Call.execute()直接返 回當前的Response. 前面部分在任何網絡庫中都會設計到,基本和volley請求策略設計是差不多的,但是關鍵一點是callback回來是在線程裡面, 不能刷新UI,線程調度管理,在發起與回調並沒有一體化處理,單獨使用Okhttp,還是需要額外線程調度處理,所以我們還是統一使用volley管理,當然也有習慣volley的原因O(∩_∩)O~. \ 從OkHttpClient類的整體設計來看,它采用門面模式來。client知曉子模塊的所有配置以及提供需要的參數。client會將所有從客戶端發來的請求委派到對應的子系統去處理。在該系統中,有多個子系統、類或者類的集合。例如上面的cache、連接以及連接池相關類的集合、網絡配置相關類集合等。每個子系統都可以被客戶端直接調 用,或者被門面角色調用。子系統並不知道門面的存在,對於子系統而言,門面僅僅是另外一個客戶端而已。同時,OkHttpClient可以看作是整個框架 的上下文。
通過類圖,其實很明顯反應了該框架的幾大核心子系統;路由、連接協議、攔截器、代理、安全性認證、連接池以及網絡適配。從client大大降低了開發者使用難度,同時非常明了的展示了該框架在所有需要的配置以及獲取結果的方式.裡面關於SPDY的方式,允許連接同一主機的所有請求分享一個socket實現,以及通過攔截器對Request 的body進行gzip的壓縮,來減少流量的傳輸等等. 樓主對Okhttp只是簡單了解,還需要不斷學習研究,就先簡單介紹下. OKHttp源碼位置:https://github.com/square/okhttp 4.Rxjava介紹,及處理異步調度  
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved