OkHttp源码详解之二完结篇

OKHttp3源码分析——学习笔记 文章目录1. HttpClient与HttpUrlConnection的区别2. OKHttp源码分析使用步骤:dispatcher任务调度器,Request请求RealCallAsyncCall3. OKHttp架构分析1. 异步请求线程池, Dispather2. 连接池清理线程池- ConnectionPool3. 缓存整理线程池 DisLruCache4. Http2异步事务线程池, ht... 阅读详情

1. 请大家思考几个问题

在开始本文之前,请大家思考如下几个问题。并请大家带着这几个问题,去本文寻找答案。如果你对下面几个问题的答案了如指掌那本文可以略过不看

  1. 在浏览器中输入一个网址,按回车后发生了什么?
  2. Okhttp的TCP连接建立发生在什么时候?
  3. Okhttp的Request请求发生在什么时候?
  4. Okio与Okhttp是在什么时候发生关联的?
  5. Okhttp的Interceptor和Chain是怎么串联起来的?
  6. Okhttp同步请求和异步请求分别是怎么实现的。异步请求有限制吗?

本文将围绕以上6个问题对Okhttp做一个简单的讲解,如有遗漏的知识点,欢迎在评论区指出,一起探讨成长。

2. Http请求的流程

首先来回答第一个问题“在浏览器中输入一个网址,按回车后发生了什么?”。Http权威指南一书给出的答案是发生了7件事情

  1. 浏览器从url中解析出主机名
  2. 浏览器将服务器的主机名转换成服务器的IP地址
  3. 浏览器将端口号从url中解析出来
  4. 浏览器建立一条与Web服务器的TCP连接
  5. 浏览器向服务器发送一条Http请求报文
  6. 服务器向浏览器回送一条Http响应报文
  7. 关闭连接,浏览器显示文档

以上七步是每一个Http请求必须要做的事情,Okhttp库要实现Http请求也不例外。这七个步骤的每一步,在Okhttp中都有体现。

  1. HttpUrl类负责步骤1和步骤3的主机名和端口解析
  2. Dns接口的具体实现负责步骤2的实现
  3. RealConnection类就是步骤4中的那条TCP连接
  4. CallServerInterceptor拦截器的intercept方法负责步骤5和步骤6的发送报文和接收报文
  5. ConnectPool连接池中提供了关闭TCP连接的方法
  6. Okio在哪操作?当然是往Socket的OutputStream写请求报文和从Socket的InputStram读取响应报文了

至此文章开头提出的6个问题,前5个都已经有了一个简单的回答。至于第六个问题,异步在Okhttp中用的缓存线程池,理论上缓存线程池,是当有任务到来,就会从线程池中取一个空闲的线程或者新建线程来处理任务,而且缓存线程池的线程数是Integer.MAX_VALUE。理论上是没有限制的。但是Dispatcher类在线程池的基础上,做了强制限制,最多可以同时处理的网络请求数64个,对于同一个主机名,最多可以同时处理5个网络请求。接下来我带大家从源码的角度来寻找这几个问题的答案

3.初识Okhttp

首先我们来写两个简单的例子来使用Okhttp完成get和post请求。

同步get请求

OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  Response response = client.newCall(request).execute();
  return response.body().string();
}

同步post请求

public static final MediaType JSON
    = MediaType.parse("application/json; charset=utf-8");

OkHttpClient client = new OkHttpClient();

String post(String url, String json) throws IOException {
  RequestBody body = RequestBody.create(JSON, json);
  Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();
  Response response = client.newCall(request).execute();
  return response.body().string();
}

异步get请求

OkHttpClient client = new OkHttpClient();

void run(String url) throws IOException {
    Request request = new Request.Builder()
            .url(url)
            .build();

    client.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {

        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            System.out.println(response.body().string());
        }
    });
}

异步post请求

public static final MediaType JSON
            = MediaType.parse("application/json; charset=utf-8");

    OkHttpClient client = new OkHttpClient();

    void post(String url, String json) throws IOException {
        RequestBody body = RequestBody.create(JSON, json);
        Request request = new Request.Builder()
                .url(url)
                .post(body)
                .build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {

            }
        });
    }

Okhttp完成一次网络请求,过程如下

  1. 创建一个OkHttpClient对象client
  2. 创建一个Request对象request,并设置url
  3. 调用client.newCall(request)生成一个Call对象call
  4. 调用call.execute()[同步调用]或者call.enqueue(callback)[异步调用]完成网络请求

4. 同步和异步网络请求过程

同步调用过程如下

OkHttpClient.newCall(Request r)  => RealCall.execute() => RealCall.getResponseWithInterceptorChain()

异步调用过程如下

OkHttpClient.newCall(Request r) => RealCall.enqueue(Callback) => Dispatcher.enqueue(AsyncCall)
    => 线程池执行AsyncCall =>AsyncCall.execute() => RealCall.getResponseWithInterceptorChain()

从上图可以看出异步调用比同步调用步骤更长,也更复杂。在这里我把整个调用过程分成两个阶段,RealCall.getResponseWithInterceptorChain()称作“网络请求执行阶段”,其余部分称作“网络请求准备阶段”。由于“网络请求执行阶段”涉及到链式(Chain)调用以及各种拦截器的执行比“网络请求准备阶段”复杂太多。所以我们先来看“网络请求准备阶段”,这个阶段也需要分成同步和异步两种方式来讲解

首先我们来看下准备阶段的公共调用部分OkHttpClient.newCall(Request r)

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

Call是什么,在我看来Call可以理解成是对网络请求封装,它可以执行网络请求,也可以取消网络请求。我们看下Call的类定义

public interface Call extends Cloneable {

  Request request();

  Response execute() throws IOException;


  void enqueue(Callback responseCallback);


  void cancel();


  boolean isExecuted();

  boolean isCanceled();


  Call clone();

  interface Factory {
    Call newCall(Request request);
  }
}
  1. request()返回的Request对象
  2. execute()同步执行网络操作
  3. enqueue(Callback responseCallback)异步执行网络操作
  4. cancel()取消网络操作

RealCall是Call的实现类。我们重点来看下execute()和enqueue(Callback responseCallback)

execute()方法

@Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    try {
      //这里只是把call存到Dispatcher的列表中
      client.dispatcher().executed(this);
      //真正执行网络操作
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  }

enqueue(Callback callback)

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

我们注意到AsyncCall类,首先它是RealCall类的非静态内部类,它持有RealCall的对象,它可以做任何RealCall能做的事情。同时它继承自NamedRunnable。NamedRunnable是Ruannable的子类。它主要有两个功能,1.可以被线程池执行 2.修改当前执行线程的线程名(方便debug)。由于这几个类都比较简单,而且篇幅有限,这里就不贴代码了。请自行查阅

当然到这里异步执行的准备阶段并没有结束,它是怎么被子线程执行的呢。这里我们就需要在Dispatcher类中寻找答案了。Dispatcher是干嘛用的,它的功能就是负责分发用户创建的网络请求,以及控制网络请求的个数,以及上一个网络请求结束后,要不要执行等待中的网络请求。下面我们来看下Dispatcher都有哪些成员变量,以及这些成员变量的作用

public final class Dispatcher {
  //最多可以同时请求数量
  private int maxRequests = 64;
  //每个host最大同时请求数量
  private int maxRequestsPerHost = 5;
  //当没有任务执行的回调 比如说执行10个任务,10个任务都执行完了会回调该接口
  private Runnable idleCallback;

  /** Executes calls. Created lazily. */
  //线程池 用的是缓存线程池(提高吞吐量,并且能及时回收无用的线程)
  private ExecutorService executorService;

  /** Ready async calls in the order they'll be run. */
  //等待的异步任务队列
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  //正在执行的异步任务队列 这个放的是Runnable
  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  //正在执行的同步任务队列 这个存储的是RealCall
  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;
  }

 }

总结下几个比较重要的概念

  1. executorService 线程池并且是缓存线程池,最最大程度提高吞吐量,并且能回收空闲的线程
  2. Deque readyAsyncCalls异步请求等待队列,当异步请求数超过64个,或者单个主机名请求超过5个,网络任务(AsyncCall)会放到该队列里。当任务执行完毕,会调用promoteCalls()把满足条件的网络任务(AsyncCall)放到线程池中执行
  3. Deque runningAsyncCalls 正在执行的异步任务队列
  4. Deque runningSyncCalls 正在执行的同步任务队列

紧接着我们来看下Dispatcher的enqueue(AsyncCall call)

synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
     //如果请求没有超过限制 用线程池执行网络请求
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      //如果超过了限制,把请求放到异步等待队列中去,什么时候被唤醒?finished后
      readyAsyncCalls.add(call);
    }
  }

通过线程池执行AsyncCall 最终调用的是AsyncCall的run(),而run()中调用的是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) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }

在execute()中调用了getResponseWithInterceptorChain(),也就是我们前面说的“网络请求执行阶段”。至此,同步和异步网络请求的“网络请求准备阶段”就讲解完了,接下来我们讲解“网络请求执行阶段”

5. Interceptor和Chain

Response getResponseWithInterceptorChain() throws IOException {
    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));

    //用Chain把List中的interceptors串起来执行
    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    return chain.proceed(originalRequest);
  }

要理解上面的代码。我们需要搞清楚两个概念 Interceptor和Chain。我们先来看下它们的定义

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

  interface Chain {
    Request request();

    Response proceed(Request request) throws IOException;

    Connection connection();
  }
}

可以举一个比较形象的例子来解释它们的关系。就以我们IT行业来讲。老板说想要一个APP,然后把需求转化成任务流转给产品经理,产品经理构思了下,然后把任务流转给设计师,设计师一通设计之后,把设计图流转给码农,码农接到任务后就开始加班加点的编码,最终写出了APP,然后把APP交给设计师,让设计师查看界面是否美观,设计师再把APP流转给产品经理,产品经理觉得很满意,最终把APP交付给老板。老板看了很满意,说大家伙晚上加个鸡腿。虽然实际生产中并不是这样的一个流程,想了很久觉得还是这样讲,更好理解。对应到这个例子中,产品经理、设计师、程序员他们都是真正干活的家伙,他们对应的是Interceptor。Chain是什么,是老板吗?不是!!Chain只是一套规则,对应的就是例子里的流转流程。

image

image

对照图片,“网络请求执行阶段”会依次执行Interceptors里的Interceptor。Interceptor执行分成三步。第一步:处理请求 第二步:执行下个Interceptor 第三步:处理响应

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

首先执行的就是retryAndFollowUpInterceptor

RetryAndFollowUpInterceptor

//第一部分 根据url解析出主机名 解析端口
Request request = chain.request();

    streamAllocation = new StreamAllocation(
        client.connectionPool(), createAddress(request.url()), callStackTrace);

....省略

while(true){
    ....
    //第二部分处理下一个Interceptor
    response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
    releaseConnection = false;
    ....
    //第三部分 处理响应,如果是重定向,用while循环,重新处理下一个Interceptor
     if (followUp == null) {
        if (!forWebSocket) {
          streamAllocation.release();
        }
        return response;
      }

      closeQuietly(response.body());

      if (++followUpCount > MAX_FOLLOW_UPS) {
        streamAllocation.release();
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }



}

BridgeInterceptor和CacheInterceptor请读者自行分析

ConnectInterceptor中会解析主机DNS并且建立TCP连接,Socket的输入输出流通过Source和Sink处理

public final class ConnectInterceptor implements Interceptor {
  public final OkHttpClient client;

  public ConnectInterceptor(OkHttpClient client) {
    this.client = client;
  }

  @Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    StreamAllocation streamAllocation = realChain.streamAllocation();


    boolean doExtensiveHealthChecks = !request.method().equals("GET");

    //这里会解析DNS并且建立TCP连接,Socket的输入输出流会和Okio的Source和Sink关联
    HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }
}

CallServerInterceptor 通过httpCodec的sink向socket发送请求,并且通过httpCodec的openResponseBody把socket的输入流写入到Response中

@Override public Response intercept(Chain chain) throws IOException {
    HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();
    StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
    Request request = chain.request();

    long sentRequestMillis = System.currentTimeMillis();
    httpCodec.writeRequestHeaders(request);

    Response.Builder responseBuilder = null;
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
      // Continue" response before transmitting the request body. If we don't get that, return what
      // we did get (such as a 4xx response) without ever transmitting the request body.
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        httpCodec.flushRequest();
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

      // Write the request body, unless an "Expect: 100-continue" expectation failed.
      if (responseBuilder == null) {
        Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
      }
    }

    httpCodec.finishRequest();

    if (responseBuilder == null) {
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    int code = response.code();
    if (forWebSocket && code == 101) {
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)
          .build();
    } else {
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))//真正把body写进去
          .build();
    }

    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      streamAllocation.noNewStreams();
    }

    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }

    return response;
  }

6. 结束语

OkHttp库还是挺庞大的,涉及到很多Http的基础知识。这里只是讲解了OkHttp的一小部分。很多细节的东西也没有深入讲解。比如说Socket的建立,连接池的管理,HttpCodec如何解析输入输出流,路由的细节。希望有机会可以再深入的讲解一番

框架和流程——OkHttp 源码详解(一) 前言 OkHttp应该是目前Android平台上使用最为广泛的开源网络库了,Android 在6.0之后也将内部的HttpUrlConnection的默认实现替换成了OkHttp。 网上很多分析OkHttp的,都是在总体流程上,没有那么的细致,甚至有的同学看完了文章,认为OkHttp没有DNS解析。我说有,他说没有,我说有,他说没有,我说有,他说没有,我就站起来了,很快啊,年轻人, 一、鸟瞰OkHttp 下面的代码是一个很简单的例子,一步一步分析,起内部的工作原理 OkHttpClient client 阅读详情

相关推荐

从零构建高精度电子秤:HX711与STM32的时序通信陷阱与实战避坑指南

本文详细解析了使用STM32F103C8T6与HX711模块构建高精度电子秤的通信时序陷阱与实战解决方案。重点探讨了微秒级延迟实现、数据读取算法、校准方法和滤波优化,帮助开发者避开常见通信问题,提升电子秤的测量精度和稳定性。

data3的博客 783

探索 OkHttp 原理

主机组件标识了因特网上能够访问资源的宿主机器,比如 www.xxx.com 或 192.168.1.66 。 4. 端口号 端口组件标识了服务器正在监听的网络端口,对下层使用了 TCP 协议的 HTTP 来说,默认端口为 80 。 5. 查询路径 服务器上资源的本地名,由斜杠( / )将其与前面的 URL 组件分隔开来,路径组件的语法与服务器的方案有关。 路径组件说明了资源位于服务器的什么地方,类似于分级的文件系统路径,比如 /goods/details 。 6. 查询参数 比如数据库服务是可以通过提供查询

m0_69440777的博客 1291

《计算机科学概论(第13版)》5-12章

定序是按给定语句的顺序应用每个算法步骤。(primitive):建立一组定义明确的构建块,利用它们来构造和执行算法的表示。可以消除歧义,不会引起误解,就像咱们传统的自然语言(用英语来沟通交流)。(programming language):原语的集合以及说明如何组合这些原语来表示比较复杂的想法的规则集合。用程序设计语言描述的算法可以在计算机上执行。每个原语都有自己的语法和语义,语法指的是原语的符号表示,语义是指该原语的含义。伪代码:用户开发过程中非正式表达想法的符号系统。

输入知识,消化知识,输出知识,分享知识,教人让我学习更好,业精于勤荒于嬉,行成于思毁于随 1036

OkHttp - maxRequests & maxRequestPerHost

OkHttp - maxRequests & maxRequestPerHost

3497

研发工作量科学量化评估模型:从多维系数到等级落地指南

摘要:本文提出了一套基于多维系数的科学量化评估模型,用于精准评估软件开发工作量。该模型从需求复杂度、技术难度、质量要求和依赖风险四个核心维度进行量化评估,通过系数乘积计算综合工作量。文章详细说明了各维度的系数区间划分标准,并演示了从基础工作量估算到综合系数计算的具体步骤。该模型通过结构化评估过程,将主观经验转化为客观量化指标,帮助团队提高工作量评估的准确性,增强项目交付的可预测性。其价值在于使评估过程透明化、结构化和可追溯,从而减少项目延期风险,优化资源规划。

友莘居士的博客 1684

几款常见存储系统的对比分析

如何对比选择存储系统?本文给你答案!本文将详细对比NetApp、Lsilon、Vast Data、DDN lustre和WekaFS的五个存储系统的简单性、速度、规模及标准。

hongcloudtech的博客 1679

Okhttp 同步异步源码分析

Okhttp源码分析 Okhttp在使用过程中我们表面能所看到的流程 大概是是这样,通过OkHttpClient将构建的Request转换为请求呼叫,然后在RealCall中进行异步或同步任务,最后通过一些的拦截器interceptor发出网络请求和得到返回的response 下面我们来看一张请求图。图片来自于网络作者,文末会注明。 我们先来一段 Okhttp 请求 事咧 (异步请求) publ...

qq_30107007的博客 577

【用unity实现100个游戏之15】开发一个类保卫萝卜的Unity2D塔防游戏1(附项目源码)

当今,塔防游戏已经成为游戏市场上备受欢迎的一类游戏类型。《保卫萝卜》作为其中的经典之作,深受玩家喜爱。本项目旨在基于《保卫萝卜》的玩法和特点,开发一个Unity2D塔防游戏,让玩家可以在游戏中体验到精彩的策略对抗与刺激的关卡挑战。本项目将通过Unity引擎进行开发,利用2D游戏开发相关技术,包括但不限于精灵动画、碰撞检测、UI界面设计等。我们将实现萝卜作为关键角色的防守任务,并设计各种怪物和防御塔,使得游戏具有多样的战术策略和游戏乐趣。

向宇的博客,专注php/web全栈 unity游戏开发,欢迎大家评论纠错 4924

OkHttp相关知识(一)

调用流程:   OkHttp请求过程中最少只需要接触OkHttpClient、Request、Call、Response,但是框架内部进行大量的逻辑处理。   所有的逻辑大部分集中在拦截器中,但是在进入拦截器之前还需要依赖分发器来调配请求任务。 分发器:内部维护队列与线程池,完成请求调配; 拦截器:完成整个请求过程。 ...

xuyin1204的博客 454

QCC51XX---人机接口设备协议( HID)

QCC51XX---人机接口设备协议( HID)

weixin_42162924的博客 820

C# ScottPlot 5.0 WinForms 十字光标示例:轻松实现动态数值显示,你学会了吗?

ScottPlot是一个免费、开源的.NET交互式绘图库,能够轻松地实现大型数据集的交互式显示。它支持多种图表类型,如折线图、柱状图、饼图、散点图等。ScottPlot的性能非常出色,即使是处理千万级别的数据也游刃有余。

java专栏 2219

架构师学习--okhttp3原理解析

okhttp是目前最流行的网络框架,为什么这样说呢?本文将带领大家逐步解密。 一、使用 想必大家在项目中都是如下使用的,这里以一部请求为例: 这里可以看出发出一条请求,离不开这几个类: OkHttpClient Request Call Callback 下面就对这四个类逐个击破。 二、常用类介绍 1、OkHttpClient 这个类使用构造者模式,通过这个类我们能够设置分发器Dispatc...

qinbin2015的博客 642

全网最全RT-DETR涨点改进,基于官方源码1:1还原,在Ultralytics项目中配置rtdetr-r18、rtdetr-r34、rtdetr-r50、rtdetr-r101版本~小论文大论文必备

本文详细介绍了如何将RT-DETR的ResNet18/34/50/101/154版本移植到ultralytics项目中。主要内容包括:1) RT-DETR核心特点,如高效混合编码器、IoU感知查询选择等;2) 提供完整ResNet核心代码;3) 分步配置指南,包括创建模块文件和修改tasks.py;4) 各版本yaml配置文件的创建方法。该方案可帮助研究者快速实现RT-DETR改进,适用于目标检测相关论文研究。所有配置文件均可正常运行,为实验部署提供了便利。

Ai缝合怪、助力小伙伴高效发论文 3935

okhttp源码解析

OkHttp是一个非常优秀的网络请求框架,已被谷歌加入到Android的源码中。目前比较流行的Retrofit也是默认使用OkHttp的。所以OkHttp的源码是一个不容错过的学习资源,学习源码之前,务必熟练使用这个框架,否则就是跟自己过不去。 use -> running source code -> reading & learning the source code. 1、初识

json_it的博客 2万+

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

OkHttp源码详解

OkHttp源码解析 前言 OkHttp是目前最流行的网络请求框架,现在大部分的应用都在用它,所以OkHttp源码是一个不错的学习资源。并且里面涉及的设计模式有: 责任链 策略 单例 监听者 享元 工厂 建造者 门面 今天我们就根据源码来一探究竟!(PS:本篇解析的源码是基于OkHttp 3.12.0 版本!)

jack6wolf的博客 641

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

OkHttp3源码详解

前言:为什么有些人宁愿吃生活的苦也不愿吃学习的苦,大概是因为懒惰吧,学习的苦是需要自己主动去吃的,而生活的苦,你躺着不动它就会来找你了。 一、概述 OKHttp是一个非常优秀的网络请求框架,已经被谷歌加入到Android源码中,目前比较流行的Retrofit底层也是使用OKHttp的,OKHttp的使用是要掌握的,有不懂得可以参考我的博客OKHttp3的使用和详解 在早期版本中,OKHt...

https://juejin.cn/user/1654096907477549/posts 2334

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Android OkHttp使用和源码详解

/创建OkHttpClient对象//创建Request.url(url).build();//创建Call对象client.newCall(request)//通过execute()方法获得请求响应的Response对象@Override@Override//处理UI需要切换到UI线程处理});除了直接 new OkHttpClient 之外,还可以使用内部工厂类 Builder 来设置 OkHttpClient。

2401_83817171的博客 768

OKHTTP源码解析

一、OKHTTP 使用 1、GET 请求 OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url(url) .build(); Response response = client.newCall(request).execute(); ...

k_every的博客 811

OKHttp源码解析

Android4.4之后系统的HttpUrlConnection的实现是基于okhttp实现的。 Android各版本引用的对应okhttp版本: Android 4.4.4_r1:     OKHttp 1.1.2 Android 5.0.1_r1:     OKHttp 2.0.0 Android 6.0.1_r1:     OKHttp 2.4.0 An...

JackWangDev的博客 249

OkHttp:源码详解|通过源码阐述OkHttp的工作流程和设计思想

源码基于okhttp3 java版本:3.14.x 在《OkHttp:基本使用详解》和 《OkHttp:进阶详解》中描述了OkHttp的基本使用方法。自此我们了解了OkHttp的基础使用和了解了使用中设计到的相关概念。 接下来我们通过《OkHttp:源码详解》一系列文章从源码的角度来阐述OkHttp的工作流程和设计思想。 本系列文章分为六部分 OkHttp:源码详解之核心流程(一) OkHttp:源码详解之重试重定向拦截器(二) OkHttp:源码详解之桥拦截器(三) OkHttp:源码详解之缓存拦截器.

尘埃zza的博客 289

okhttp视频教程,应用详解与源码解析

这期专栏的题目为okhttp的应用详解与源码解析,我们知道okhttp是一个网络通信框架,之所以选择网络通信这个主题,这是因为我们手机上的每个app都离不开这个功能,网通通信已经成为每个app必备的一个最基础的功能,它就像我们日常生活中的水和电一样,如果我们日常生活中没有水和没有电我们生活就没有办法正常进行了,同样道理如果我们的app应用中没有了网络通讯的功能,那么这个app就没有灵魂。 csdn视频播放地址:https://edu.youkuaiyun.com/course/detail/29076 本课程准备从以下

门心叼龙的专栏 2985

OkHttp库源码解析

OkHttp库使用及源码解析 OkHttp库使用及源码解析 特点 使用 一个简单的GET请求的步骤(阻塞方式): 更好的用法(异步) 提交数据的POST请求 源码解析 OkHttp的线程池 对于同步Call和异步Call的处理 拦截器 HttpStream和StreamAllocation BridgeInterceptor CacheInterceptor ConnectInter...

mingC0758的博客 659

OkHttp的源码解读1

本文将详细解读 OkHttp 的源码,包括其主要组件、请求流程和连接管理等方面的内容。

jiet_h的博客 1130

OkHttp源码解析

OkHttp源码解析 资料 OkHttp源码深度解析 OkHttp源码走心解析(很细 很长) 整体结果 拦截器(责任链模式) RetryAndFollowUpInterceptor – 失败和重定向拦截器 BridgeInterceptor – 封装request和response拦截器 CacheInterceptor – 缓存相关的过滤器,负责读取缓存直接返回、更新缓存 ConnectInterceptor – 连接服务,负责和服务器建立连接 这才是真正的请求网络 CallServerInterc

AdrianAndroid的专栏 2775

OkHttp3源码详解(四)缓存策略

OkHttp的缓存工作都是在CacheInterceptor中完成的,Cache部分有如下几个关键类:Cache:Cache管理器,其内部包含一个DiskLruCache将cache写入文件系统:To measure cache effectiveness, this class tracks three statistics:requests issued since this cache was created.requests that required network use.whose respo

2401_84895943的博客 706
上一篇: OkHttp源码详解之Okio源码详解
下一篇: Android系统服务启动顺序
字节小站
博客等级 码龄10年 53粉丝 55原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值