Android之OkHttp源码解析(1)- 请求过程

前言

此篇文章将会解析OkHttp的源码,因为OkHttp的的源码有很多部分,我们这篇来讲解OkHttp的网络请求,我们还是从OkHttp的异步操作开始解析,OkHttp版本为3.7。

OkHttp的请求网络流程

当我们要请求网络的时候需要调用OkHttpClient的newCall(request),然后在进行execute()或者enqueue()操作,当调用newCall()方法时候,会调用如下代码。

@Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
}

它其实返回的是一个RealCall类,我们调用enqueue()异步请求方法实际上是调用了RealCall的enqueue()方法。

//RealCall的enqueue()方法源码
//从这里开始调用
@Override public void enqueue(Callback responseCallback) {
    enqueue(responseCallback, false);
}

@Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    //这里交给dispatcher进行调度
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

从这里源码可以看到,最终的请求是dispatcher的enqueue()来完成的,我们接下来就来分析这个。

public final class Dispatcher {

    //最大并发请求数
    private int maxRequests = 64;
    //每个主机的最大请求数
    private int maxRequestsPerHost = 5;
    
    /** Executes calls. Created lazily. */
    //消费者线程池
    private 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<>();

    ......
}

上面为Dispatcher类的部分代码,主要用于控制并发的请求,接下来我们看看Dispatcher的构造方法。

public Dispatcher(ExecutorService executorService) {
  this.executorService = executorService;
}

public Dispatcher() {
}

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;
}

Dispatcher有两个构造方法,可以使用自己设定的线程池,也如果没有线程池,则会在正式请求网络前调用executorService()来创建默认线程池,这个线程池类似于CachedThreadPool,比较适合执行大量的耗时比较少的额任务。然后我们回到前面之前讲过的就是当RealCall调用enqueue()方法时候,实际上就是调用了Dispatcher的enqueue()方法,它的代码如下。

synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      //这里通过executorService()创建线程池然后通过线程池的execute()会开始执行AsyncCall的execute()
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
}

当正在运行的异步请求队列的数量小于64并且正在运行的请求主机数小于5时,把请求加载到到runningAsyncCalls中并在线程池中执行,否则就加在到readyAsyncCalls中进行缓存等待,线程池中传进来的参数是AsyncCall,它是RealCall的内部类,其内部也实现了execute()方法,代码如下。

//AsyncCall的execute()方法源码
@Override protected void execute() {
  boolean signalledCallback = false;
  try {
    ......
  } catch (IOException e) {
    ......
  } finally {
    client.dispatcher().finished(this);
  }
}

无论这个请求的结果如何,都会执行finaly块里面的代码,我们来看看里面是什么。

void finished(RealCall call) {
    finished(runningSyncCalls, call, false);
}

private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
    //移除队列
    if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
    //这里开始检查是否为异步请求,检查等候的队列,readyAsyncCalls
    if (promoteCalls) promoteCalls();
    runningCallsCount = runningCallsCount();
    idleCallback = this.idleCallback;
    }

    if (runningCallsCount == 0 && idleCallback != null) {
        idleCallback.run();
    }
}

private void promoteCalls() {
    //检查 运行队列 和 等待队列
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
    
    //将等待队列加入到运行队列中
    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
        AsyncCall call = i.next();
        //相同host的请求没有达到最大,加入到运行队列
        if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
    }

    if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
}

这里是通过调度器移除队列,并且判断是否存在等待队列,如果存在,检查执行队列是否达到最大值,如果没有将等待队列变为执行队列。这样也就确保了等待队列被执行。

好了接下来我们回到AsyncCall这个类上面来,我们会发现它继承了NamedRunnable。

public abstract class NamedRunnable implements Runnable {
  protected final String name;

  public NamedRunnable(String format, Object... args) {
    this.name = String.format(format, args);
  }

  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }

  protected abstract void execute();
}

然后发现了,其实本质上AsyncCall就是一个Runnable,线程池会执行AsyncCall的execute()方法。接下来我们来看看AsyncCall的execute()源码。

//AsyncCall的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 {
      responseCallback.onFailure(RealCall.this, e);
    }
  } finally {
    client.dispatcher().finished(this);
  }
}

从这里我们发现getResponseWithInterceptorChain()返回一个response,很明显这是在请求网络,我们就着重看这个。我们先看getResponseWithInterceptorChain()的源码。

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    //责任链
    List<Interceptor> interceptors = new ArrayList<>();
    配置OkhttpClient时设置的拦截器,可以由用户自己设置。
    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) {
        //配置OkHttpClient时设置的networkInterceptors
        interceptors.addAll(client.networkInterceptors());
    }
    //执行流操作
    interceptors.add(new CallServerInterceptor(forWebSocket));
    //创建责任链
    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    //执行责任链
    return chain.proceed(originalRequest);
}

我们发现,这里其实是运用了责任链模式,这里也是OkHttp最精髓的地方。从上述代码中,可以看出都实现了Interceptor接口,这是okhttp最核心的部分,采用责任链的模式来使每个功能分开,每个Interceptor(拦截器)自行完成自己的任务,并且将不属于自己的任务交给下一个,简化了各自的责任和逻辑。我们暂时先不看拦截器的实现,这里接着往下看,这里是通过RealInterceptorChain创建了责任链,也就是接来执行RealInterceptorChain的proceed()方法。

@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);
    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");
    }

    return response;
}

从这段实现可以看出,是按照添加到 interceptors 集合的顺序,逐个往下调用拦截器的intercept()方法,然后最终返回处理后的response。到这里其实整个网络请求的整个流程就到此结束了,其实重点就是里面的拦截器的具体实现。关于拦截器后面我会单独来写一篇来讲述里面每个拦截器的实现。这里最后放一张网络请求的流程图。

OkHttp的请求过程


参考

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

推荐阅读更多精彩内容