Okhttp之责任链

Okhttp之责任链

Okhttp简介

okhttp是一个第三方类库,用于android中请求网络。
这是一个开源项目,是安卓端最火热的轻量级框架,由移动支付Square公司贡献(该公司还贡献了Picasso和LeakCanary)

官网网址:OKHttp官网

基本用法

OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  Response response = client.newCall(request).execute();
  return response.body().string();
}

这里大概分为四步

  1. 构建OkHttpClient对象,配置公共的网络请求设置,如超时时间,拦截器等
  2. 构建Request对象,主要有url,method,RequestBody
  3. 生成call对象
  4. 获得Response对象

OkhttpClient,Request的构建都使用了建造者模式,这里不做详细介绍.

接下来通过这个例子分析整个流程:

#OkhttpClient.java
/**
   * Prepares the {@code request} to be executed at some point in the future.
   */
  @Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }

OkhttpClient的newCall方法其实是调用RealCall 的一个静态方法,跟进去看一下

#RealCall.java

final class RealCall implements Call {

.....
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // Safely publish the Call instance to the EventListener.
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
  }

}

这里逻辑很简单,就是new了一个RealCall的实例,而RealCall 则是Call 接口的一个实现类,接下里看一下Call接口都定义了哪些方法:

/**
 * A call is a request that has been prepared for execution. A call can be canceled. As this   object
 * represents a single request/response pair (stream), it cannot be executed twice.
 */
public interface Call extends Cloneable {
  /** Returns the original request that initiated this call. */
  Request request();
  
  Response execute() throws IOException;
    
  void enqueue(Callback responseCallback);

  /** Cancels the request, if possible. Requests that are already complete cannot be canceled. */
  void cancel();

  /**
   * Returns true if this call has been either {@linkplain #execute() executed} or {@linkplain
   * #enqueue(Callback) enqueued}. It is an error to execute a call more than once.
   */
  boolean isExecuted();

  boolean isCanceled();

  /**
   * Create a new, identical call to this one which can be enqueued or executed even if this call
   * has already been.
   */
  Call clone();

  interface Factory {
    Call newCall(Request request);
  }
}

对于Call接口的定义,官方给出了解释:

A call is a request that has been prepared for execution. A call can be canceled. As this object

represents a single request/response pair (stream), it cannot be executed twice.

翻译: 一个Call是为执行而准备的请求,它能够被取消,这个对象代表一个请求/响应对(流),不能够被执行两次.

大致就是Call相当一个真正用于网络请求的任务,它可以被取消,它里面的请求和响应是一一对应的,不能够被执行两次,大概可能导致状态异常吧(瞎猜的),所以每个请求需要新建一个Call的实例,不能够复用之前的.

Call中也包含了关键的用于同步请求的execute()方法,用于异步请求的enqueue()方法.

接下来看一下RealCall对于execute()的实现:

#RealCall.java

@Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      client.dispatcher().finished(this);
    }
  }

首先判断executed是否为true,如果是true则抛出异常,同时也保证了Call只能够被执行一次.

首先分析client.dispatcher().executed(this);

#Dispatcher.java

public final class Dispatcher {
  private int maxRequests = 64;
  private int maxRequestsPerHost = 5;
  private @Nullable Runnable idleCallback;

  /** Executes calls. Created lazily. */
  private @Nullable ExecutorService executorService;

  /** 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<>();
    
    
  /** Used by {@code Call#execute} to signal it is in-flight. */
  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }
    
}

可以看到Dispatcher 其实就是一个任务调度器 ,它里面维护了线程池,和三个任务队列(双向队列)

  • readyAsyncCalls : 等待异步请求的队列
  • runningAsyncCalls: 运行的异步请求队列
  • runningSyncCalls : 运行的同步请求队列

executed()方法则是将当前同步请求任务加入到同步请求队列中

这里先提一下Interceptor ,也就是我们所说的拦截器.那它到底是啥呢?

#Interceptor.java

public interface Interceptor {

  Response intercept(Chain chain) throws IOException;

  interface Chain {

    Request request();

Response proceed(Request request) throws IOException;

/**
 * Returns the connection the request will be executed on. This is only available in the chains
 * of network interceptors; for application interceptors this is always null.
 */
@Nullable Connection connection();

Call call();

int connectTimeoutMillis();

Chain withConnectTimeout(int timeout, TimeUnit unit);

int readTimeoutMillis();

Chain withReadTimeout(int timeout, TimeUnit unit);

int writeTimeoutMillis();

Chain withWriteTimeout(int timeout, TimeUnit unit);
}

}

没错,它就是一个接口,而且只有一个intercept方法,它接收一个实现了Chain 接口的对象,并返回一个Response对象.

接下来继续分析Okhttp如何进行网络请求,并获取响应对象.

Response result = getResponseWithInterceptorChain();

getResponseWithInterceptorChain 才是真正执行网络请求并获得Response对象,看一下具体实现:

#RealCall.java

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 (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    return chain.proceed(originalRequest);
  }

它首先创建了一个空的List,并把我们用户自定义的拦截器添加进去,然后添加Okhttp 预先定义的拦截器,主要有以下几个:

  • RetryAndFollowUpInterceptor : 负责失败重试和重定向
  • BridgeInterceptor : 负责处理http 通用请求头和返回头,Cookie
  • CacheInterceptor : 负责处理缓存
  • ConnectInterceptor : 开启和服务器的连接
  • networkInterceptors : 用户定义的网络拦截器,权限更高,更接近底层,一般不需要关注
  • CallServerInterceptor : 实现网络请求

这里只是大概介绍每个拦截器的职责,不作详细解析,有兴趣可以自己查看相关源代码.

Okhttp实际上在这里把这些拦截器进行了排序,任何一个顺序改变,都可能导致无法正常工作.

接下来看一下okhttp是如何将这些拦截器串联在一起工作的:

在处理完所有拦截器之后,它创建了一个RealInterceptorChain 对象,并把拦截器列表作为参数传了进去,然后调用了proceed 方法.

#RealInterceptorChain.java

  @Override public Response proceed(Request request) throws IOException {
    return proceed(request, streamAllocation, httpCodec, connection);
  }

  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    // If we already have a stream, confirm that the incoming request will use it.
    if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.httpCodec != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    if (response.body() == null) {
      throw new IllegalStateException(
          "interceptor " + interceptor + " returned a response with no body");
    }

    return response;
  }

这里除去参数校验可以分为3步;

  1. new一个RealInterceptorChain对象,并把当前的RealInterceptorChain对象的成员原封不动当作参数传入,唯一不同的就是把index+1之后传入。
  2. 并从拦截器列表中获取第index个位置上的拦截器对象。index最初传入的是0,之后每当proceed方法调用一次,也就是new一个RealInterceptorChain对象时,会对index+1,此时就会拿到拦截器列表的下一个对象,其实就是在进行遍历。
  3. 调用上一步拿到的拦截器的intercept方法,每个拦截器内部可以决定中断或延续整个责任链,中断则返回Response对象,延续则调用chain.procced()方法进入到下一个Interceptor.

至此整个责任链模式分析完毕。

总结

其实就是利用的迭代加递归的思想,在每个拦截器的内部调用chain.proceed方法,即调用下一个Interceptorintercept方法,而每个拦截器又可以自己决定中断或延续整个调用过程,但如果需要发起网络请求,则必须走到最后一个拦截器由CallServerInterceptor发起请求并返回Response,并依次返回。
每个拦截器都有自己的职责,可以处理从上游传过来的请求对象,也可以处理从下游返回的请求对象,设计相当优雅.

最后附上整个过程的流程图(来自网络):

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容