1. 程式人生 > >安卓進階(7)之OkHttp3.10攔截器原理解析

安卓進階(7)之OkHttp3.10攔截器原理解析

部落格流程

  1. 用一個demo介紹如何新增自定義的攔截器;
  2. 介紹攔截器是怎麼產生攔截效果的;
  3. 介紹okhttp裡預設的各個攔截器的作用。

新增自定義的log攔截器

在使用okhttp時,我們可能需要獲取到okhttp的log日誌,請求引數以及響應引數和資料。我們用一個小的demo來展示一下:

OkHttpClient client;

void initOkhttpClient() {

    client = new OkHttpClient.Builder()
            .addInterceptor(new LoggerInterceptor())
            .
build(); } void interceptorTest() { Map map = new HashMap(); map.put("commodityType", "01"); Gson gson = new Gson(); String json = gson.toJson(map); final Request request = new Request.Builder() .url("http://192.168.32.77:8089/api/commodity/getCommodityList") .
post(RequestBody.create(MediaType.parse("application/json"), json)) .build(); Call call = client.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(
Call call, Response response) throws IOException { } }); }

LoggerInterceptor類具體如下:

public class LoggerInterceptor implements Interceptor {

    public static final String TAG = "OkHttp日誌";

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        printRequestMessage(request);
        Response response = chain.proceed(request);
        printResponseMessage(response);
        return response;
    }

    /**
     * 列印請求訊息
     *
     * @param request 請求的物件
     */
    private void printRequestMessage(Request request) {
        if (request == null) {
            return;
        }
        Log.e(TAG, "Url : " + request.url().url().toString());
        Log.e(TAG, "Method: " + request.method());
        Log.e(TAG, "Heads : " + request.headers());
        RequestBody requestBody = request.body();
        if (requestBody == null) {
            return;
        }
        try {
            Buffer bufferedSink = new Buffer();
            requestBody.writeTo(bufferedSink);
            Charset charset = requestBody.contentType().charset();
            charset = charset == null ? Charset.forName("utf-8") : charset;
            Log.e(TAG, "Params: " + bufferedSink.readString(charset));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 列印返回訊息
     *
     * @param response 返回的物件
     */
    private void printResponseMessage(Response response) {
        if (response == null) {
            return;
        }
        ResponseBody responseBody = response.body();
        long contentLength = responseBody.contentLength();
        BufferedSource source = responseBody.source();
        try {
            source.request(Long.MAX_VALUE); // Buffer the entire body.
        } catch (IOException e) {
            e.printStackTrace();
        }
        Buffer buffer = source.buffer();
        Charset charset = Util.UTF_8;
        MediaType contentType = responseBody.contentType();
        if (contentType != null) {
            charset = contentType.charset(Charset.forName("utf-8"));
        }
        if (contentLength != 0) {
            String result = buffer.clone().readString(charset);

            Log.e(TAG, "頭資訊: " + response.headers());
            Log.e(TAG, "body: " + result);
        }
    }
}

攔截器原理

攔截器的原理,也就是說,攔截器是如何產生作用的?上面已經講解了如何新增一個攔截器,這一節,我首先來講解呼叫攔截器的流程: 在執行

Call call = client.newCall(request);

其實是new了一個RealCall,開啟RealCall.enqueue()

public void enqueue(Callback responseCallback) {
	 ...
	 client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

這裡呼叫到了排程器Dispatcher.enqueue()方法,來看下這個方法:

synchronized void enqueue(AsyncCall call) {
   if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
     runningAsyncCalls.add(call);
     executorService().execute(call);
   } else {
     readyAsyncCalls.add(call);
   }
 }

假設滿足佇列要求,這裡會將AsyncCall物件以任務的形式提交到執行緒池中,相當於一個任務就開始執行了。而AsyncCallRealCall的一個內部類,且繼承於NameRunnableNameRunnable.run()中有一個抽象方法供RealCall實現:

public abstract class NamedRunnable implements Runnable {
  ...
  @Override public final void run() {
    ...
    try {
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }

  protected abstract void execute();
}

我們再來看RealCall.execute()

@Override 
protected void execute() {
    boolean signalledCallback = false;
    try {
      Response response = getResponseWithInterceptorChain();
      if (retryAndFollowUpInterceptor.isCanceled()) {
        signalledCallback = true;
        responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
      } else {
        signalledCallback = true;
        responseCallback.onResponse(RealCall.this, response);
      }
    } catch (IOException e) {
      if (signalledCallback) {
        // Do not signal the callback twice!
        Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
      } else {
        eventListener.callFailed(RealCall.this, e);
        responseCallback.onFailure(RealCall.this, e);
      }
    } finally {
      client.dispatcher().finished(this);
    }
  }
}

引數responseCallback就是我們在外面newCallback例項,關於攔截器的就是這一句程式碼了:

Response response = getResponseWithInterceptorChain();

我們繼續往下看:

  Response getResponseWithInterceptorChain() throws IOException {
  // Build a full stack of interceptors.
  List<Interceptor> interceptors = new ArrayList<>();
  //開發人員自定義的攔截器全部新增到interceptors物件中
  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 (!forWebSocket) {
    interceptors.addAll(client.networkInterceptors());
  }
  //預設添加回調服務攔截器
  interceptors.add(new CallServerInterceptor(forWebSocket));
 //new了一個攔截器例項
  Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
      originalRequest, this, eventListener, client.connectTimeoutMillis(),
      client.readTimeoutMillis(), client.writeTimeoutMillis());
  //這句程式碼是重點,迴圈+遞迴
  return chain.proceed(originalRequest);
}

接下來看下攔截器鏈RealInterceptorChainchain.proceed()

 public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    ......
    // Call the next interceptor in the chain.
    //這裡重點看兩個引數,整型的`index`和集合物件`interceptors`,假設索引`index`剛開始為0,而`RealInterceptorChain next = new RealInterceptorChain(interceptors, ..., index + 1, ...);`表示`new`了一個索引`index`為1的攔截器鏈
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    //當index為0時,獲取到第一個攔截器
    Interceptor interceptor = interceptors.get(index);
    //以(index+1)下一個攔截器鏈為引數,執行上一個攔截器的攔截方法intercept()。
    Response response = interceptor.intercept(next);
    ......
    return response;
  }

攔截器的核心原理,在於對迴圈遞迴的理解,我將遞迴流程圖畫出來了,如下: 在這裡插入圖片描述 至此,攔截器的原理就已分析完。

各個預設攔截器的作用

攔截器的話,主要就是看裡面的intercept()方法啦

重試和跟蹤攔截器RetryAndFollowUpInterceptor

RetryAndFollowUpInterceptor攔截器中的intercept()是一個死迴圈:

 @Override 
 public Response intercept(Chain chain) throws IOException {
	  Request request = chain.request();
	  ......
	  Response priorResponse = null;
	  while (true) {
	    ......
	    response = realChain.proceed(request, streamAllocation, null, null);
	    ......
	    
	    Request followUp = followUpRequest(response, streamAllocation.route());
	    if (followUp == null) {
	      if (!forWebSocket) {
	        streamAllocation.release();
	      }
	      return response;
	    }
	    ......
	  }
}

我將部分程式碼省略掉了,大致邏輯是,如果followUp例項為null,表示不需要進行重試策略,這時候直接返回response,否則需要重試策略,重試需要對資源的釋放和複用處理。

橋接攔截器BridgeInterceptor

橋接主要是對請求頭和響應頭的處理了,新增一些預設的請求頭。

快取攔截器CacheInterceptor

只支援get請求,需要伺服器配置,控制相應的header響應頭。

連線攔截器ConnectInterceptor

獲取到具體的聯結器RealConnection,跟伺服器進行連線。

呼叫服務攔截器CallServerInterceptor

通道已經連線好,進行資料互動。