• OkHttp相关知识(二)


    okhttp中一次网络请求的大致过程:

      1. Call对象对请求的封装
      1. dispatcher对请求的分发
      1. getResponseWithInterceptors()方法

    一、OkHttp同步方法总结:

    1. 创建OkHttpClient和构建了携带请求信息的Request对象
    2. 将Request封装成Call对象
    3. 调用Call的execute()发送同步请求

    call.execute实际上调用的是RealCall.execute()方法:

     @Override public Response execute() throws IOException {
        synchronized (this) {
          if (executed) throw new IllegalStateException("Already Executed");
          executed = true;
        }
        transmitter.timeoutEnter();
        transmitter.callStart();
        try {
          client.dispatcher().executed(this);
          return getResponseWithInterceptorChain();  //这行语句的执行在后面拦截器链的介绍中会阐述
        } finally {
          client.dispatcher().finished(this);
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    其中client.dispatcher().executed(this);这个步骤就是将本次call请求加入到runningSyncCalls ArrayDeque队列中

      /** Used by {@code Call#execute} to signal it is in-flight. */
      synchronized void executed(RealCall call) {
        runningSyncCalls.add(call);
      }
    
    • 1
    • 2
    • 3
    • 4

    其中dispatcher中比较重要的几个变量:

      /** Executes calls. Created lazily. */
      private @Nullable ExecutorService executorService;
    
      /** Ready async calls in the order they'll be run. */
      private final Deque readyAsyncCalls = new ArrayDeque<>();
    
      /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
      private final Deque runningAsyncCalls = new ArrayDeque<>();
    
      /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
      private final Deque runningSyncCalls = new ArrayDeque<>();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    OkHttp同步需要注意:

    • 送请求后,就会进入阻塞状态,直到收到响应。
      在这里插入图片描述

    二、OkHttp异步方法总结

    1. 创建OkHttpClient和构建了携带请求信息的Request对象
    2. 将Request封装成Call对象
    3. 调用Call的enqueue方法进行异步请求

    OkHttp异步需要注意:

    • OnResponse 和 onFailure工作在工作线程,即子线程

    同步和异步的区别

      1. 发起请求的方法调用不同
        同步是调用execute(),异步是调用enqueue()
      1. 阻塞线程与否
        同步会阻塞线程,异步不会阻塞线程,它会开启一个异步线程去完成网络请求的操作
      1. 同步请求就是执行请求的操作是阻塞式,直到Http响应返回
      1. 异步请求就类似非阻塞式的请求,它的执行结果一般都是通过接口回调的方式告知调用者

    equeue()方法后续源码流程:

    @Override public void enqueue(Callback responseCallback) {
        synchronized (this) {
          if (executed) throw new IllegalStateException("Already Executed");
          executed = true;
        }
        transmitter.callStart();
        client.dispatcher().enqueue(new AsyncCall(responseCallback));
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    其中,AsyncCall其实就是一个Runnable,调用Dispatcher的enqueue()方法

    void enqueue(AsyncCall call) {
        synchronized (this) {
          readyAsyncCalls.add(call);
    
          // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
          // the same host.
          if (!call.get().forWebSocket) {
            AsyncCall existingCall = findExistingCallWithHost(call.host());
            if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
          }
        }
        promoteAndExecute();
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
     private boolean promoteAndExecute() {
        assert (!Thread.holdsLock(this));
    
        List executableCalls = new ArrayList<>();
        boolean isRunning;
        synchronized (this) {
          for (Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) {
            AsyncCall asyncCall = i.next();
    
            if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
            if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.
    
            i.remove();
            asyncCall.callsPerHost().incrementAndGet();
            executableCalls.add(asyncCall);
            runningAsyncCalls.add(asyncCall);
          }
          isRunning = runningCallsCount() > 0;
        }
    
        for (int i = 0, size = executableCalls.size(); i < size; i++) {
          AsyncCall asyncCall = executableCalls.get(i);
          asyncCall.executeOn(executorService());
        }
    
        return isRunning;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    其中,runningAsyncCalls表示正在运行的任务队列,主要用于判断并发表示的数量,readyAsyncCalls表示缓存等待请求的队列,
    maxRequests = 64–>表示当前正在请求的Runnable最大数
    maxRequestsPerHost = 5–>表示当前网络向同一个host请求的最大数

    Enqueue方法总结:

      1. 判断当前call
      1. 封装成了一个AsyncCall对象
      1. client.dispatcher().enqueue()

    Q1、OkHttp如何实现同步和异步请求?

    发送的同步/异步请求都会在dispatcher中管理其状态

    Q2:到底什么是dispatcher

    dispatcher的作用为维护请求的状态,并维护一个线程池,用于执行请求。
    在这里插入图片描述

    Q3. 异步请求为什么需要两个队列

    • Deque :正在等待执行,等待缓存的队列

    • Deque:正在运行的任务队列

    • Dispatcher 生产者

    • ExecutorService:消费者池

    OkHttp的任务调度

    Call执行完肯定需要在runningAsyncCalls队列中移除这个线程

    readyAsyncCalls队列中的线程在什么时候才会被执行呢?

    private boolean promoteAndExecute() {
        assert (!Thread.holdsLock(this));
    
        List executableCalls = new ArrayList<>();
        boolean isRunning;
        synchronized (this) {
          for (Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) {
            AsyncCall asyncCall = i.next();
    
            if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
            if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.
    
            i.remove();
            asyncCall.callsPerHost().incrementAndGet();
            executableCalls.add(asyncCall);
            runningAsyncCalls.add(asyncCall);
          }
          isRunning = runningCallsCount() > 0;
        }
    
        for (int i = 0, size = executableCalls.size(); i < size; i++) {
          AsyncCall asyncCall = executableCalls.get(i);
          asyncCall.executeOn(executorService());
        }
    
        return isRunning;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    三、OkHttp拦截器

    拦截器是OkHttp中提供一种强大机制,它可以实现网络监听、请求以及响应重写、请求失败重试等功能。
    在这里插入图片描述

    系统提供的内部拦截器链:

    在这里插入图片描述

    • RetryAndFollowUpInterceptor
      重试和失败重定向拦截器,这个拦截器主要做些初始化的工作和创建一个StreamAllocation对象(OkHttp3.0代码中没有该对象),用来传递给后面的拦截器
      功能:
      (1)创建StreamAllocation对象
      (2)调用RealInterceptorChain.proceed(…)进行网络请求
      (3)根据异常结果或者响应结果判断是否要进行重新请求

      最大的请求次数 MAX_FOLLOW_UPS = 20

      (4)调用下一个拦截器,对response进行处理,返回给上一个拦截器

    • BridgeInterceptor
      桥接和适配拦截器
      功能:
      (1)是负责将用户构建的一个Request请求转化为能够进行网络访问的请求
      (2)将这个符合网络请求的Request进行网络请求
      (3)将网络请求回来的响应Response转化为用户可用的Response

    • CacheInterceptor
      缓存拦截器
      BridgeInterceptor和CacheInterceptor主要是用于补充用户请求创建过程中缺少一些必须的Http请求头和处理缓存的一些功能。

    • ConnectInterceptor
      连接拦截器,负责建立可用的连接,ConnectInterceptor是CallServerInterceptor的基础。
      (1) ConnectInterceptor获取Interceptor传过来的StreamAllocation,StreamAllocation.newStream()
      (2)将刚才创建的用于网络IO的RealConnection对象,以及对于与服务器交互最为关键的HttpCodec等对象传递给后面的拦截器
      流程功能:
      (1)创建一个RealConnection对象
      (2)选择不同的链接方式
      (3)CallServerInterceptor

    • CallServerInterceptor
      该适配器主要负责是将Http的请求写进网络的IO流中,并从网络IO流中读取服务端返回给客户端的数据。

    RealCall.java

     Response getResponseWithInterceptorChain() throws IOException {
        // Build a full stack of interceptors.
        List interceptors = new ArrayList<>();
        interceptors.addAll(client.interceptors());  //用户自定义的拦截器
        interceptors.add(new RetryAndFollowUpInterceptor(client));
        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, transmitter, null, 0,
            originalRequest, this, client.connectTimeoutMillis(),
            client.readTimeoutMillis(), client.writeTimeoutMillis());
    
        boolean calledNoMoreExchanges = false;
        try {
          Response response = chain.proceed(originalRequest);
          if (transmitter.isCanceled()) {
            closeQuietly(response);
            throw new IOException("Canceled");
          }
          return response;
        } catch (IOException e) {
          calledNoMoreExchanges = true;
          throw transmitter.noMoreExchanges(e);
        } finally {
          if (!calledNoMoreExchanges) {
            transmitter.noMoreExchanges(null);
          }
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    OkHttp拦截器总结:

      1. 创建一系列拦截器,并将其放入一个拦截器list中,这list拦截器包含用户、网络和系统内部的拦截器
      1. 创建一个拦截器链RealInterceptorChain,并执行拦截器链的proceed方法。
        proceed方法的核心就是创建下一个拦截器
      1. 在发起请求前对request进行处理
      1. 调用下一个拦截器,获取response
      1. 对response进行处理,返回给上一个拦截器
  • 相关阅读:
    正则表达式
    可用于嵌入式的解释器调研对比,及lua解释器介绍
    1027推免分享材料 备份
    GStreamer appsrc 等插件实现视频音频混流,录制和推流
    copy模块的copy()和deepcopy()
    情绪智力测试
    5-管线‘|‘常用指令(筛选、排序、tab转空格、重定向、切分文件等)
    小商品公众号微信店铺搭建的作用是什么
    不能说的秘密:加密信件
    uniCloud云开发及一键生成代码
  • 原文地址:https://blog.csdn.net/xuyin1204/article/details/127878940