Okhttp3 使用和原理(DEMO)


 

基本使用:

http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0106/2275.html

http://www.jianshu.com/p/1873287eed87

http://blog.csdn.net/itachi85/article/details/51190687

 

一個最簡單的DEMO

public class OkHttp3BasicActivity extends Activity {

    @BindView(R.id.sn_tv)
    TextView tv;

    private OkHttpClient client ;

    String str = "";

    private Handler handler = new Handler()
    {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what)
            {
                case 1:
                    tv.setText(msg.obj.toString());
                    break;
            }
        }
    };


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.simple_network_main);

        ButterKnife.bind(this);

        new Thread(new Runnable() {
            @Override
            public void run() {
                okrun();
            }
        }).start();

    }

    private void okrun()
    {
        client = new OkHttpClient();

        httpUrl = httpUrl+"?"+httpArg;

        Request request = new Request.Builder()
                .url("http://publicobject.com/helloworld.txt")
                .build();

        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

                str = response.body().string();
                Message msg = Message.obtain();
                msg.what =1;
                msg.obj = str;
                handler.sendMessage(msg);
            }
        });

    }
}

 

兩個需要注意的點:

  1. okhttp3不能在ui線程中運行
  2. onresponse在子線程中,需要用handler回調才能操作ui

 

 

請求網絡原理解析:

http://liuwangshu.cn/application/network/7-okhttp3-sourcecode.html

HTTP請求執行流程分析

http://www.jianshu.com/p/230e2e2988e0

 

OkHttp3源碼詳解(二整體流程)

整個實現流程如下:

  • 創建OkHttpClient對象。OkHttpClient為網絡請求執行的一個中心,它會管理連接池,緩存,SocketFactory,代理,各種超時時間,DNS,請求執行結果的分發等許多內容。
  • 創建Request對象。Request用於描述一個HTTP請求,比如請求的方法是"GET"還是"POST",請求的URL,請求的header,請求的body,請求的緩存策略等。
  • 利用前面創建的OkHttpClient對象和Request對象創建Call對象。Call是一次HTTP請求的Task,它會執行網絡請求以獲得響應。OkHttp中的網絡請求執行Call既可以同步進行,也可以異步進行。調用call.execute()將直接執行網絡請求,阻塞直到獲得響應。而調用call.enqueue()傳入回調,則會將Call放入一個異步執行隊列,由ExecutorService在后台執行。
Call對象
如果使用enqueue方法,則調用dispatch.enqueue(),發送到線程池
如果使用execute,則不需要dispatch發送到線程池處理,直接同步處理。

線程池 executorService()

如果正在運行的異步任務隊列數量小於最大請求數,線程池調用execute()執行該任務,否則加入准備隊列

默認情況下,這是一個不限容量的線程池。但Dispatcher會限制每個host同時執行的最大請求數量,默認為5,同時也會限制同時執行的總的最大請求數量

用戶可以通過Dispatcher的構造函數來定制ExecutorService,這需要通過OkHttpClient.Builder在OkHttpClient的構建過程中間接的做到。

 

線程池execute()

由getResponseWithInterceptorChain()來執行網絡請求,得到response  

        Response response = getResponseWithInterceptorChain();

 成功后回調CallBack的onResponse方法  

          responseCallback.onResponse(RealCall.this, response); 

 

可以看到這里對回調接口是同步調用,也就是回調方法將在后台線程中被調用。

 

getResponseWithInterceptorChain()加上了一系列的interceptor,然后執行chain.proceed(request)

private Response getResponseWithInterceptorChain() throws IOException {  
  
    //構建全棧攔截器  
    List interceptors = new ArrayList<>();  
    interceptors.addAll(client.interceptors());//自定義攔截器  
    interceptors.add(retryAndFollowUpInterceptor);//重試攔截器  
    interceptors.add(new BridgeInterceptor(client.cookieJar()));//橋接攔截器  
    interceptors.add(new CacheInterceptor(client.internalCache()));//緩存攔截器  
    interceptors.add(new ConnectInterceptor(client));//連接攔截器  
    if (!retryAndFollowUpInterceptor.isForWebSocket()) {  
      interceptors.addAll(client.networkInterceptors());//用戶預定義的網絡攔截器  
    }  
    interceptors.add(new CallServerInterceptor(  
        retryAndFollowUpInterceptor.isForWebSocket()));//調用服務攔截器  
  
    //內部通過責任鏈模式來使用攔截器  
    Interceptor.Chain chain = new RealInterceptorChain(  
        interceptors, null, null, null, 0, originalRequest);  
  
    return chain.proceed(originalRequest);//獲取Response  
  }  
由此可見OkHttp中,Http請求的實際處理流程將大致如下圖這樣:

 

RetryAndFollowUpInterceptor

     重試與重定向攔截器,用來實現重試和重定向功能,內部通過while(true)死循環來進行重試獲取Response(有重試上限,超過會拋出異常)。followUpRequest主要用來根據響應碼來判斷屬於哪種行為觸發的重試和重定向(比如未授權,超時,重定向等),然后構建響應的Request進行下一次請求。當然,如果沒有觸發重新請求就會直接返回Response。

RetryAndFollowUpInterceptorintercept()中首先從client取得connection pool,用所請求的URL創建Address對象,並以此創建StreamAllocation對象。

RetryAndFollowUpInterceptor對重定向的響應也不會無休止的處理下去,它處理的最多的重定向級數為20次,超過20次時,它會拋異常出來。

總結一下RetryAndFollowUpInterceptor做的事情:

  1. 創建StreamAllocation對象,為后面流程的執行准備條件。
  2. 處理重定向的HTTP響應。
  3. 錯誤恢復。

 

BridgeInterceptor

橋接攔截器,用於完善請求頭

這個Interceptor做的事情比較簡單。可以分為發送請求和收到響應兩個階段來看。在發送請求階段,BridgeInterceptor補全一些http header,這主要包括Content-TypeContent-LengthTransfer-EncodingHostConnectionAccept-EncodingUser-Agent,還加載Cookie,隨后創建新的Request,並交給后續的Interceptor處理,以獲取響應。

而在從后續的Interceptor獲取響應之后,會首先保存Cookie。如果服務器返回的響應的content是以gzip壓縮過的,則會先進行解壓縮,移除響應中的header Content-EncodingContent-Length,構造新的響應並返回;否則直接返回響應。

 

CacheInterceptor

緩存攔截器,首先根據Request中獲取緩存的Response,然后根據用於設置的緩存策略來進一步判斷緩存的Response是否可用以及是否發送網絡請求(CacheControl.FORCE_CACHE因為不會發送網絡請求,所以networkRequest一定為空)。如果從網絡中讀取,此時再次根據緩存策略來決定是否緩存響應。

對於CacheInterceptor.intercept(Chain chain)的分析同樣可以分為兩個階段,即請求發送階段和響應獲取之后的階段。這兩個階段由chain.proceed(networkRequest)來分割。

在請求發送階段,主要是嘗試從cache中獲取響應,獲取成功的話,且響應可用未過期,則響應會被直接返回;否則通過后續的Interceptor來從網絡獲取,獲取到響應之后,若需要緩存的,則緩存起來。

關於HTTP具體的緩存策略這里暫時不再詳述。

RealCall.getResponseWithInterceptorChain()可見CacheInterceptor的cache同樣來自於OkHttpClient。OkHttp已經有實現Cache的整套策略,在Cache類,但默認情況下不會被用起來,需要自己在創建OkHttpClient時,手動創建並傳給OkHttpClient.Builder。


 

ConnectInterceptor

 連接攔截器,用於打開一個連接到遠程服務器。說白了就是通過StreamAllocation獲取HttpStream和RealConnection對象,以便后續讀寫。

 

CallServerInterceptor

調用服務攔截器,攔截鏈中的最后一個攔截器,通過網絡與調用服務器。通過HttpStream依次次進行寫請求頭、請求頭(可選)、讀響應頭、讀響應體。

 

CallServerInterceptor首先將http請求頭部發給服務器,如果http請求有body的話,會再將body發送給服務器,繼而通過httpStream.finishRequest()結束http請求的發送。

隨后便是從連接中讀取服務器返回的http響應,並構造Response。

如果請求的header或服務器響應的header中,Connection值為closeCallServerInterceptor還會關閉連接。

總結一下這幾個Interceptor的職責:
RetryAndFollowUpInterceptor --->創建StreamAllocation對象,處理http的redirect,出錯重試。對后續Interceptor的執行的影響:修改request及StreamAllocation。
BridgeInterceptor-------------->補全缺失的一些http header。對后續Interceptor的執行的影響:修改request。
CacheInterceptor-------------->處理http緩存。對后續Interceptor的執行的影響:若緩存中有所需請求的響應,則后續Interceptor不再執行。
ConnectInterceptor------------>借助於前面分配的StreamAllocation對象建立與服務器之間的連接,並選定交互所用的協議是HTTP 1.1還是HTTP 2。對后續Interceptor的執行的影響:創建了httpStream和connection。
CallServerInterceptor----------->處理IO,與服務器進行數據交換。對后續Interceptor的執行的影響:為Interceptor鏈中的最后一個Interceptor,沒有后續Interceptor。
 
 
在RealInterceptorChain.proceed()中,除了對狀態及獲取的reponse做檢查之外,最主要的事情即是構造新的RealInterceptorChain對象,獲取對應Interceptor,並調用Interceptor的intercept(next)了。在這里,index充當迭代器或指示器的角色,用於指出當前正在處理的Interceptor。
public final class RealInterceptorChain implements Interceptor.Chain {
  private final List<Interceptor> interceptors;
  private final StreamAllocation streamAllocation;
  private final HttpStream httpStream;
  private final Connection connection;
  private final int index;
  private final Request request;
  private int calls;

  public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
      HttpStream httpStream, Connection connection, int index, Request request) {
    this.interceptors = interceptors;
    this.connection = connection;
    this.streamAllocation = streamAllocation;
    this.httpStream = httpStream;
    this.index = index;
    this.request = request;
  }
RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpStream, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

RealInterceptorChain + Interceptor實現了裝飾器模式,實現了請求/響應的串式或流式處理。只不過內層裝飾器不是外層裝飾器的成員變量,而是接口方法中創建的臨時變量。

 

@Override public Response intercept(Chain chain) throws IOException {  
    RealInterceptorChain realChain = (RealInterceptorChain) chain;  
    Request request = realChain.request();  
    StreamAllocation streamAllocation = realChain.streamAllocation();  
  
    // We need the network to satisfy this request. Possibly for validating a conditional GET.  
    boolean doExtensiveHealthChecks = !request.method().equals("GET");  
    HttpStream httpStream = streamAllocation.newStream(client, doExtensiveHealthChecks);  
    RealConnection connection = streamAllocation.connection();  
  
    return realChain.proceed(request, streamAllocation, httpStream, connection);  
  }

 在Http1xStram中,它利用Okio對Socket的讀寫操作進行封裝,而創建HttpStream 對象的過程涉及到 StreamAllocationRealConnection,代碼較長,這里就不展開,這個過程概括來說,就是找到一個可用的RealConnection,再利用RealConnection 的輸入輸出(BufferedSourceBufferedSink)創建HttpStream 對象,供后續步驟使用。

 

里我們可以看到,核心工作都由HttpStream對象完成,而HttpStream實際上利用的是 Okio,而 Okio 實際上還是用的Socket,所以沒什么神秘的,只不過一層套一層,層數有點多。

 其實 Interceptor 的設計也是一種分層的思想,每個Interceptor 就是一層。為什么要套這么多層呢?分層的思想在 TCP/IP 協議中就體現得淋漓盡致,分層簡化了每一層的邏輯,每層只需要關注自己的責任(單一原則思想也在此體現),而各層之間通過約定的接口/協議進行合作(面向接口編程思想),共同完成復雜的任務。

 

個人理解:

OkHttpClient.newCall(request)進行execute或者enqueue操作

 

Request保存了url,method,body,head等信息

 

1.調用dispatcher的enqueue方法

dispatcher維護了一個類似於CachedThreadPool的線程池,比較適合執行大量的耗時比較少的任務。線程池正在運行的請求主機數小於5時則把請求加載到runningAsyncCalls中並在線程池中執行。

 

2.進入攔截器鏈

重試和重定向:處理重定向事件

bridge:完善請求頭,加載cookie

Cache:讀取緩存(ETAG,LastModified),判斷緩存是否過期,如果未過期則直接返回,不再進入后續攔截器。如果開啟網絡連接則判斷是否要緩存響應

Connection:建立連接

 

其中,Cache這里的緩存都是基於Map,key是請求中url的md5,value是在文件中查詢到的緩存,頁面置換基於LRU算法

建立連接后,核心工作都由HttpStream對象完成,而HttpStream實際上利用的是 Okio,而 Okio 實際上還是用的Socket


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM