由浅入深了解OkHttp二:OkHttp同步/异步请求

时间:2022-08-27 13:54:36

如果您对这一系列的文章感兴趣,可以关注此专栏: android网络框架OkHttp+Retrofit源码分析

此节我们深入OkHttp源码了解一下OkHttp是如何执行同步/异步请求的

同步请求:
1、获取OkHttpClient
2、获取Request请求对象
3、获取okhttp3.Call对象

1、获取OkHttpClient:
OkHttp给我们提供了一个门面类OkHttpClient,我们可以使用new OkHttpClient.Builder().build()来进行各种设置:
网络复杂(连接超时/读取超时…)
需要设置其他参数(拦截器/分发器等…)

new OkHttpClient.Builder()
        .connectTimeout(10000, TimeUnit.MILLISECONDS)
        .readTimeout(10000, TimeUnit.MILLISECONDS)
        .build()

2、获取Request请求对象

Request request = new Request.Builder().url("http://www.baidu.com").get().build();

Request 代表着请求的报文信息,比如请求的方法、请求头、请求地址,这个Request也是通过构建者模式来创建

3、获取okhttp3.Call对象

Call requestCall = getOkHttpClient().newCall(request);

这个Call对象,就相当于实际的OkHttp请求,也就是可以把他理解为是Request请求和Response响应的一个桥梁,通过client的newCall()方法,传入我们之前创建好的request对象。
要注意,到这里为止同步请求和异步请求没什么区别,接下来的步骤就是实现的同步或者异步请求的逻辑了。也就是前三步骤只是为了获取实际的请求Call。

4、Call对象的execute()同步请求方法

    Response response = requestCall.execute();
        //响应成功
        if (response.isSuccessful()) {
            String json = response.body().string();
        }

通过Response的body().string()方法获取返回回来的json数据(也可以是其他类型的数据(XML类型) 这个需要和服务器端商量好)

异步请求:

requestCall.enqueue(new Callback() {
    public void onFailure(Call call, IOException e) {}
    public void onResponse(Call call, Response response){
    }

同步和异步请求最大的区别就是这个第四步骤,因为异步请求调用的则是enqueue()方法,传入CallBack进行数据的成功和失败逻辑

同步请求总结
1、创建OkHttpClient和Requet对象
2、将Request封装成Call对象
3、调用Call的execute()方法同步请求

同步请求发送之后,就会进入阻塞状态,直到收到响应。而且也只有同步请求会阻塞,异步请求是新开的线程做网络请求

由浅入深了解OkHttp二:OkHttp同步/异步请求

需要注意的的是:
异步请求的onResponse方法的返回仍然是子线程,okhttp并没有给我们把响应的数据转换到主线程中,因此我们在这个地方更新UI的时候是会报错的 需要我们手动的转换到主线程 然后才进行数据的解析和UI更新

当然如果使用的是Retrofit,那么这里的线程切换其实Retrofit已经帮我们完成了,完成这部分工作的主要是Executorer这个回调执行器

OkHttp源码解读—>OkHttp同步请求源码解析

1、创建OkHttpClient客户端

这个OkHttpClient对象的创建是使用了建造者模式来构建的,主要有Http请求分发器、连接池等还可以设置超时时间

2、创建Request请求报文信息类
这个Request对象的创建也是一个建造者模式来构建的。通过链式调用指定请求方法,指定头部,请求参数等等设置

3、创建Http请求的实际Call对象
接收okhttpclient request等参数,创建了实现类的对象,并且创建一个重定向拦截器 ,而且不管是同步还是异步,都是通过newCall()这个方法创建的RealCall对象来进行相应的操作

4、execute()方法进行同步请求
Response response = requestCall.execute();
RealCall的实现类覆写的这个execute()方法,因此实际上是RealCall对象执行的这个方法,下面来看一下这个方法里面的核心代码:

//捕捉异常堆栈信息
captureCallStackTrace();
//监听事件开启
eventListener.callStart(this);
try {
  //核心代码
  client.dispatcher().executed(this);

  Response result = getResponseWithInterceptorChain();
  if (result == null) throw new IOException("Canceled");
  return result;
}

上面的核心代码里面,RealCall会调用分发器分发请求
当把请求call放置到同步请求队列当中 进行请求之后 我们通过getResponseWithInterceptorChain()这个方法来获取相应Response

由浅入深了解OkHttp二:OkHttp同步/异步请求

如上图:Dispatcher会维持Call请求发送过来的状态,并且维护了一个线程池,用于执行网络请求,当Call在执行一个任务的时候,会通过Dispatcher这个分发器推到我们的执行队列中,依次执行任务

Dispatcher分发器在同步请求中做的很简单,就是保存和移除同步请求。对于异步请求,Dispatcher就需要做的好多工作了

OkHttp源码解读—>OkHttp异步请求源码总结

异步和同步的差别只在于Call之后的execute()和enqueue()方法,因此前三步骤这里不再赘述

第四步call执行enqueue方法,实现类是RealCall,代码如下:

synchronized (this) {
  //是否已经执行过 
  if (executed) throw new IllegalStateException("Already Executed");
  executed = true;
}//捕捉堆栈信息
captureCallStackTrace();
//开启监听
eventListener.callStart(this);

client.dispatcher().enqueue(new AsyncCall(responseCallback));

同样经过加上同步锁等步骤,最后通过将传入的Callback对象传入AsyncCall()里面创建一个AsyncCall对象实例。

然后realCall通过client.dispatcher方法获取到Dispatcher分发器 传入到刚创建好的Runnable这个实例(AsyncCall对象),最后调用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)

如果都符合就把请求添加到正在执行的异步请求队列当中,然后通过线程池去执行这个请求call,否则的话在就绪(等待)异步请求队列当中添加

关键方法executorService:

public synchronized ExecutorService executorService() {
  if (executorService == null) {
    //虽然这个地方定义了最大的线程池的数量是Integer.MAX_VALUE 但是我们知道上面对请求数量有了限制(64个)
  executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE,60,     
    TimeUnit.SECONDS,new SynchronousQueue<Runnable>(),      Util.threadFactory("OkHttp Dispatcher", false));
  }
  return executorService;
}

当创建好ExecutorService这个线程池对象之后,就需要调用execute(call)执行方法执行Runnable,也就是执行AsyncCall里面的run()方法,我们去查找AsyncCall里面的run方法,可以看到没有这个方法,却有一个execute()方法,而这个方法是复写的父类NamedRunnable的方法

protected void execute() {
  boolean signalledCallback = false;
  try {//拦截器链
    Response response = getResponseWithInterceptorChain();
    //重定向/重试是否取消
    if (retryAndFollowUpInterceptor.isCanceled()) {
      signalledCallback = true;
      //callback的onFailure()返回 在call.enqueue()里面传进去的
      responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
    } else {//如果成功 返回结果
      signalledCallback = true;
      responseCallback.onResponse(RealCall.this, response);
    }
  } catch (IOException e) {
    //网络请求失败的操作...
  } finally {
    client.dispatcher().finished(this);
  }}

上面我们可以看到通过拦截器链得到Response,然后通过重定向拦截器判断是否取消,取消调用callBack的失败方法,没有取消就直接返回结果

最后无论是否取消,都会调用dispatcher的finish方法,其关键代码如下:

synchronized (this) {
  //1、调用calls.remove()方法进行删除正在请求的异步线程
  if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
  //2、调用promoteCalls()方法调整整个请求队列
  if (promoteCalls) promoteCalls();
  //3、重新计算正在执行的线程的数量
  runningCallsCount = runningCallsCount();
  idleCallback = this.idleCallback; }

异步请求总结:
1、判断当前call
这个请求只能被执行一次,如果已经请求过了,就会抛出异常

2、通过传递进来的Callback封装成一个AsyncCall(Runnable)对象

3、获取Dispatcher对象并执行enqueue()异步请求

如果这个AsyncCall请求符合条件(判断实际的运行请求数是否小于允许的最大的请求数量(64) 并且共享主机的正在运行的调用的数量小于同时最大的相同Host的请求数(5)) 才会添加到执行异步请求队列,然后通过线程池进行异步请求否则就把这个AsyncCall请求添加到就绪(等待)异步请求队列当中

这个Dispatcher持有一个正在执行的请求队列、一个等待执行的请求队列,一个线程池。这三个来完整的处理异步请求操作