Android—OkHttp同步异步请求过程源码分析与拦截器

OkHttp同步请求步骤:

  1. 创建OkHttpClient,客户对象
  2. 创建Request,请求主体,在请求主体设置请求的url,超时时间等
  3. 用newCall(request)将Reuqest对象封装成Call对象,然后用Call对象的execute()发起同步请求。
  4. execute()返回的是Response对象。可以用execute().body().toString()得到请求所返回的主体内容。
val client = OkHttpClient()
val request = Request.Builder()
    .url("https://www.baidu.com")
    .build()
val response = client.newCall(request).execute().body().toString()

注意:发送请求后,就会进入阻塞状态,直到收到响应。

OkHttp异步请求步骤:

  1. 创建OkHttpClient,客户对象
  2. 创建Request,请求主体,在请求主体设置请求的url,超时时间等
  3. 用newCall(request)将Reuqest对象封装成Call对象,然后用Call对象的enqueue()发起异步请求。
  4. enqueue(object: Callback{重写onFailure、onResponse方法}) 在onResponse方法中获取申请数据内容。
val client = OkHttpClient()
val request = Request.Builder()
    .url("https://www.baidu.com")
    .build()
val response = client.newCall(request).enqueue(object: Callback {
    override fun onFailure(call: Call, e: IOException) {
        TODO("Not yet implemented")
    }
    override fun onResponse(call: Call, response: Response) {
        response.body().toString()
    }
})

源码分析:

注意:下面的源代码段可能来自不同一个类文件,只是将他们放一起,容易观察,主要放一些关键代码,其他会有...代替。

1.关于创建OkHttpClient对象,下面源码:

public OkHttpClient() {
    this(new Builder());
  }

public Builder() {
      dispatcher = new Dispatcher();   
      protocols = DEFAULT_PROTOCOLS;
      connectionSpecs = DEFAULT_CONNECTION_SPECS;   
      ......
      connectionPool = new ConnectionPool();
      .....
    }

可以看到OkHttp采用了建造者模式,在Builder()里面封装各种需要的属性,关键的主要有dispatcher分发器,connectionSpecs决定是异步还是同步,connectionPool 连接池。连接池具体到连接拦截器才会使用到,每个连接都会放入连接池中,由它进行管理。 

总结新建Client对象时,新建了一个分发器和一个连接池,还有一些属性的初始化。

2.创建Request对象时,源码:

public Builder() {
      this.method = "GET";
      this.headers = new Headers.Builder();
    }

Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.tags = Util.immutableMap(builder.tags);
  }

可以看到Request里面也有一个Builder类,Builder构造函数默认请求方式为Get,还有对请求头部的封装。

总结:新建一个Request对象里面主要封装了请求路径,头部信息等。

3.用newCall(request)将Reuqest对象封装成Call对象时,源码:

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

//可以看到newCall方法里面是调用了RealCall类的newRealCall方法,下面到RealCall类里看看。

  static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// 调用RealCall的构造函数
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
  }

//下面是RealCall类构造函数

  private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
    ......
  }

总结:newCall方法实际生成RealCall对象,对象里面包含了Client客户对象和Request的请求对象,还新建了一个RetryAndFollowUpInterceptor 重定向拦截器。

4.Call对象调用的execute()同步请求方法,源码:

@Override public Response execute() throws IOException {
   .....
//开启事件监听
   eventListener.callStart(this);   
   try {
//分发器用executed方法将Call对象添加进同步运行队列
     client.dispatcher().executed(this); 
//结果是从拦截器链方法中获取的
     Response result = getResponseWithInterceptorChain();  
     ......
   } finally {
//finish方法里将Call对象从Calls队列中移出
     client.dispatcher().finished(this);  
   }
}

//下面进到client.dispatcher().executed(this)的excuted方法里面

 synchronized void executed(RealCall call) {
//runningSyncCalls是正在运行的同步队列
    runningSyncCalls.add(call);  
 }

总结:excute()同步申请方法,分发器将Call对象添加到同步运行队列。请求数据从Response result = getResponseWithInterceptorChain();  中获取。

5.enqueue异步请求方法,源码:

@Override public void enqueue(Callback responseCallback) {
//判断是否请求过这个Call对象
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
//异常检测
    captureCallStackTrace();
//事件监听
    eventListener.callStart(this);
//调用分发器的enqueue方法,分发器在client创建时新建的。
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

可以看到enqueue方法里面又调用了分发器的enqueue方法,在enqueue方法里新建了一个AsyncCall对象,

AsyncCall对象传入我们上一层传入enqueue方法的CallBack对象。

接下来看看上面的AsyncCall类是什么东西。

final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

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

看到AsyncCall继承自NamedRunnable,再来看看NamedRunnable是什么东西

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

可以看到NamedRunnable实现了Runnable接口,里面最核心的就是在run方法里面运行了execute()方法,这个方法的具体实现其实跟同步请求execute方法一样,在AsyncCall类里,和同步请求最后的execute()是同一个方法。

@Override protected void execute() {
      .......
      Response response = getResponseWithInterceptorChain(); 
      .....
  }

我把大部分代码都省了,最重要的就上面那句,跟同步请求一样,最后结果也是经过一系列拦截器的方法后的数据。

那么同步跟异步有什么区别呢?

异步传入enqueue方法的CallBack的对象实现了Runnable接口,让它在子线程中运行。

还有,接下来回到开头看看client.dispatcher().enqueue(new AsyncCall(responseCallback));这句,分发器类里的变量和它的enqueue方法(刚刚看的是AsyncCall类)。

public final class Dispatcher {
  //默认的最大并发请求量 
  private int maxRequests = 64;
  //单个host支持的最大并发量
  private int maxRequestsPerHost = 5;  
  .........
  //异步等待队列
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  //异步运行队列
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  //同步运行队列
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
  ...........
  //计算队列内请求数量的方法,如果异步请求满足不超过64,5的条件则进行请求操作。
  //有的版本OkHttp是通过promoteAndExecute()进行条件判断,原理差不多
  synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
  //把Call对象添加进runningAsyncCalls异步进行队列
      runningAsyncCalls.add(call);
  //创建线程池并执行Call请求
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }
  ......
}    

分发器对Request类型进行判断,把Call对象添加进readyAsyncCalls异步等待队列或runningAsyncCalls,而在同步请求时分发器是把Call对象直接添加到runningSyncCalls同步运行队列。异步请求最后开启线程池获取数据。

总结:enqueue方法传入CallBack对象,CallBack对象被封装为AsyncCall,AsyncCall内部实现了Runnable接口,分发器进行判断,如果符合条件就把AsyncCall传入了异步进行对列,开启线程池在子线程获取数据。否则添加进异步等待队列。

readyAsyncCalls异步等待队列的请求什么时候能运行呢?

我们已经知道异步跟同步请求通过分发器分发队列,但是最后都要经过AsyncCall类的execute()方法来获取数据,execute()方法最后finally里面运行client.dispatcher().finished(this);方法,我们进去finished方法看看。

//异步请求的finished方法 
void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }
//同步请求的finished方法
 void finished(RealCall call) {
    finished(runningSyncCalls, call, false);
  }
//具体的finished方法
 private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
//将实现的Call对象从队列中移出
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
//注意promoteCalls是第三个参数,既如果是异步请求才会运行该方法,重新整理队列。
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }
    if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }
  }

进入promoteCalls()方法

  private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
//循环到队列最后一个元素,call为最后一个请求
    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();
//如果符合条件就把call从等待队列移除加入运行队列。
      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }
      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }

总结:在readyAsyncCalls队列中的请求会在异步请求的finished方法里进行判断,如果符合条件则进入runningAsyncCalls。

Dispatcher分发器:

从上面的Dispatcher类可以看出分发器有3个队列,异步有两个队列是运用了消费者模式,类中还有excuted,enqueue,finished方法,Dispatcher主要作用就是根据Request类型将Call对象调入不同队列,最后用finished将完成的请求移除队列并把等待的请求调进运行队列。

ExecutorService线程池: 

下面进到executorService().execute(call)看看,

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

第一个参数代表核心线程数量,为0就代表线程空闲之后不会被保留,会被销毁;如果大于0,即使本地任务执行完毕,核心线程也不会被销毁。

第二个参数是int整数的最大值,他表示的是线程池中可以容纳的最大线程数量。但是确实它得满足64跟5的条件。

第三个keepAliveTime,当我们的线程池中线程数量大于核心线程数量时,空闲线程需要等待60秒的时间才会被终止。

OkHttp拦截器

我们已经知道了请求最后都是从Response result = getResponseWithInterceptorChain()这句中获取的数据。

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

总归创建了6个拦截器

全部拦截器的基本流程:

  1. 在发起请求前对request进行处理。
  2. 调用chain.proceed()方法,获取下一个拦截器的response。
  3. 对reponse进行处理,返回给上一个拦截器。

RetryAndFollowUpInterceptor 重定向拦截器

负责失败重连的拦截器。

  1. 创建StreamAllocation对象,但是没有使用,它的使用是在ConnectInterceptor。它负责为一次“请求”寻找“连接”并建立“流”。Connection是建立在Socket之上的物理通信信道,而Stream则是代表逻辑的流,如果有多个stream(即多个 Request) 都是连接在一个 host 和 port上,那么它们就可以共同使用同一个 socket ,这样做的好处就是可以减少TCP的一个三次握手的时间。
  2. 调用RealInterceptorChain.proceed(...)进行网络请求
  3. 根据异常结果或响应结果判断是否进行重新请求(20次)
  4. 调用下一个拦截器,对response进行处理,返回上一个拦截器

BridgeInterceptor  桥拦截器

该拦截器是链接客户端代码和网络代码的桥梁,它首先将客户端构建的Request对象信息构建成真正的网络请求;然后发起网络请求,最后就是讲服务器返回的消息封装成一个Response对象。

  1. 将用户构建的Request请求转化为能够进行网络访问的请求
  2. 执行符合条件的请求
  3. 将Response转化为用户可用的Response,OkHttp支持Gzip压缩,GzipSource类对数据进行解压。

CacheInterceptor 缓存拦截器

该拦截器用于处理缓存的功能,主要取得缓存 response 返回并刷新缓存。

  1. 底层使用的是 DiskLruCache 缓存机制。
  2. CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
  3. get() 方法获取一个 CacheStrategy 对象。CacheStrategy,它是一个策略器,负责判断是使用缓存还是请求网络获取新的数据。
  4. responseCache.put(userResponse);
  5. put(userResponse)方法将 userResponse 缓存到本地。 

为什么需要缓存 Response?

  • 客户端缓存就是为了下次请求时节省请求时间,可以更快的展示数据。
  • OKHTTP 支持缓存的功能

ConnectInterceptor  连接拦截器

该拦截器的功能就是负责与服务器建立 Socket 连接,并且创建了一个 HttpCodec它包括通向服务器的输入流和输出流。

  1. 获取到第一个拦截器生成的StreamAllocation对象,
  2. 通过StreamAllocation对象,streamAllocation.newStream()创建HttpCodec对象,
  3. streamAllocation.connection()获取一个RealConnection对象
  4. 将HttpCodec、RealConnection对象传递给拦截器

NetworkInterceptors 网络拦截器

  • 允许像重定向和重试一样操作中间响应。
  • 网络发生短路时不调用缓存响应。
  • 在数据被传递到网络时观察数据。
  • 有权获得装载请求的连接。

CallServerInterceptor 调用服务拦截器

该拦截器的功能使用 HttpCodec与服务器进行数据的读写操作的。

  1. 首先是获取了httpCodec对象,该对象的主要功能就是对不同http协议(http1.1和http/2)的请求和响应做处理,该对象的初始化是在ConnectIntercepor的intercept里面
  2. OkHttp通过OKIO的Sink对象(该对象可以看做Socket的OutputStream对象)的writeRequest来向服务器发送请求的。
  3. 将OKIO的Source对象作为输入流InputStream对象读取数据封装为Response对象。
  4. 100-continue用于客户端在发送POST数据给服务器前,征询服务器情况,看服务器是否处理POST的数据,如果不处理,客户端则不上传POST数据,如果处理,则POST上传数据。

ConnectionPool

OkHttp中所有的连接(RealConnection)都是通过ConnectionPool来管理。

  1. StreamAllocation里面包含了RealConnection对象,该对象归根是由ConnectionPool的get() 方法遍历 connections 中的所有 RealConnection 寻找同时满足条件的RealConnection,重复利用RealConnection。
  2. ConnectionPool类里put方法,采用GC回收算法,异步触发清理任务,然后将健康的connection添加到connections队列中。调用cleanup方法执行清理,并等待一段时间,持续清理,其中cleanup方法返回的值来来决定而等待的时间长度。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章