Android之OkHttp框架的分析

Okhttp是Android开发常用的一个网络请求框架,下面将按照自己的理解将okhttp分为三条主线进行分析。

文章目录

    • 使用方式
    • OkHttp第一条主线:请求发送到哪里去了?
    • OkHttp第二条主线:请求是如何被消费的?
    • OkHttp第三条主线:请求是如何被维护的?
    • 常见问题

使用方式

   OkHttpClient okHttpClient= new OkHttpClient.Builder().build();Request request=new Request.Builder().url("").build();Call call=okHttpClient.newCall(request);call.enqueue(new Callback() {@Overridepublic void onFailure(@NotNull Call call, @NotNull IOException e) {}@Overridepublic void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {}});

OkHttp第一条主线:请求发送到哪里去了?

首先先看call.enqueue方法,在call接口中

/*** Schedules the request to be executed at some point in the future.** <p>The {@link OkHttpClient#dispatcher dispatcher} defines when the request will run: usually* immediately unless there are several other requests currently being executed.** <p>This client will later call back {@code responseCallback} with either an HTTP response or a* failure exception.** @throws IllegalStateException when the call has already been executed.*/void enqueue(Callback responseCallback);

再看一下call接口的实现类

  @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))方法

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

可以发现在dispatcher类中有两个队列,一个是runningSyncCalls,一个是readyAyncCalls,也就是运行中的队列和等待中的队列。
并且有判断条件,当运行中队列请求数小于64并且访问同一目标机器的数量小于5,将请求放入运行中的队列,不然放入等待队列。
但是你会发现一个问题,假设我们有84个请求,那么我们会将64条请求放入运行中队列,剩下的86-64条放到等待中的队列,那么等待中的队列要等到什么时候进行处理呢?如果两个队列中都有数据再来20条数据又要怎么办?这个时候我们再看第二条主线。

OkHttp第二条主线:请求是如何被消费的?

点进行看executorService().execute(call)

public interface Executor {/*** Executes the given command at some time in the future.  The command* may execute in a new thread, in a pooled thread, or in the calling* thread, at the discretion of the {@code Executor} implementation.** @param command the runnable task* @throws RejectedExecutionException if this task cannot be* accepted for execution* @throws NullPointerException if command is null*/void execute(Runnable command);
}

可以发现重点在与我们传入的call,回到

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

再观察AsyncCall

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() {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 {eventListener.callFailed(RealCall.this, e);responseCallback.onFailure(RealCall.this, e);}} finally {client.dispatcher().finished(this);}}}

再点进去看一下继承类NamedRunnable

public abstract class NamedRunnable implements Runnable {protected final String name;public NamedRunnable(String format, Object... args) {this.name = Util.format(format, args);}@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();
}

发现run()方法中会执行execute()方法,且这里的execute方法是抽象的
那么也就表示我们在执行 executorService().execute(call);的时候其实会调用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 {eventListener.callFailed(RealCall.this, e);responseCallback.onFailure(RealCall.this, e);}} finally {client.dispatcher().finished(this);}}

这里的代码也就是代表是请求怎么访问服务器的。点进行看一下getResponseWithInterceptorChain,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);}

这一块运用到了责任链模式,假设客户端的请求到达服务端,中间有3个节点要求,需要满足个节点的要求才能到达服务器,如果你第1个节点需要验证的东西你通不过,那么就到达不了第2个节点,在第1个节点就被拦截了。使用这种设计模式的话,可以节省很多无用功,做到优化。
在这里插入图片描述
在这里插入图片描述

还有可以添加自己定义的拦截器,增加框架的拓展性。

OkHttp第三条主线:请求是如何被维护的?

    @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 {eventListener.callFailed(RealCall.this, e);responseCallback.onFailure(RealCall.this, e);}} finally {client.dispatcher().finished(this);}}

在execute()方法中点进去 client.dispatcher().finished(this);

  /** Used by {@code AsyncCall#run} to signal completion. */void finished(AsyncCall call) {finished(runningAsyncCalls, call, true);}

再进来

  private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {int runningCallsCount;Runnable idleCallback;synchronized (this) {if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");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.for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {AsyncCall call = i.next();if (runningCallsForHost(call) < maxRequestsPerHost) {i.remove();runningAsyncCalls.add(call);executorService().execute(call);}if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.}}

如果运行中的队列个数已经大于等于maxRequests64,则直接返回,如果等待中的队列为空,那么也直接返回。不然的话就去循环等待中的队列,把等待中的队列移除并将其放入运行中的队列,再和上次请求消费完一样,放入线程池进行处理。
说明了当某个请求被消费掉之后,会回来检查等待队列中是否有数据,如果有数据,则将数据从等待中移除,然后放入运行中,再直接交给线程池进行处理

常见问题

下面是几个常见的问题,如果有错误欢迎指正!

  1. 为什么运行中的队列最大是64
    因为okhttp在写源码的时候,大量参考了浏览器的源码 默认都是64,避免手机和服务器建立过多的线程造成内存溢出 ;而且这个数是可以改的
  2. 访问统一目标机器数量为什么是5
    这可能是出于对服务器压力的考虑。如果同一个手机对同一个服务器的请求个数太多,可能会影响服务器的性能和稳定性。这个数字也是OkHttp的开发者根据经验和实践设定的,没有一个确定的理由或标准。
  3. 为什么设计两个队列,能不能用一个队列
    等待队列(readyAsyncCalls):用于存放还未执行的异步请求,等待调度器分配线程执行
    运行队列(runningAsyncCalls):用于存放正在执行的异步请求,当运行中队列请求数小于64并且访问同一目标机器的数量小于5才能将请求放入运行中队列,限制最大并发数
    Dispatcher类负责调度和分发这两个队列中的请求,以及复用和管理线程池
    okhttp设计两个队列的原因是为了完成调度和复用,以及控制执行和分发,这样可以保证请求的顺序和效率,以及避免资源的浪费和冲突。一个队列可能无法满足这些需求。
  4. 队列为什么用Deque?能不能用arraylist?hashmap?普通数组行不行?
    Deque:双端队列,可以在头部和尾部进行数据的访问和增删。
    ArrayList:基于数组实现的列表,元素有序且可以重复,支持随机访问。
    HashMap:基于数组和键值对实现的映射,元素无序且键不能重复,支持根据键快速查找值。
    数组:一种基本的数据结构,元素有序且可以重复,支持根据下标快速访问。
    队列为什么用Deque?因为Deque可以实现先进先出或后进先出的操作,适合用于存储请求等待执行。
    如果需要存储有序的元素,并且不需要根据键查找值,可以用ArrayList或数组。如果需要存储无序的键值对,并且需要根据键查找值,可以用HashMap。但是这些数据结构可能不如Deque方便和高效地进行头尾的操作。
  5. 为什么线程池中,默认的队列使用SynchronousQueue?
    SynchronousQueue是一个没有容量的阻塞队列,它可以保证每个提交的任务都会被执行,而不会被缓存或丢弃。 可以支持公平性策略,让等待时间最长的生产者或消费者优先执行。可以避免线程池中的线程过多或过少,提高资源利用率和响应速度
  6. 使用了什么设计模式?
    OkHttp中最直接的责任链模式的使用就是Interceptor的使用。书写简单漂亮,使用也非常方便,只需要OkHttpClient.Builder调用addInterceptor()方法,将实现了Interceptor接口的类添加进去即可,扩展性和可定制化都非常方便。
OkHttpClient httpClient = new OkHttpClient.Builder().addInterceptor(new HeaderInterceptor()).addInterceptor(new LogInterceptor()).addInterceptor(new HttpLoggingInterceptor(logger).......readTimeout(30, TimeUnit.SECONDS).cache(cache).build();

OkHttp中最直接的建造者模式的使用就是XXBuilder的使用。在OkHttp中的OkHttpClient、Request、Response、HttpUrl、Headers、MultipartBody等大量使用了类似的建造者模式。

public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
......public static final class Builder {Dispatcher dispatcher;@Nullable Proxy proxy;int callTimeout;int connectTimeout;
......public OkHttpClient build() {return new OkHttpClient(this);}}
}
public class Headers {
......public static final class Builder {final List<String> namesAndValues = new ArrayList<>(20);
......public Headers build() {return new Headers(this);}}
}
public class Request {
......public static class Builder {@Nullable HttpUrl url;String method;Headers.Builder headers;@Nullable RequestBody body;
......public Request build() {return new Request(this);}}
}

将对象的创建与表示相分离,Builder负责组装各项配置参数,并且生成对象,目标对象则对外提供接口,符合类的单一原则
工厂模式相对于建创者模式的生成对象的过程更复杂,侧重于对象的生成过程,比如

public interface Call extends Cloneable {Request request();Response execute() throws IOException; void enqueue(Callback responseCallback);void cancel();  boolean isExecuted();  boolean isCanceled(); Call clone();//创建Call实现对象的工厂interface Factory {//创建新的Call,里面包含了Request对象。Call newCall(Request request);}
}
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {@Override public Call newCall(Request request) {return RealCall.newRealCall(this, request, false /* for web socket */);}
}final class RealCall implements Call {......
}

在Call接口中,有一个内部工厂Factory接口。这样只要像下面这样就可以了:
实现Call接口,现实相应的功能,RealCall;
使用某个类(OkHttpClient)实现Call.Factory接口,在newCall中返回RealCall对象,就可以了。

  1. 为什么这么设计?如果是你,还能想到其他更好的设计模式吗?
    这些设计模式的目的是为了提高OkHttp的可扩展性、可维护性和可读性。如果是我,我可能会考虑使用观察者模式,让用户可以注册一些回调函数,来监听请求和响应的状态变化。这样可以让用户更方便地处理异步请求和异常情况。
  2. 每个拦截器设计的意义是什么?
    RetryAndFollowUpInterceptor:负责请求失败的时候实现重试重定向功能。
    BridgeInterceptor:负责将用户构造的请求转换为向服务器发送的请求,添加一些必要的头部信息。
    CacheInterceptor:负责处理缓存逻辑,根据缓存策略和响应头判断是否使用缓存或更新缓存。
    ConnectInterceptor:负责建立连接,选择路由,协商协议等。
    CallServerInterceptor:负责向服务器发送请求和接收响应,处理GZIP压缩等。
  3. 为什么使用Socket连接池?好处是什么
    okhttp使用Socket连接池的原因是提高性能和效率。连接池可以复用已有的连接,避免频繁地创建和关闭连接,减少请求延迟和网络开销。如果使用HTTP/2协议,连接池还可以多路复用同一主机的请求,进一步提升并发能力。
  4. 为什么每次请求运行完之后都需要对队列中的请求进行维护?这样设计有什么好处
    如果是以前的volley框架的话,他会启动一个线程会有一句代码while(true),代表这个线程一直是死循环。假设客户端一直源源不断向服务器发送请求,那么没问题,但是要是不发送请求,线程一直跑的话,这样的话就会造成浪费。
    但是okhttp不一样,假设发送一条请求,会将请求放到线程池,线程池执行去请求服务器,请求完成后会回去检查,如果没有任何请求发送过来,就不可能将请求加入到运行中的队列,也就不可能放到线程池,也不是发送到服务器,使得框架内部是停止状态。不会向while(true)那样源源不断发送请求,使得造成浪费,性能下降。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/5363.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《PyTorch深度学习实践》第十讲 卷积神经网络(基础篇)

b站刘二大人《PyTorch深度学习实践》课程第十讲卷积神经网络&#xff08;基础篇&#xff09;笔记与代码&#xff1a;https://www.bilibili.com/video/BV1Y7411d7Ys?p10&vd_sourceb17f113d28933824d753a0915d5e3a90 上一讲中MNIST数据集的例子采用的是全连接神经网络&#…

ModaHub魔搭社区:向量数据库Milvus产品问题(三)

目录 Milvus 的数据落盘逻辑是怎样的&#xff1f; Mishards 推荐的配置是什么&#xff1f; Mishards 支持 RESTful API 吗&#xff1f; 什么是归一化&#xff1f;Milvus 中为什么有时候需要归一化&#xff1f; 为什么欧氏距离和内积在计算向量相似度时的结果不一致&#x…

算法与数据结构(六)

一、图 一、临接表 表示方法如下&#xff1a; 带权值的无向图的构建&#xff1a; #define MaxInt 32767 // 极大值 #define MVNum 100 // 最大定点数 typedef int ArcType; // 边的权值类型 typedef char VerTexType; // 顶点数据类型//弧(边)的结点结构 st…

当金融风控遇上人工智能,众安金融的实时特征平台实践

导读&#xff1a;随着企业数字化转型升级&#xff0c;线上业务呈现多场景、多渠道、多元化的特征。数据要素价值的挖掘可谓分秒必争&#xff0c;业务也对数据的时效性和灵活性提出了更高的要求。在庞大分散、高并发的数据来源背景下&#xff0c;数据的实时处理能力成为企业提升…

Maven中依赖使用范围

IDEA中help中show Log in Explorer可以查看idea日志 依赖使用范围 构建包含的流程&#xff1a;编译 &#xff0c;测试 &#xff0c;运行 &#xff0c;打包 &#xff0c;安装 &#xff0c;部署 comile test package install deploy 使用标签 1&#xff1a;compile 缺省值 伴随者…

VRP基础操作

目录 一、华为VRP 1.1、VRP介绍 1.2、设备管理接口 1.3、Console口登录 1.4、参数配置 二、华为VRP命令行基础 2.1、真机设备初始化启动 2.2、命令行视图 2.3、命令行功能 2.4、命令行在线帮助 2.5、配置系统时钟 2.6、配置标题消息 2.7、命令等级 2.8、用户界面…

突破性5G NTN技术,美格智能携手高通发布卫星物联网连接方案

通信技术的快速发展&#xff0c;使得万物互联成为现实&#xff0c;物联网深刻影响我们的生活方式。目前&#xff0c;全球物联网连接主要由WiFi、蓝牙和蜂窝网络等几类技术支撑。数据显示&#xff0c;蜂窝基站的陆地覆盖率约为20%&#xff0c;而海洋覆盖率则不到5%。 这意味着陆…

Docker数据卷与容器的挂载

什么是Docker数据卷: 数据卷&#xff08;Volumes&#xff09;是宿主机中的一个目录或文件&#xff0c;当容器目录和数据卷目录绑定后&#xff0c;对方的修改会立即同步。一个数据卷可以被多个容器同时挂载&#xff0c;一个容器也可以被挂载多个数据卷。简单来说数据卷本质其实是…

FPGA的软核、硬核、固核

“核” 现在的FPGA设计&#xff0c;规模巨大而且功能复杂&#xff0c;因此设计的每一个部分都从头开始是不切实际的。一种解决的办法是&#xff1a;对于较为通用的部分可以重用现有的功能模块&#xff0c;而把主要的时间和资源用在设计中的那些全新的、独特的部分。这就像是你在…

20kV高精度可调高压稳压测试电源的学习与使用

一&#xff1a;应用范围 A: 二极管反向耐压测试 B: 二极管反向漏电流测试 C: 高压电容耐压测试 D: 玻璃釉电阻非线性性能测试 E:氙灯击穿电压测试 F: 材料耐压测试 二、特点 高精度恒流恒压高压输出源 它拥有0~20kV的电压输出能力, 0.005%的电压分辨率精度, 0.1uA的电 …

mysql——存储过程

目录 存储过程存储过程的优点创建存储过程调用存储过程查看存储过程查看存储过程的详细信息查看存储过程的属性 存储过程的参数删除存储过程存储过程控制语句 存储过程 存储过程是一组为了完成特定功能的SQL语句集合存储过程在使用过程中是将常用或者复杂的工作预先使用SQL语句…

Android通过连接USB读写SD卡(libaums方案)

Android通过连接USB读写SD卡 最近有一个需求是要求通过Usb扩展读取到SD卡的内容。可以从Usb存储设备拷贝文件到内置卡&#xff0c;也可以从内置卡文件拷贝到Usb存储。 1. 相关的引入包 implementation androidx.core:core-ktx:1.7.0implementation androidx.appcompat:appcompa…