okhttp3和retorfit原理

网络基础知识:okhttp是一个网络框架,帮我们封装了网络请求中需要重复做的事。

发起网络请求一般都是使用http协议,需要提供一下内容:
  1. 请求方法GET、POST、PUT、DELETE、HEAD等(常用的请求方法是get和post,而put一般是用来做更新,post一般是用来做提交的)

  2. 请求资源地址url

  3. 使用的http协议版本http/1/1.1/2

  4. 多个请求header

  5. 回车,换行符

  6. 请求body数据

有了发起http请求的主要信息,我们在网络请求时只要提供url和请求实体,剩下的就是网络框架帮我们做了。发起请求,一般使用socket,具体实现可能通过tcp或者udp来传输,tcp的话需要建立连接,这过程需要写很多代码,网络框架就要帮我们处理了。

服务端处理请求后,给我们发回响应,包括响应码,响应头和响应体。

常见的响应码:

  • 200 请求成功

  • 304 缓存可以继续用

  • 404 地址有错误

  • 500 服务器内部错误

发起请求及响应的过程如下:
  1. 域名解析,向DNS服务器发出域名解析请求,获取该域名对应的IP地址

  2. 根据IP发起TCP/IP三次握手连接

  3. 连接成功,构造http请求信息

  4. 发送请求

  5. 服务端收到请求,进行处理

  6. 服务端将响应已经建立的连接,向客户端发送数据

  7. 客户端收到响应进行处理

在请求过程中,第一步域名解析是操作系统底层实现的,从第二步开始就要我们自己实现了。我们最好只做业务相关的工作:发起请求拿到响应

需要网络框架帮我们把重复的操作都封装好:

  • 剩余的建立连接

  • 构造请求信息

  • 发起请求

  • 接收请求

  • 请求响应码和请求头的基本处理

深入了解okhttp

OkHttp 请求实现流程:

使用OkHttp 发起一个异步请求比较简单:

  1. 需要构造一个 OkHttpClient

  2. 构造请求信息 Request,并将Request封装成Call对象

  3. 发起请求(同步调用的是Call对象的execute方法进行请求,异步调用的是Call对象的enqueue方法请求)

代码如下:

private void testOkHttp() {

        OkHttpClient okHttpClient = getOkHttpClient();    //构造 OkHttpClient
        Request request = new Request.Builder()
                .get()  //Method GET
                .url("www.baidu.com")
                .build();    //构造请求信息

        okHttpClient.newCall(request)
                .enqueue(new Callback() {    //发起异步请求
                    @Override
                    public void onResponse(final Call call, final Response response) throws IOException {
                        //成功拿到响应
                        int code = response.code();
                        ResponseBody body = response.body();
                        String string = body.string();
                    }

                    @Override
                    public void onFailure(final Call call, final IOException e) {
                        e.printStackTrace();
                    }
                });
    }

注:异步请求的回调Callback中的onResponse和onFailure方法都是在子线程中执行的

okHttpClient.newCall(request) 方法的源码:

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

newCall(Request) 方法调用了 RealCall.newRealCall() 方法:

static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
  RealCall call = new RealCall(client, originalRequest, forWebSocket);
  call.eventListener = client.eventListenerFactory().create(call);
  return call;
}

RealCall.newRealCall() 方法创建了一个新的RealCall对象,这个RealCallokhttp3.Call 接口的一个实现。

okhttp3.Call 表示一个等待执行的请求,它只能被执行一次,定义了这些方法:

public interface Call extends Cloneable {
  //返回这个请求关联的 Request 对象
  Request request();

  //立即执行请求,阻塞等待拿到响应
  Response execute() throws IOException;

  //请求入队,异步执行
  void enqueue(Callback responseCallback);

  //取消一个请求
  void cancel();

  boolean isExecuted();

  boolean isCanceled();

  Call clone();

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

可以看到,我们前面发起异步请求的 enqueue() 方法是定义在 Call 中的。

  okHttpClient.newCall(request)
                .enqueue(new Callback() { ...});        //原来就是 Call 的方法

在 OkHttp 中,Call 的唯一实现就是 RealCall,它表示一个准备好被执行的请求。和 Request 不同在于,它还提供了发起请求、取消等方法。

拿到 OkHttp.Call 的实例RealCall 对象后,我们调用了它的 enqueue() 方法:

 //RealCall.enqueue()
@Override public void enqueue(Callback responseCallback) {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  captureCallStackTrace();
  eventListener.callStart(this);
  client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

核心就在最后这句 client.dispatcher().enqueue(new AsyncCall(responseCallback));,它做了两件事:

  1. 创建一个 AsyncCall 对象

  2. 调用 Dispatcher.enqueue() 方法将请求入队

final class AsyncCall extends NamedRunnable {
  private final Callback responseCallback;

  AsyncCall(Callback responseCallback) {
    super("OkHttp %s", redactedUrl());
    this.responseCallback = responseCallback;
  }

  String host() {    //用于标识这个请求
    return originalRequest.url().host();
  }

  Request request() {
    return originalRequest;
  }

  RealCall get() {
    return RealCall.this;
  }

  @Override protected void execute() {
    //...
  }
}

AsyncCall 就是一个 Runnable,用于异步执行任务。

接着看 client.dispatcher() 方法,它返回一个调度器 Dispatcher,这是 OkHttp 中比较核心的一个类:

public final class Dispatcher {
  private int maxRequests = 64;    //同时最多发起 64 个请求
  private int maxRequestsPerHost = 5;    //同一 host 最多发起 5 个请求
  private @Nullable Runnable idleCallback;

  private @Nullable ExecutorService executorService;    //将会异步创建的线程池

  //等待被执行的异步请求队列
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  //正在运行的异步请求队列(其中也包括取消后没有完成的请求)
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  //正在运行的同步请求队列
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

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

我们可以得到比较关键的信息如下:

  • 在一个 OkHttpClient 中一般只有一个 Dispatcher,因此一个 OkHttpClient 能发起的最多请求就是 Dispatcher 中定义的 64 个

  • 同样,同一 host 能发起的最多请求是 5 个

  • Dispatcher 中用三个队列保存同步、异步请求

  • 默认线程池核心线程数量为 0,最多数量不限制,消息队列为 SynchronousQueue,因此有请求时会不断创建新线程

然后回到我们之前调用的 Dispatcher.enqueue() 方法:

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

可以看到,调度器在收到一个异步请求后,会先判断当前正在运行的异步请求是否超过默认的 64 个、同一 host 的请求是否小于默认的 5,是的话就开始执行;否则加入等待执行的队列中。

前面介绍了 AsyncCall 是一个 NamedRunnable,等它被执行时会调用它的 run() 方法,这个方法调用了 execute() 方法。

public abstract class NamedRunnable implements Runnable {
  //...
  @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();
}

一个请求从提交到执行背后所经历的流程,如下图:

image

一个异步请求在发起到执行要经历这么几个状态:

  1. 创建一个 Request:代表用户提交的一个请求的基本信息,包括:URL,请求方法,请求头,请求体,自定义的 CacheControl,tag 等

  2. 创建一个 RealCall:代表准备好被执行的一个请求,在 Request 基础上添加了同步执行、异步执行、取消等操作

  3. 创建一个 AsyncCall:代表可以异步执行的任务

  4. 添加到正在运行/等待运行的异步队列中

  5. 被执行后调用 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) {
      //...
      } finally {
        client.dispatcher().finished(this);    //从 dispatcher 里移除当前这个,执行下一个 ①
      }
    }

这个方法中先通过 getResponseWithInterceptorChain() 方法拿到了响应 Response,然后进行了回调,最后在 finally 代码块中调用了 client.dispatcher().finished(this) 方法,这个方法的作用是从调度器 Dispatcher 里移除当前这个请求,执行下一个。

我们重点看 getResponseWithInterceptorChain() 方法,这个方法是 OkHttp 的核心。

 //RealCall.getResponseWithInterceptorChain()
Response getResponseWithInterceptorChain() throws IOException {
  // 把我们传入的和 OkHttp 内置的拦截器添加到一个列表里
  List<Interceptor> interceptors = new ArrayList<>();
  interceptors.addAll(client.interceptors());  //我们自定义的

  //内置的 5 个拦截器
  interceptors.add(retryAndFollowUpInterceptor);            //重试、取消
  interceptors.add(new BridgeInterceptor(client.coJar()));      //桥接
  interceptors.add(new CacheInterceptor(client.internalCache()));    //缓存
  interceptors.add(new CotInterceptor(client));    //连接
  if (!forWebSocket) {
    interceptors.addAll(client.networkInterceptors());
  }
  interceptors.add(new CallServerInterceptor(forWebSocket));  //最后是网络请求

  //创建一个 RealInterceptorChain ,传入刚才的拦截器列表
  Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
      originalRequest, this, eventListener, client.connectTimeoutMillis(),
      client.readTimeoutMillis(), client.writeTimeoutMillis());
  //开始执行
  return chain.proceed(originalRequest);
}

可以看到,在这个方法里,主要做了 3 件事:

  1. 把我们构造 OkHttpClient 时传入的拦截器和 OkHttp 内置的 5 个拦截器添加到一个列表里

  2. 创建一个 RealInterceptorChain ,传入刚才的拦截器列表

  3. 调用 RealInterceptorChain.proceed() 方法,这个方法会返回响应

这里提到了 OkHttp 大名鼎鼎的拦截器和拦截器链。很多人觉得 OkHttp 好用的理由之一就是它可以使用拦截器方便的修改请求和响应值,一般我们都会自定义拦截器用来添加请求 Header,或者打印请求、响应信息。

我们来看看这个拦截器链是怎么运转起来的吧。

首先看看 OkHttp 的拦截器和链接口:

public interface Interceptor {
  Response intercept(Chain chain) throws IOException;

  interface Chain {
    //当前的请求信息
    Request request();
    //处理请求,返回结果
    Response proceed(Request request) throws IOException;
    //执行当前请求的连接
    @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);
  }
}

可以看到,拦截器链 Chain 接口中定义了获取当前请求信息和处理请求的方法,然后还定义了一些修改超时时间相隔的方法,比价简单。

拦截器 Interceptor 接口则只有一个方法:

Response intercept(Chain chain) 

这个方法的参数是一个拦截器链,返回一个响应。

一般我们自定义一个拦截器时,主要分三步:

  1. 修改请求信息

  2. 调用拦截器链获取结果

  3. 修改响应

比如:

public class MyInterceptor implements Interceptor {
    @Override
    public Response intercept(final Chain chain) throws IOException {
        Request originalRequest = chain.request();
        //修改请求
        Request newRequest = originalRequest.newBuilder()
                .addHeader("xx", "xx")
                .tag("tag")
                .build();

        //调用拦截器处理获取结果
        Response originalResponse = chain.proceed(newRequest);

        //拿到结果进行处理后返回
        Response newResponse = originalResponse.newBuilder()
                .removeHeader("xx")
                .build();
        return newResponse;
    }
}

其中最关键的是 Response originalResponse = chain.proceed(newRequest); ,我们去看看 OkHttp 中 Chain 唯一的实现类 RealInterceptorChain 怎么实现的吧。

首先看下 RealInterceptorChain 的成员属性:

public final class RealInterceptorChain implements Interceptor.Chain {
  private final List<Interceptor> interceptors;    //所有拦截器
  private final StreamAllocation streamAllocation;    //连接引用相关
  private final HttpCodec httpCodec;    //底层数据流
  private final RealConnection connection;    //连接信息
  private final int index;    //当前拦截器的索引
  private final Request request;
  private final Call call;
  private final EventListener eventListener;
  private final int connectTimeout;
  private final int readTimeout;
  private final int writeTimeout;
  private int calls;
  //...
}

可以看到,有很多看起来很厉害的类,先别急,StreamAllocation, HttpCodec, RealConnection我们后面介绍,这里我们只关心两个属性:

  private final List<Interceptor> interceptors;    //所有拦截器
  private final int index;    //当前拦截器的索引

还记得在前面提到的 RealCall.getResponseWithInterceptorChain() 方法吗:

 //RealCall.getResponseWithInterceptorChain()
Response getResponseWithInterceptorChain() throws IOException {
  List<Interceptor> interceptors = new ArrayList<>();
  //...
  Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
      originalRequest, this, eventListener, client.connectTimeoutMillis(),
      client.readTimeoutMillis(), client.writeTimeoutMillis());

  return chain.proceed(originalRequest);
}

在这个方法里,构造了一个 RealInterceptorChain,同时传入的参数 index 为 0,然后调用了它的 proceed() 方法:

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

  calls++;
  //...

  // 先创建一个新的链,索引是当前加一
  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);    //它执行的参数是持有下一个引用的链

  //...
  return response;
}

RealInterceptorChain.proceed() 中我们看到,除了做一些异常判断,最重要的是上面我截取的部分,做了三件事:

  1. 先创建一个新的链,索引是当前加一(我们姑且叫它“下一个链”)

  2. 根据当前索引从所有拦截器中取出拦截器

  3. 调用拦截器的拦截方法,参数是下一个链

看到这里可能会有些懵,这是什么,调用了一个链的处理方法,结果在它里面又给我创建了一个链,不明白。

文字、代码比较抽象,看下图:

enter image description here

首先我们从前面的 RealCall.getResponseWithInterceptorChain() 方法可以知道,OkHttp 首先创建了一个 RealInterceptorChain,索引值是 0,这个 RealInterceptorChain 对象的 proceed()方法会创建一个新的 RealInterceptorChain,索引是 1(我们叫它“第二个链”),然后取出拦截器列表中的第一个拦截器,把第二个链作为参数调用拦截器的 intercept(Chain) 方法。

这时第一个拦截器的拦截方法就被调用了,参数是第二个链,它在处理请求信息后,调用拦截器链的 proceed() 方法,这个方法会再创建一个链(第三个),然后取出第二个拦截器然后调用它的拦截方法(参数是第三个链)。

以此循环,直到索引 index 超出拦截器列表的长度,就不再往下递归调用了,逐层返回结果。

刘望舒大神的这张图看着更直观:

enter image description here
小结

我们对 OkHttp 如何发起请求、逐层处理、拿到响应有了一个基本的认识,概括如下:

  1. 发起异步请求后会构造异步任务 AsyncCall 入队,等被执行时会调用它的 execute() 方法

  2. 这个执行方法会通过拦截器链,挨个调用我们自定义的和系统内置的 5 个拦截器,对请求信息和响应做处理,最后返回结果,回调我们传入的参数

  3. 然后从队列中移除当前任务,执行下一个,以此循环

okhttp拦截器总结
  1. 在发起请求前对request进行处理

  2. 调用下一个拦截器,获取response

  3. 对response进行处理,返回给上一个拦截器

okhttp缓存策略(使用的是DiskLruCache)
ConnectInterceptor(连接拦截器,负责建立连接和流对象的)
  1. ConnectInterceptor获取Interceptor传过来的SteamAllocation,然后执行SteamAllocation.newStream(),创建RealConnection对象

  2. 选择不同的连接方式,根据是否需要隧道连接来选择隧道连接或者Socket连接

  3. 将刚才创建的用于网络IO的RealConnection对象,以及对于与服务器交互最为关键的HttpCodec(可以编码request,解码response)等对象传递给后面的CallServerInterceptor拦截器

ConnectionPool总结
  1. okhttp使用了gc回收算法

  2. SteamAllocation的数量会渐渐变成0

  3. 变成0后会被线程池检测到并回收,这样就可以保持多个健康的keep-alive连接

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

推荐阅读更多精彩内容

  • 简介 OkHttp 是一款用于 Android 和 Java 的网络请求库,也是目前 Android 中最火的一个...
    然则阅读 1,052评论 1 39
  • 概述 okhttp,一个处理网络请求的开源项目,是安卓端最火热的轻量级框架,由移动支付Square公司贡献用于替代...
    wavefreely阅读 3,802评论 0 4
  • OkHttp3的使用 1、创建OkHttpClient;2、创建Request请求对象;3、OkHttpClien...
    Samuel_Tom阅读 274评论 0 0
  • 每次发朋友圈,都习惯在内容前附上"微幸福"这个前缀。任凭这一点点的微幸福,如生活中温暖的光,滋润着我的世界。今天看...
    良人犹美人阅读 238评论 2 5
  • 《影子》 出逃的灵魂 被太阳捉住—— 戴上身体的锁链 俯于大地修行 《风》 历史被镌刻进风里 而风,拼命地想摆脱—...
    忧伤没有伤口阅读 375评论 1 12