安卓进阶(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;
  }

拦截器的核心原理,在于对循环递归的理解,我将递归流程图画出来了,如下:
安卓进阶(7)之OkHttp3.10拦截器原理解析_第1张图片
至此,拦截器的原理就已分析完。

各个默认拦截器的作用

拦截器的话,主要就是看里面的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

通道已经连接好,进行数据交互。

参考文章
关于Okhttp3-CacheInterceptor
OkHttp深入理解-ConnectInterceptor
OKhttp源码学习

你可能感兴趣的:(安卓进阶,安卓进阶系列---罗小辉)