OkHttp原理流程源码分析

OkHttp已经是非常流行的android客户端的网络请求框架,我其实在项目中使用也已经好几年了,之前一直把重心放在如何快速的搞定业务上、迭代的效率上,这一点来讲,对于一个公司优秀员工是没有毛病的。但是越到后面,你会发现,真正的高效,是你对技术的更深层次的理解,对技术更熟练的掌握。所以今天重回技术本身,搞清楚OkHttp的实现机制和部分源码分析,也提醒阅读本篇文章的同学,除了在公司加班加点赶业务进度的同时,从长远角度看,提深自我的技术技能,才是对公司和自己的双赢结果。

“OkHttp 4.x upgrades our implementation language from Java to Kotlin and keeps everything else the same. We’ve chosen Kotlin because it gives us powerful new capabilities while integrating closely with Java.”

引入官方文档的一段内容,OkHttp4.x实现语言从Java改成了Kotlin,这里面我们使用的源码是OkHttp3.10,在逻辑实现上并无太多差异。

一、OkHttp的最基础的核心类介绍

选型支撑:

摘自Http官方文档的一段介绍,翻译成了中文如下:

HTTP是现代应用常用的一种交换数据和媒体的网络方式,高效地使用HTTP能让资源加载更快,节省带宽。OkHttp是一个高效的HTTP客户端,它有以下默认特性:

  • 支持HTTP/2,允许所有同一个主机地址的请求共享同一个socket连接
  • 连接池减少请求延时
  • 透明的GZIP压缩减少响应数据的大小
  • 缓存响应内容,避免一些完全重复的请求

OkHttp的第一个特性,允许同一个主机地址请求共享同一个socket连接,确实OkHttp是直接对于网络分层模型中传输层socket进行封装的,内部实现涉及请求线程池和连接池线程池。

核心类介绍:

从一个简单的Demo入手:

private String url = "http://www.baidu.com";
void okHttpTest() {
   
    //核心客户端类
    OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
    //构建请求的类
    Request request = new Request.Builder().url(url).build();
    //构建调用类
    Call call = okHttpClient.newCall(request);
    //同步调用
    call.execute();
    //入队列异步调用
    call.enqueue(new Callback() {
   
        @Override
        public void onFailure(Call call, IOException e) {
   

        }
        @Override
        public void onResponse(Call call, Response response/*响应核心类*/) throws IOException {
   

        }
    });
}

OkHttp的核心类主要有OkHttpClient,Dispatcher,Call,Request,Response,Interceptor,Chain。除了Dispatcher(分发器)、Intercoptor(拦截器)、Chain(链) 在上述调用代码中没有出现,这三个核心类是OkHttp的底层实现,在之后的源码流程分析中,会一一提及。

OkHttpClient是负责管理多个Call的组织者,Call是由Request和Response组成,Call入队列被执行后的响应回调提供Response对象作为响应。OkHttp执行请求的方式有同步调用和异步调用两种方式,分别是call.excute()进行同步调用,call.enqueue(callBack)进行异步调用。

二、OkHttp原理流程源码分析

我们从上面的demo代码开始,分析一下OkHttp的调用流程以及源码实现,上述代码中:

第一步:OkHttpClient okHttpClient = new OkHttpClient.Builder().build();构建一个OkHttpClient的客户端对象。

第二步:Request request = new Request.Builder().url(url).build();构建请求对象,在请求的对象的构建过程中,会通过Request对象构建者模式的api传入url。

这里OkHttpClient、Request对象的设计,实际是【构建者设计模式】的应用,本篇不对设计模式做详细介绍。

第三步:Call call = okHttpClient.newCall(request);构建了call类对象,传入Request对象,看newCall方法的源码,实际返回的RealCall对象。这里面的目的是准备一个真实的请求对象在未来的某个时间点将被执行。

第四步:异步情况下,通过 call.enqueue(new Callback() {}),完成加入请求队列的操作,等待源码层的实现发起正式的网络请求;同步情况下直接调用call.excute()。

作为应用层开发,完成上述四步流程,一个完整的网络请求所需写的代码就完成了,使用起来还是比较简单的。

接下来,我们继续看call.enqueue(new Callback(){})的内部实现,call的真实对象是RealCall的实例。

2.1OkHttp发起网络请求的两种方式
1、同步请求

同步请求直接调用RealCall.execute()方法,源码如下:

@Override public Response execute() throws IOException {
   
  ...
  try {
   
    //调用分发器执行
    client.dispatcher().executed(this);
    //真正的执行代码在此方法中,最终会返回Response对象,这个方法的核心实现拦截器的相关逻辑
    Response result = getResponseWithInterceptorChain();
    if (result == null) throw new IOException("Canceled");
    return result;
  }
  ....
}

Dispatcher中的执行代码:

/**Used by Call#execute to signal it is in-flight.*/
synchronized void executed(RealCall call) {
   
  //未做任何执行操作,只是添加到运行队列,便于对请求的取消操作进行统一管理
  runningSyncCalls.add(call);
}

上述代码同步执行,也不涉及线程池。

2、异步请求

看RealCall.enqueue方法的源码:

@Override public void enqueue(Callback responseCallback) {
   
  //当前call正在被执行的一个判定
  synchronized (this) {
   
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  captureCallStackTrace();
  eventListener.callStart(this);
  //这里实际调用了dispatcher的enqueue方法(小细节 这里在RealCall内部创建Asyncall的对象实例)
  client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

接下来,看一下dispatcher类的源码:

/**
 * 源码中注释的解释:Policy on when async requests are executed.
 * 首先Dispatcher这里被定义为分发器,处理异步请求执行时的策略
 */
public final class Dispatcher {
   
    //最大请求数
    private int maxRequests = 64;
    //同一个host地址的最大请求数
    private int maxRequestsPerHost = 5;
    
    /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
    /** 正在执行的任务,包括刚被取消未完成的;这里Deque Double end queue*/
    private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
  
   
    /** Ready async calls in the order they'll be run. */
    /** 准备好将被按顺序执行的call任务*/
    private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
    ....
    synchronized void enqueue(AsyncCall call) {
   
    //判定条件入不同的队列
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
   
      runningAsyncCalls.add(call);
      //使用线程池执行call
      executorService().execute(call);
    } else {
   
      readyAsyncCalls.add(call);
    }
  }
  ....
}

runningAsyncCalls:异步任务的运行时队列,readyAsyncCalls:异步任务的等待队列。

runningAsyncCalls.size() < maxRequests 最大请求数<64;同时同一个host地址的最大请求数<5,则把当前call对象添加到运行时队列中,并使用线程池执行call,否则将包装了request的call对象添加到readyAsyncCalls异步等待队列中。这里面的队列是Deque,即Double end queue,是一个双端可插入、移除元素的队列,为什么使用这种数据结构?

之后,使用线程池executorService.execute(call)执行call,前面diam注释中提到过,这里的call是new出来的AsyncCall对象实例。AsyncCall是RealCall的内部类,定义了Request()方法和前面构建的Request对象进行关联。

final class AsyncCall extends NamedRunnable {
   
  private final Callback responseCallback;
  AsyncCall(Callback responseCallback) {
   
    super("OkHttp %s", redactedUrl());
    this.responseCallback = responseCallback;
  }
  //orginalRequest,就是在构建RealCall对象的时候,把先前构建的Request对象传入的
  String host() {
   
    return originalRequest.url().host();
  }
  Request request() {
   
    return originalRequest;
  }
  RealCall get() {
   
    return RealCall.this;
  }
  @Override protected void execute() {
   
      boolean signalledCallback = false;
      try {
   
        //真实发起请求的逻辑实现在拦截器中的逻辑中。
        Response response = getResponseWithInterceptorChain();
        ....
      } catch (IOException e) {
   
       ...
        }
      } finally {
   
        //finished方法是对Dispatcher中运行时队列的移除操作
        client.dispatcher().finished(this);
      }
    }
  }
}

AsyncCall是一个runable对象,真正发起请求的是在其内部的实现execute方法中。

接下来,继续看一下client.dispatche

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hymKing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值