android面試(12)-Okhttp
可以說,okhttp非常的火,火了好幾年了,雖然現如今比較火的網路框架是retrofit,但是,retrofit內部其實也是用了okhttp;
1.使用方法:
(1)建立一個請求客戶端okhttpClient物件
(2)建立一個請求Request物件,通過Build模式建立
(3)建立一個實際的http請求call物件,它可以呼叫execute(同步獲取資料),也可以呼叫enqueue(非同步獲取資料);
public class OkhttpFace extends Activity { //第一步:建立一個okHttpClient private final OkHttpClient client=new OkHttpClient(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); } private void okhttpAsyRequest() throws Exception{ //第二步:建立一個Request,通過Builder模式生成 Request request=new Request.Builder().url("http://www.baidu.com").build();//第三步,通過client的call方法建立一個call物件,代表實際的http請求 Call call = client.newCall(request); //同步獲取資料 Response response = call.execute(); //獲取請求頭 Headers headers=response.headers(); Log.e("請求資料為",response.body().toString()); } private void okhttpSynRequest(){ //第二步:建立一個Request,通過Builder模式生成 Request request=newRequest.Builder().url("http://www.baidu.com").build(); //第三步,通過client的call方法建立一個call物件,代表實際的http請求 Call call = client.newCall(request); //非同步獲取資料 call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { e.printStackTrace(); } @Override public void onResponse(Call call, Response response) throws IOException { //獲取請求頭 Headers headers=response.headers(); Log.e("請求資料為",response.body().toString()); } }); } }
這裡的同步獲取資料指的是,當呼叫execute方法是,它會阻塞執行緒去獲取資料,非同步獲取資料指的是,它會開啟一個新的執行緒,在新的執行緒中去獲取資料,其中的callback回撥都是工作在新執行緒中的;
2.原始碼分析:
我們先來看同步獲取資料:
首先第一步沒啥好說的,使用okhttp,每一個請求都需要一個client客戶端,所以,你可以把它搞成全域性的final物件;
第二步是採用Build模式新建一個request請求,這裡提一下,build模式其實在開源框架中使用的很頻繁,它的主要作用就是講一個複雜的物件的建立和顯示分離開來;
第三步call.execute方法,我們點進去看看:(其實真正呼叫execute的不是call,而是Realcall)
@Override public Response execute() throws IOException { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } try { client.dispatcher().executed(this); Response result = getResponseWithInterceptorChain(); if (result == null) throw new IOException("Canceled"); return result; } finally { client.dispatcher().finished(this); } }
在execute方法中,首先它會做一個同步的檢查,判斷這個請求是否已經被執行了,因為每一個請求只能被執行一次,接下來,它會呼叫client的dispatcher()方法去執行網路請求,這裡的dispatcher其實在它的官方文件上寫的是執行非同步操作的策略,但是,現在在同步裡出現了,我們先不去關注他,因為一會的非同步會出現很多這個dispatcher;繼續往下看,getResponseWithInterceptorChian()這個方法可以說把okhttp最精髓的地方體現出來了,也就是----攔截器,最後,它會返回請求到的資料,然後把這次的請求關閉;
其實,真正做網路請求的,就是getResponseWithInterceptorChian()這個方法,我們需要看看:
private Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. List<Interceptor> 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); }
這麼多的interceptor,沒錯,這就是攔截器,okhttp最大的亮點就是這個interceptor,網路請求要很多步驟,比如需要去請求網路,快取,透明壓縮,等等一系列的操作,okhttp這個網路框架就把這些操作分割成一個個的interceptor,然後把這些interceptor連成一個鏈也就是Interceptor.Chain,然後去完成一次完整的網路請求,這是典型的分層思想的提現,把一次複雜的工作分步驟去做,更加合理,也更加高效安全;
就比如上面的程式碼中,一次請求就添加了失敗重定向攔截器,橋接攔截器,快取攔截器,連線網路的攔截器,連線伺服器的攔截器,這些攔截器組成一條完整的鏈,這裡,我們選一個攔截器看看內部是什麼,我們就選這個CallServerInterceptor
@Override public Response intercept(Chain chain) throws IOException { HttpStream httpStream = ((RealInterceptorChain) chain).httpStream(); StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation(); Request request = chain.request(); long sentRequestMillis = System.currentTimeMillis(); httpStream.writeRequestHeaders(request); if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) { Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength()); BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut); request.body().writeTo(bufferedRequestBody); bufferedRequestBody.close(); } httpStream.finishRequest(); Response response = httpStream.readResponseHeaders() .request(request) .handshake(streamAllocation.connection().handshake()) .sentRequestAtMillis(sentRequestMillis) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); if (!forWebSocket || response.code() != 101) { response = response.newBuilder() .body(httpStream.openResponseBody(response)) .build(); } if ("close".equalsIgnoreCase(response.request().header("Connection")) || "close".equalsIgnoreCase(response.header("Connection"))) { streamAllocation.noNewStreams(); } int code = response.code(); if ((code == 204 || code == 205) && response.body().contentLength() > 0) { throw new ProtocolException( "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength()); } return response; }這個方法內部其實就是取得請求request的頭部和body,然後取得response的頭部和body,大致就是這麼一個流程,但是在這裡,我們注意一個類,
HttpStream httpStream = ((RealInterceptorChain) chain).httpStream();
HttpStream類,這個類點進去看啊,它其實是集成了okio,我們又知道okio內部其實就是socket,所以我們就可以斷定okhttp底層其實就是socket;
接下來,我們再來看看okhttp的非同步請求的原始碼:
其實非同步與同步最大的不同就是call呼叫的方法不一樣,我們進enqueue方法看看
@Override public void enqueue(Callback responseCallback) { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } client.dispatcher().enqueue(new AsyncCall(responseCallback)); }
這裡我們發現,有事dispatcher,好吧,終究還是跑不掉,進去看看吧:
在dispatcher裡,其實我們能看見三個非常重要的集合類:
/** Ready async calls in the order they'll be run. */ private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>(); /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */ private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>(); /** Running synchronous calls. Includes canceled calls that haven't finished yet. */ private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
第一個是正在準備的非同步請求
第二個是正在執行的非同步請求,
第三個是正在執行的同步請求;
瞭解了這些之後,我們再看看dispatch的enqueue方法
synchronized void enqueue(AsyncCall call) { if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { runningAsyncCalls.add(call); executorService().execute(call); } else { readyAsyncCalls.add(call); } }
從這裡我們就可以看出他是怎麼實現非同步的了,首先它會判斷當前的請求是否符合某些特定的要求,如果滿足,那麼就直接把這個請求新增到正在執行的非同步請求集合裡,並直接執行網路請求,如果不滿足,那麼就會把它加入到正在準備的非同步請求集合中;其實在這裡的executorService()內部就是一個執行緒池
public synchronized ExecutorService executorService() { if (executorService == null) { executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false)); } return executorService; }說到底,非同步和同步其實就是在處理多個請求時,同步是將其阻塞,非同步是封裝一個執行緒池,最後真正去執行網路請求的其實還是interceptor。