1. 程式人生 > >android面試(12)-Okhttp

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=new
Request.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。