OKHttp实现原理分享

news2024/11/13 0:15:19

前言介绍

        大约在2年半之前,就想写一篇关于OKHttp原理的文章,一来深入了解一下其原理,二来希望能在了解原理之后进行更好的使用。但是因为种种原因,一直无限往后推迟,最近因为我们情景智能半个月一次的分享轮到我了,所以有了压力之后,抽出了一整天的时间完成了这篇文章。之所以每半个月搞一次技术分享,其目标之一也是为了督促分享人能够在压力之后,达成设定的目标,所以技术分享最大的收益其实恰恰就是分享者自身。

        另外这里也顺便给大家带来一些我对AI工具使用的心得。完成这次分享的时候,充分借助了AI工具。完成简单工作时,AI工具确实能够提供很大的帮助,甚至直接帮助我完成,但是在完成一些具有一定深度的任务时,AI工具给的结果不是很理想往往给的不准确或者就是错误,这也正常,因为具有深度的问题被搜索的频次较少,样本量较低,则准确性就不高。

        言归正传,回到OKHttp,OKHttp是一个开源的HTTP客户端库,由Square公司开发,广泛应用于Java和Kotlin应用程序中进行网络请求和处理响应。目前OKHttp已成为安卓端最为主流通信的框架,之前存在的一些自带的框架如HttpClient已经逐渐从源码中都已废弃,那么为什么都在使用OKHttp,OKHttp有哪些优势,我们一起来看一下。

一.基本用法

1.1 构建OKHttpClient

通过三步流程进行进行创建,使用创建者模式。

首先创建Builder;

然后对Builder配置一些参数;

最后通过build生成OKHttpClient对象。

Java
val builder = OkHttpClient.Builder()
builder.cache(Cache(File(context?.filesDir?.absolutePath + File.separator + "ok"), 100))
client = builder.build()

1.2 构建Request

通过三步流程进行创建,使用创建者模式。

首先创建Builder对象;

然后对Builder配置一些参数;

最后通过build生成Request对象。

Java
val builder = Request.Builder()
val cacheBuilder = CacheControl.Builder()
cacheBuilder.noStore()
builder.cacheControl(cacheBuilder.build())
val request = builder.url("https://www.baidu.com").get().build()

1.3 构建Call

通过client和request构造生成Call。

Java
val newCall = client.newCall(request)

1.4 同步发送请求

直接使用call.execute()方法发送请求,execute方法阻塞线程。

Java
val response = newCall.execute()

1.5 异步发送请求

直接使用call.enqueue()方法发送请求,enqueue方法不阻塞线程。

Java
newCall.enqueue(object : Callback {
    override fun onFailure(call: okhttp3.Call, e: IOException) {
    }

    override fun onResponse(call: okhttp3.Call, response: okhttp3.Response) {
    }
})

1.6 解析Reponse

Java
val content = IOHelper.readStrByCode(response.body()?.byteStream(), "utf-8")

1.7 支持状态监听

Java
//OKHttpClient.Builder
public Builder eventListener(EventListener eventListener) {
  if (eventListener == null) throw new NullPointerException("eventListener == null");
  this.eventListenerFactory = EventListener.factory(eventListener);
  return this;
}
//EventListener
public abstract class EventListener {
    public void callStart(Call call) {}
    public void dnsStart(Call call, String domainName) {}
    ...
}

 

二.请求流程

正常网络请求流程

OKHttp请求流程

1.构建RealCall;

Java
val newCall = client.newCall(request)

2.构建责任链

Java
Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    //2.
构建5+2层拦截器;
    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());
   
    //3.开始一层层处理拦截器流程;
    Response response =  chain.proceed(originalRequest);
    //4.返回结果。
    return response;
}

.5+2层拦截器

3.1 责任链

调用一层层的往下层递归嵌套调用,结果一层层的向上层返回。

3.2 RetryAndFollowUpInterceptor

RetryAndFollowUpInterceptor是第一个拦截器,主要作用就是两个:重试和重定向。

需要说明的是,这里的重试并不是通常意义上的只要失败了无论怎么样都会再试一次的那个重试,而是满足特定条件下因为某种小的错误才会尝试的重试。

重试逻辑中,如果下层返回异常,如3.2.1代码和3.2.2代码,则进入到Exception的逻辑。

该逻辑中,会通过recover方法进行判断是否需要重试,如果需要则继续循环,否则如3.2.3代码返回reponse结束流程。

Java
@Override
public Response intercept(Chain chain) throws IOException {
   
    while (true) {
        try {
         
response = realChain.proceed(request, streamAllocation, null, null);
          releaseConnection = false;
        } catch( RouteException e ) {
            //3.2.1
代码
            if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
              throw e.getLastConnectException();
            }
            releaseConnection = false;
            continue;
        } catch( IOException e ) {
            //3.2.2代码
            boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
            if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
            releaseConnection = false;
            continue;
        } finally {
            ...
        }
        Request followUp = followUpRequest(response, streamAllocation.route());
        if (followUp == null) {
          if (!forWebSocket) {
            streamAllocation.release();
          }
          //3.2.3代码
          return response;
        }
        ...
        if (++followUpCount > MAX_FOLLOW_UPS) {
            streamAllocation.release();
            throw new ProtocolException("Too many follow-up requests: " + followUpCount);
        }
        ...
    }
}

private boolean recover(IOException e, StreamAllocation streamAllocation,
    boolean requestSendStarted, Request userRequest) {
  streamAllocation.streamFailed(e);

  // The application layer has forbidden retries.
  if (!client.retryOnConnectionFailure()) return false;

  // We can't send the request body again.
  if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;

  // This exception is fatal.
  if (!isRecoverable(e, requestSendStarted)) return false;

  // No more routes to attempt.
  if (!streamAllocation.hasMoreRoutes()) return false;

  // For failure recovery, use the same route selector with a new connection.
  return true;
}

重试逻辑中

判断是否需要重试的逻辑在recover方法中,主要分为4种。

应用层禁止重试,不能再次请求,发生致命的异常,没有路由可以尝试。

重定向逻辑中

会尝试最多进行20次的重定向,超过20次则认为失败。

3.3 BridgeInterceptor

桥接拦截器的的主要作用有三个:

  • 首先把一个用户请求转换为网络请求;
  • 其次调用网络请求;
  • 最后把网络响应转换为用户的响应。

则顾名思义,header中的各种基础信息,如gzip、keep-alive、text/html等等,进行内容的组装。

接下来看代码

Java
public Response intercept(Chain chain) throws IOException {
    RequestBody body = userRequest.body();
    //
拼装请求的基础信息
    if (body != null) {
        MediaType contentType = body.contentType();
        if (contentType != null) {
          requestBuilder.header("Content-Type", contentType.toString());
        }
        ...
    }
    ...
    //拼装Cookie
    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
      requestBuilder.header("Cookie", cookieHeader(cookies));
    }
    ...
    //责任链传递
   
Response networkResponse = chain.proceed(requestBuilder.build());
    //解析响应的基础信息
    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
    //gzip解压
    if (transparentGzip && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding")) && HttpHeaders.hasBody(networkResponse)) {
        GzipSource responseBody = new GzipSource(networkResponse.body().source());
        Headers strippedHeaders = networkResponse.headers().newBuilder().removeAll("Content-Encoding").removeAll("Content-Length").build();
        responseBuilder.headers(strippedHeaders);
        String contentType = networkResponse.header("Content-Type");
        responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
    }
    return responseBuilder.build();
}

3.4 CacheInterceptor

CacheInterceptor的主要作用,就是对数据进行缓存以及确认是否使用缓存。

执行流程:

主要分为3块:

  • 根据缓存策略确定是否使用;
  • 如果不使用缓存,则发送请求;
  • 更新缓存;

Java
@Override
public Response intercept(Chain chain) throws IOException {
    //
找到缓存
    Response cacheCandidate = cache != null ? cache.get(chain.request()) : null;
    long now = System.currentTimeMillis();
   
    //构建缓存策略,通过缓存策略决定是否使用此次的缓存Reponse
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;
   
    //如果使用缓存,则会把networkRequest设置为空,则返回CacheResponse。
    if (networkRequest == null) {
        return cacheResponse.newBuilder().cacheResponse(stripBody(cacheResponse)).build();
    }
   
networkResponse = chain.proceed(networkRequest);
   
    //
更新缓存Response
    if (cacheResponse != null) {
        ...
        cache.update(cacheResponse, response);
        return response;
    }
    //添加新的缓存,或者根据请求的配置删除缓存。
    Response response = networkResponse.newBuilder().cacheResponse(stripBody(cacheResponse)).networkResponse(stripBody(networkResponse)).build();
    if (cache != null) {
        if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
          // Offer this request to the cache.
          CacheRequest cacheRequest = cache.put(response);
          return cacheWritingResponse(cacheRequest, response);
        }
        if (HttpMethod.invalidatesCache(networkRequest.method())) {
            cache.remove(networkRequest);
        }
     }
     return response;
}

缓存和缓存策略:

简单看一下缓存和缓存策略。

使用的是LruCache,key为url。

Java
public final class Cache implements Closeable, Flushable {
    final DiskLruCache cache;
   
    Cache(File directory, long maxSize, FileSystem fileSystem) {
        ...
        this.cache = DiskLruCache.create(fileSystem, directory, 201105, 2, maxSize);
    }
   
    Response get(Request request) {
        String key = key(request.url());
        DiskLruCache.Snapshot snapshot;
       
        snapshot = this.cache.get(key);
        Entry entry = new Entry(snapshot.getSource(0));
       
        Response response = entry.response(snapshot);
        return response;
    }
}

3.5 ConnectInterceptor

ConnectInterceptor的主要作用就是创建连接。

请求流程:

Java
@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解析,获取到IP地址和端口,创建连接对象HttpCodec。
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    //这里的connection就是上面创建好的
    RealConnection connection = streamAllocation.connection();
    //交给下一个拦截器处理
    return realChain.proceed(request, streamAllocation, httpCodec, connection);
}

  • 创建协调类StreamAllocation,它负责协调Collections、Stream、Calls的关系。
  • 创建HttpCodec对象,这个对象是经过DNS解析后,直接存储IP地址和端口的类,分为HTTP1.1和HTTP2.0两个版本。下面是我断点获取到的访问baidu的信息

Java
Connection{www.baidu.com:443, proxy=DIRECT hostAddress=www.baidu.com/180.101.50.242:443 cipherSuite=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 protocol=http/1.1}

  • 尝试进行连接。
  • 交给下一个拦截器。

我们主要看一下newStream中的流程。这里的核心就是获取一个有效的连接,核心代码在StreamAllocation的findConnection方法中。

主要有三部分,首先尝试从连接池中查找,如果找不到则通过路由生成一个新的连接,最后更新连接到连接池。

Java
private RealConnection findConnection() throws IOException {
    if (result == null) {
        //
获取连接之前,先查询连接缓存池
        Internal.instance.get(connectionPool, address, this, null);
        if (connection != null) {
            foundPooledConnection = true;
            result = connection;
        } else {
            selectedRoute = route;
        }
    }
    ...
    //创建新的连接
    if (!foundPooledConnection) {
        if (selectedRoute == null) {
          selectedRoute = routeSelection.next();
        }
    }
    //上面找到了连接,则添加到缓存池
    Internal.instance.put(connectionPool, result);
}

缓存池获取连接

Java
//获取连接
RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
    for (RealConnection connection : connections) {
        if (connection.isEligible(address, route)) {
          //确定使用直接把connection指向streamAllocation,其实这里返回值并没有用到。
          streamAllocation.acquire(connection, true);
          return connection;
        }
    }
    return null;
}

//合法性判断
public boolean isEligible(Address address, @Nullable Route route) {
    ...
    //基础类,判断是否正在被使用
    if (allocations.size() >= allocationLimit || noNewStreams) return false;
    //判断地址是否匹配
    if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;
    //判断URL是否匹配,如果完全匹配则直接返回。
    if (address.url().host().equals(this.route().address().url().host())) {
      return true; // This connection is a perfect match.
    }
   
    // 必须支持HTTP/2
    if (http2Connection == null) return false;
   
    // 路由必须共享一个IP地址,不支持代理连接
    if (route == null) return false;
    if (route.proxy().type() != Proxy.Type.DIRECT) return false;
    if (this.route.proxy().type() != Proxy.Type.DIRECT) return false;
    if (!this.route.socketAddress().equals(route.socketAddress())) return false;
   
    // 连接的证书支持新的域名
    if (route.address().hostnameVerifier() != OkHostnameVerifier.INSTANCE) return false;
   
    ...
   
    return true;
}

 

开始连接

首先通过DNS获取地址

然后通过路由创建连接

Java
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
    ...
    if (!foundPooledConnection) {
        if (selectedRoute == null) {
          selectedRoute = routeSelection.next();
        }
        route = selectedRoute;
        refusedStreamCount = 0;
        //
创建连接
        result = new RealConnection(connectionPool, selectedRoute);
        acquire(result, false);       
    }
    //进行握手
    result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled, call, eventListener);
    routeDatabase().connected(result.route());
}   

添加连接到连接池

Java
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
    ...
    Socket socket = null;
    synchronized (connectionPool) {
      reportedAcquired = true;
   
      // Pool the connection.
      Internal.instance.put(connectionPool, result);
      ...
    }
    ...
    eventListener.connectionAcquired(call, result);
    return result;
}

3.6 CallServerInterceptor

到了这一步,连接已经创建好了。所以CallServerInterceptor的作用就是发送最终的请求到后台。

主要分为几步:

写入header;

写入requestBody;

发送请求流;

读取ResponseHeader;

读取ResponseBody。

Java
@Override public Response intercept(Chain chain) throws IOException {
    //
材料准备齐全,开始干活了。
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    HttpCodec httpCodec = realChain.httpStream();
    StreamAllocation streamAllocation = realChain.streamAllocation();
    RealConnection connection = (RealConnection) realChain.connection();
    Request request = realChain.request();   
    //写入header
    httpCodec.writeRequestHeaders(request);
   
    //写入requestBody
    long contentLength = request.body().contentLength();
    CountingSink requestBodyOut =
        new CountingSink(httpCodec.createRequestBody(request, contentLength));
    BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
    request.body().writeTo(bufferedRequestBody);
    bufferedRequestBody.close();
   
    //IO流发送
    httpCodec.finishRequest();
   
    //解析ReponseHeader
    responseBuilder = httpCodec.readResponseHeaders(false);
   
    //解析ReponseBody
    if (forWebSocket && code == 101) {
   
    } else {
        response = response.newBuilder().body(httpCodec.openResponseBody(response)).build();
    }
    return response;
}

写入header的逻辑。

Java
@Override public void writeRequestHeaders(Request request) throws IOException {
  String requestLine = RequestLine.get(
      request, streamAllocation.connection().route().proxy().type());
  writeRequest(request.headers(), requestLine);
}

public void writeRequest(Headers headers, String requestLine) throws IOException {
  if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
  sink.writeUtf8(requestLine).writeUtf8("\r\n");
  for (int i = 0, size =
headers.size(); i < size; i++) {
    sink.writeUtf8(headers.name(i))
        .writeUtf8(": ")
        .writeUtf8(headers.value(i))
        .writeUtf8("\r\n");
  }
  sink.writeUtf8("\r\n");
  state = STATE_OPEN_REQUEST_BODY;
}

读取header的逻辑。

Java
//Http1Codec
public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {
    //
这里阻塞,等到有响应了才继续往下走。
    StatusLine statusLine = StatusLine.parse(readHeaderLine());
    Response.Builder responseBuilder = new Response.Builder().protocol(statusLine.protocol).code(statusLine.code).message(statusLine.message).headers(readHeaders());
    ...
    return responseBuilder;  
}

3.7 自定义外层拦截器

举2个例子典型的例子:

例子1:内容转换为对象

客户端和服务端进行通信,如果采用的是Protobuff或者其它的数据类型,那么把返回的二进制流,转换为我们想要的对象类型,就可以在外层拦截器执行。

例子2:MOCK数据

我们现在和后台一般是并行开发,并行开发会存在一个问题,客户端前期开发时没有后台接口可用。这时候我们往往会选择mock数据,但是mock的数据是写在逻辑层的,侵入性较高。但是如果我们把mock的逻辑放在外层拦截器中,就会方便很多。首先,逻辑层完全不需要改动;其次拦截器可以进行配置,生产缓存不是使用该拦截器;最后,拦截器还可以设置读取磁盘上的文件甚至本地后台服务,方便内容修改。

还可以有以下作用:

  • 修改请求:可以在请求发送之前修改请求的URL头信息或请求体。
  • 处理响应:可以在响应接收之后修改响应的头信息或响应体。
  • 重试请求:可以在请求失败时实现重试逻辑。
  • 日志记录:可以记录请求和响应的详细信息,用于调试和监控。

3.8 自定义连接拦截器

  • 网络层拦截:networkInterceptors在网络请求和响应的过程中拦截,而普通的Interceptor可以在缓存、重定向等过程中拦截。
  • 访问原始数据:networkInterceptors可以访问网络请求和响应的原始数据,包括未解码的响应体。
  • 顺序执行:networkInterceptors必须按顺序执行,并且必须调用Chain.proceed()方法继续请求或响应的处理。

3.9 小结

通过一系列的拦截器,完成不同的任务,从而实现一个完整的请求。

.线程设计

4.1 请求任务线程池

OKHttp任务调度,使用的线程池的设计。

添加任务:

相关代码如下:

Java
//如果未超过最大请求数,则直接执行,否则加入等待队列
synchronized void enqueue(AsyncCall call) {
  if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
    runningAsyncCalls.add(call);
    executorService().execute(call);
  } else {
    readyAsyncCalls.add(call);
  }
}
//OKHttp中是一个不设置上限数量的线程池,PS:这个支持外部配置
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;
}

//6个参数
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         threadFactory, defaultHandler);
}

 

线程池6个参数:

参数名

参数含义

corePoolSize

核心线程数

maximumPoolSize

最大线程数

keepAliveTime

持续存活时间

unit

时间单位

workQueue

任务队列

任务执行

Java
final class AsyncCall extends NamedRunnable {
    @Override
    protected void execute() {    
        try{
            Response response = getResponseWithInterceptorChain();
            ...
        } catch (Exception e) {
            ...
            responseCallback.onFailure(RealCall.this, e);
        }
       
    }
}

4.2 连接池线程池

连接池线程池用于连接池定期清理一些过期的连接。

创建一个最大线程数量为1,过期时间为60秒的线程池。

Java
private final Executor executor = new ThreadPoolExecutor(
    0 /* corePoolSize */, 1 /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
    new LinkedBlockingQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));

当连接数量大于0时,设置任务定期清理连接。

Java
private Runnable cleanupRunnable = new Runnable() {
  @Override
  public void run() {
    while (true) {
        //cleanup
为根据各种条件计算出来的等待时间,这里不是重点就不详细介绍了。
        long waitNanos = cleanup(System.nanoTime());
        ...
        ConnectionPool.this.wait(waitMillis, (int) waitNanos);
    }
  }
}   

   

4.3 缓存线程池

缓存线程池用于定期清理一些过期的缓存。

创建最大线程数量为1的线程池。

Java
Executor executor = new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), Util.threadFactory("OkHttp DiskLruCache", true));

缓存线程池,是每次使用缓存的时候进行过期检查。而只执行一次,清理完成则任务执行完成。

Java
//DisLruCache
public synchronized Snapshot get(String key) throws IOException {
    if (journalRebuildRequired()) {
      executor.execute(cleanupRunnable);
    }
}

//
private final Runnable cleanupRunnable = new Runnable() {
    public void run() {
        synchronized (DiskLruCache.this) {
            ...
            trimToSize();
        }
    }
}

private void trimToSize() throws IOException {
  while (size > maxSize) {
    Entry toEvict = lruEntries.values().iterator().next();
    removeEntry(toEvict);
  }
}

.Socket连接

socket连接过程

整个请求过程中,Socket完整连接时间分为写入,等待,读取三个部分。

如果使用读写线程不分离:

如果使用读写线程分离:

OKHttpsocket连接

因此,OKHttp的OKIO,其实就是对SocketInputStream的一层封装,最终还是依赖底层的能力。

反思

所以:为什么OKHttp不是用读写分离?

这个问题就留给读者了。

.本篇文章未涉及部分

自定义DNS、OKIO、HTTP2.0多路复用等等。

.随堂小问题

问:OKHttp中涉及到哪些设计模式?

答:构建者、责任链、工厂、观察者。

问:连接池有什么作用?

答:避免重复的握手连接,提高通道复用效率。

问:OKHttp有哪些优势?

答:使用方便、监听->丰富的监听方便排查问题;

自定义责任链->可扩展性强;

责任链->问题排查方便;

连接池复用、支持GZIP压缩、缓存机制等。

问:OKHttp可能存在哪些缺点?

答:频繁创建对象、读写未分离。

问:五层拦截器,第1,2,3,4,5层的作用是什么?

答:RetryAndFollowUpInterceptor:重试和重定向;

BridgeInterceptor:请求和响应时实现对象和文本的转换;

CacheInterceptor:缓存响应,方便复用;

ConnectInterceptor:创建合适的连接并完成握手;

CallServerInterceptor:完成最后的请求发送和响应的解析。

问:统一失败配置,比如不同的请求,无网或若网时失败返回话术不一样,应该怎么做?

答:自定义外层拦截器

问:如果想修改原有的缓存策略,比如后台返回不缓存的,仍要缓存,应该怎么做?

答:自定义连接拦截器中,修改request的url地址即可。

问:再举一些外层拦截器可能使用到的场景?

答:重试、日志记录、加密解密、压缩解压、MOCK工具

问:HTTP1.1和HTTP2.0区别?

答:

多路复用(Multiplexing)

头部压缩(Header Compression)

服务器推送(Server Push)

数据帧(Data Frames)

连接管理(Connection Management)

流量控制(Flow Control)

优先级(Priority)

问:为什么OKHttp使用只包含1个线程的线程池,而不是使用安卓的Handler?

答:个人推测:OKHttp面向的对象并不仅仅只是安卓。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

手势识别&手势控制系统-OpenCV&Python(源码和教程)

项目特点 手部手势识别&#xff1a; 项目利用计算机视觉技术来识别手部的各种手势。这种技术可以应用于多种场景&#xff0c;比如人机交互、游戏控制、无障碍技术等。 自定义手势&#xff1a; 用户可以自定义手势&#xff0c;这意味着可以通过训练新的手势模式来扩展系统的功能…

基于vue框架的城市网约车管理系统v34td(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;用户,司机,订单评价,完成订单,司机接单,打车订单 开题报告内容 基于Vue框架的城市网约车管理系统开题报告 一、研究背景与意义 1.1 研究背景 随着城市化进程的加速和互联网技术的飞速发展&#xff0c;网约车服务作为一种新兴的出行方…

基于java+SpringBoot+Vue的阿博图书馆管理系统设计与实现

开发语言:Java 数据库:MySQL技术:SpringBootMyBatis工具:IDEA/Ecilpse、Navicat、Maven 系统简介 阿博图书馆管理系统是一款基于Java、SpringBoot和Vue.js技术开发的信息化管理系统&#xff0c;旨在为图书馆提供一个高效、便捷的图书管理与借阅服务。系统通过B/S架构&#x…

FinalShell连接Linux服务器并解决反复输入密码问题

FinalShell是一款由国人开发的SSH客户端工具&#xff0c;它支持多平台&#xff0c;包括Windows、Mac OS X和Linux。FinalShell主要用于一体化服务器管理&#xff0c;它不仅是一个SSH客户端&#xff0c;还具备强大的开发和运维功能&#xff0c;能够充分满足开发和运维的需求。 本…

人脸匿名化初步研究:解决人脸隐私安全

1、人脸匿名化定义&#xff1a; 将人脸图像匿名化方法从图像语义修改、图像语义保持、视觉可恢复以及深度学习过程中的人脸隐私保护四个方面进行分类&#xff0c;将人脸视频匿名化方法从聚焦面部区域隐私的视频匿名化方法和面向生物特征隐私的视频匿名化方法两个方面进行分类 …

开源FormCreate低代码表单组件的配置项和事件的详解

在使用开源FormCreate低代码表单时&#xff0c;您可以通过各种 props 来定制表单的行为和外观。这些参数允许您控制表单的生成规则、配置选项、双向数据绑定等&#xff0c;为复杂的表单场景提供了强大的支持。 源码地址: Github | Gitee FormCreate组件Props 以下是常用的 pr…

【项目开发 | Python】基于“羊了个羊“风格的消除类小游戏

原创文章,不得转载。 目标:使用 Python 开发"羊了个羊"风格的消除类小游戏,合理运用 AIGC 工具提高开发效率;使用文生图工具实现图片设计等工作。 文章目录 项目背景项目介绍+项目展示游戏逻辑概述主界面游戏界面获胜界面失败界面附加功能项目细节项目测试测试样…

zabbix之钉钉告警

钉钉告警设置 我们可以将同一个运維组的人员加入到同一个钉钉工作群中&#xff0c;当有异常出现后&#xff0c;Zabbix 将告警信息发送到钉钉的群里面&#xff0c;此时&#xff0c;群内所有的运维人员都能在第一时间看到这则告警详细。 Zabbix 监控系统默认没有开箱即用…

JavaScript进阶day4

目录 1.深浅拷贝 1.1 浅拷贝 1.1.1 浅拷贝的认识 1.1.2 浅拷贝的小结 1.2 深拷贝 1.2.1 递归实现深拷贝 1.2.2 js类库lodash/cloneDeep实现深拷贝 1.2.3 JSON.stringify()实现深拷贝 2.异常处理 2.1 throw 抛异常 2.2 try /catch 捕获异常 2.3 debugger 3.处理thi…

嵌入式边缘计算:融合创新与未来展望

本文深入探讨了嵌入式边缘计算。首先解析了其概念&#xff0c;指出它是将计算和数据存储能力嵌入边缘设备以实现本地数据处理。阐述了其低延迟、高可靠性、节省带宽、隐私保护和高效节能等技术特点。接着介绍了关键技术&#xff0c;包括嵌入式系统设计、边缘计算架构、通信技术…

关于QT服务端客户端的聊天

服务段头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include<QTcpServer> #include<QMessageBox> #include<QDebug> #include<QList> #include<QTcpSocket>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_N…

设计一个算法,找出由str1和str2所指向两个链表共同后缀的起始位置

假定采用带头结点的单链表保存单词&#xff0c;当两个单词有相同的后缀时&#xff0c;则可共享相同的后缀存储空间&#xff0c;例如&#xff0c;’loading’和’being’的存储映像如下图所示。 设str1和str2分别指向两个单词所在单链表的头结点&#xff0c;链表结点结构为 data…

HashTable哈希表

概念 散列表(Hash Table)&#xff0c;又称哈希表。是一种数据结构&#xff0c;特点是:数据元素的关键字与其存储地址直接相关 在顺序结构以及树型结构中&#xff0c;数据元素的关键字与其存储位置没有对应的关系&#xff0c;因此在查找一个元素时&#xff0c;必须要经过关键码…

KV260 进阶开发(PYNQ驱动开发+Pixel Pack)

目录 1. 简介 2. PixelPacker HLS 实现 2.1 PixelPacker HLS 源码 2.2 PixelPacker 功能简介 2.3 头文件介绍 2.4 启动间隔 II 2.5 Case V24 片段解释 3. PixelPacker Py 驱动 3.1 PixelPacker Py 源码 3.2 PixelPacker 类详解 3.3 property 装饰器 3.4 操作寄存器…

一、(JS)JS中鼠标事件-mouseenter、mouseleave和mouseover、mouseout区别

一、单个元素下mouseenter、mouseleave和mouseover、mouseout没有区别 我们先来一个demo&#xff0c;设置一个div <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"…

INIC6081量产工具下载,initio6081开卡软件分享

国内固态硬盘常用&#xff0c;且有量产工具流传出来的主控厂商包括慧荣、群联、点序、英韧、得一微、瑞昱、联芸、迈威、国科、华澜微等等。 每个主控需要用各自对应的量产工具&#xff0c;不同的量产工具支持的闪存颗粒也有差异&#xff0c;因此要根据固态硬盘实际的主控型号…

基于SSM的酒店客房管理系统+LW示例参考

系列文章目录 1.基于SSM的洗衣房管理系统原生微信小程序LW参考示例 2.基于SpringBoot的宠物摄影网站管理系统LW参考示例 3.基于SpringBootVue的企业人事管理系统LW参考示例 4.基于SSM的高校实验室管理系统LW参考示例 5.基于SpringBoot的二手数码回收系统原生微信小程序LW参考示…

Visual Studio 设置文件默认编码格式、行尾符等

文章目录 1.命令方式2.EditorConfig配置 1.命令方式 2.EditorConfig配置 微软官方文档 使用EditorConfig方式配置&#xff0c;无需Visual Studio软件自带对EditorConfig的支持&#xff0c;无需插件 将下面.editorconfig文件放在项目根目录下 root true # 所在目录是根目录…

基于SSM的二手交易管理系统的设计与实现 (含源码+sql+视频导入教程+文档)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 基于SSM的二手交易管理系统1拥有两种角色 管理员&#xff1a;商品管理、订单管理、充值管理、用户管理等用户&#xff1a;发布商品、查看闲置、充值账户、查看所有订单、发布求购信息、修…

今年白银市场的供需关系矛盾

自从2020年以来&#xff0c;白银手持连续4年都出现了供需缺口&#xff0c;预计今年的供需缺口将进一步扩大。2015年以来&#xff0c;白银总产量始终维持10亿盎司水平上下波动&#xff0c;2015~2023年的年均复合增速在0.4%&#xff0c;预计2024年的产量将下降1%。矿产银的产量从…