Android-okhttp解析

Okhttp的优点

  • 支持Http2所有请求共享同一个socket
  • 通过间接池(http1.x socket复用)减少请求延迟
  • 通过间接池(http1.x socket复用)减少请求延迟
  • 使用缓存
  • 自动重定向

Okhttp分发器

//请求流程-分发器
Disoarcher.enqueue(AsyncCall){
    //执行队列小于64,同一个Host请求小于5
    if (runningAsyncCalls.size < 64) {
        //ArrayDeque双向队列
        runningAsyncCalls.add
        executorService().excute(call){
            response = call.excute()
            dispatcher.finished(){
                runingAsycnCalls.remove(call)
                runningAsyncCalls.add(readyAsyncCalls.next())
                readyAsyncAclls.remove()
            }
        }
    } else{
        readyAsyncCalls.add
    }
}
//分发器--线程池
executorService(){
    //线程池策略:当新建一个任务时,首先会判断当前任务数量是否小于核心线程数量,小于的话直接执行。否则会判断当前任务队列是否还有位置,有的话会添加到任务队列中等待执行。当任务队列也满了的话会判断最大线程数量,如果当前线程数量小于最大线程数量,则创建新的线程去执行。
    //使用Synchonous队列是因为这个队列没有容量,不会出现队列任务被核心线程阻塞导致无法执行的状况,保证了并发线程的最大吞吐量。
    new threadPoolExecutor(0,MAX_VALUE,60,TimeUnit.SECONDS,SynchonousQueue())
}

拦截器

  1. RetryAndFollowUpInterceptor(重试和重定向拦截器)
    • 主要负责处理请求失败时的重试逻辑,以及在需要时进行重定向。例如,当请求因为网络问题失败时,它会尝试重新发送请求;当响应状态码指示需要重定向时,它会构建新的请求并发送。
  2. BridgeInterceptor(桥接拦截器)
    • 主要负责将用户请求补全(如添加必要的头部信息),并对响应进行额外处理(如添加缓存头部信息)。它作为用户请求与服务器响应之间的桥梁,确保请求和响应符合 HTTP 协议要求。
  3. CacheInterceptor(缓存拦截器)
    • 在请求发送前,它首先会查询缓存,如果缓存有效则直接返回缓存的响应,避免网络请求。同时,它也会判断服务器响应是否需要被缓存,如果需要,则将响应存储到缓存中。
  4. ConnectInterceptor(连接拦截器)
    • 负责与服务器建立 TCP 连接。它处理与服务器之间的连接复用,以及连接失败时的重试逻辑。
  5. CallServerInterceptor(请求服务器拦截器)
    • 这是实际与服务器通信的拦截器,负责将请求数据发送给服务器,并解析服务器返回的响应数据。它会封装请求数据为 HTTP 报文,并将服务器返回的响应数据解析为 OkHttp 的 Response 对象。

责任链模式

OkHttp中的责任链模式是一种设计模式,用于处理网络请求的拦截和响应。在OkHttp中,责任链模式通过拦截器(Interceptor)实现,每个拦截器可以处理请求或响应,或者将请求或响应传递给链中的下一个拦截器。这种模式允许开发者自定义网络请求的处理逻辑,如添加日志、修改请求头、缓存响应等。

getResponseWithInterceptorChain(){
    //创建拦截器列表
    List<Interceptor> interceptors = new ArrayLists();
    interceptors.add();
    interceptors.add();
    interceptors.add();
    //组件责任链条,传入拦截器列表
    Chain chain = new Chain(interceptors);
    //责任链开始执行
    chain.proceed{
        //获取第一条拦截器
        interceptor = interceptors.get(0);
        //执行拦截器的方法,并接受一个新的Chain对象,其中Chain的责任链去除掉当前的拦截器
        interceptor.intercept(new Chain(interceptors(index-1))){
            //。。。执行拦截器内部实际逻辑
            //继续执责任链的下一条数据并向上返回
            return chain.proceed();
        }
    }
}

在 OkHttp 客户端配置中,我们通过 addInterceptor 方法将拦截器添加到责任链中。当发起请求时,这些拦截器会按照添加的顺序依次执行,每个拦截器都可以对请求或响应进行处理,并最终调用 chain.proceed(request) 方法将请求传递给下一个拦截器或最终的请求执行者。

这种设计模式使得我们可以灵活地扩展和修改请求处理流程,而不需要修改现有的代码逻辑。

RetryAndFollowUpInterceptor

主要处理响应是否需要重试和重定向

// 需要重定向的HTTP状态码
private static final int[] REDIRECT_CODES = {301, 302, 303, 307, 308};
 // 最大重试次数
private static final int MAX_RETRY_COUNT = 3;


Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    Response response = null;
    int retryCount = 0;
    
    while (retryCount < MAX_RETRY_COUNT) {
        try {
            response = chain.proceed(request);
            
            // 检查响应是否需要重试
            if (!response.isSuccessful() && shouldRetry(response)) {
                retryCount++;
                // 可以在这里修改请求,比如增加一些头部信息
                // request = modifyRequestForRetry(request);
                continue; // 重试请求
            }
            
            // 检查响应是否需要重定向
            if (isRedirectNeeded(response)) {
                String redirectUrl = response.getHeader("Location");
                if (redirectUrl != null) {
                    // 创建新的请求,使用重定向的URL
                    request = createRedirectRequest(request, redirectUrl);
                    continue; // 执行重定向请求
                }
            }
            
            // 如果不需要重试也不需要重定向,则返回响应
            return response;
            
        } catch (IOException e) {
            // 处理IO异常,根据需要决定是否重试
            if (shouldRetryOnException(e)) {
                retryCount++;
                continue; // 重试请求
            } else {
                throw e; // 抛出异常,终止请求
            }
        }
    }
    
    // 如果达到最大重试次数仍然失败,则抛出一个异常或返回特定的错误响应
    throw new IOException("Failed to get successful response after " + MAX_RETRY_COUNT + " retries.");
}

// 判断响应是否需要重试
private boolean shouldRetry(Response response) {
    // 根据响应码或其他条件判断是否需要重试
    // 比如,可以重试500服务器错误或502坏网关错误等
    return response.code() == 500 || response.code() == 502 || response.code() == 503;
}

// 判断响应是否需要重定向
private boolean isRedirectNeeded(Response response) {
    // 检查响应码是否在需要重定向的HTTP状态码列表中
    for (int code : REDIRECT_CODES) {
        if (response.code() == code) {
            return true;
        }
    }
    return false;
}

// 创建重定向请求
private Request createRedirectRequest(Request originalRequest, String redirectUrl) {
    // 根据原始请求和重定向URL创建新的请求
    // ... 创建逻辑 ...
    return new Request.Builder()
            // ... 设置请求的属性,比如URL、头部信息等 ...
            .url(redirectUrl)
            .build();
}

// 判断异常是否需要重试
private boolean shouldRetryOnException(IOException e) {
    // 根据异常类型或其他条件判断是否需要重试
    // 比如,可以重试SocketTimeoutException或UnknownHostException等
    return e instanceof SocketTimeoutException || e instanceof UnknownHostException;
}

BridgeInterceptor

添加了以下请求头

  1. Content-Type:如果请求体(RequestBody)存在且内容类型(MediaType)不为空,则会添加Content-Type请求头,用于指示资源的MIME类型。
  2. Content-Length:如果请求体的内容长度是固定的,则会添加Content-Length请求头,用于指示请求体的长度(以字节为单位)。
  3. Transfer-Encoding:如果请求体的内容长度是未知的(即内容长度不是固定的),则会添加Transfer-Encoding: chunked请求头,表示请求体将被分成多个块进行传输。
  4. Host:如果请求中没有设置Host请求头,则会添加Host请求头,其值为请求的URL的主机部分。
  5. Connection:如果请求中没有设置Connection请求头,则会添加Connection: Keep-Alive请求头,用于指示希望保持TCP连接活跃。
  6. Accept-Encoding:如果请求中没有设置Accept-Encoding请求头,并且也没有设置Range请求头,则会添加Accept-Encoding: gzip请求头,用于指示客户端支持gzip压缩。
  7. Cookie:如果请求URL对应的Cookie不为空,则会添加Cookie请求头,用于将Cookie信息发送给服务器。
  8. User-Agent:如果请求中没有设置User-Agent请求头,则会添加User-Agent请求头,其值通常为OkHttp的版本信息或其他自定义的用户代理字符串。

CacheInterceptor

@Override
public Response intercept(Chain chain) throws IOException {
    // 尝试从缓存中获取响应
    Response cacheCandidate = cache != null ? cache.get(chain.request()) : null;
    long now = System.currentTimeMillis();

    // 创建缓存策略
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();

    // 从缓存策略中获取网络请求和缓存响应
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;

    // 如果缓存策略指示不需要网络请求,并且存在有效的缓存响应,则直接返回缓存响应
    if (cache != null) {
        cache.trackResponse(strategy);
    }

    // 如果缓存策略指示不需要网络请求,但缓存响应为空,则返回一个错误响应
    if (cacheCandidate != null && cacheResponse == null) {
        closeQuietly(cacheCandidate.body());
        // ...
    }

    // 如果缓存策略指示不需要网络请求,并且存在有效的缓存响应,则直接返回缓存响应
    if (networkRequest == null) {
        return cacheResponse.newBuilder()
                .cacheResponse(stripBody(cacheResponse))
                .build();
    }

    // 如果需要发起网络请求,则调用下一个拦截器进行处理
    Response networkResponse = null;
    try {
        networkResponse = chain.proceed(networkRequest);
    } finally {
        // ...
    }

    // 如果服务器返回304状态码,表示内容未修改,使用缓存响应
    if (cacheResponse != null && networkResponse.code() == HTTP_NOT_MODIFIED) {
        Response response = cacheResponse.newBuilder()
                .headers(combine(cacheResponse.headers(), networkResponse.headers()))
                .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
                .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
                .cacheResponse(stripBody(cacheResponse))
                .networkResponse(stripBody(networkResponse))
                .build();
        cache.trackConditionalCacheHit();
        cache.update(cacheResponse, response);
        return response;
    }

    // 否则,返回网络响应,并根据需要更新缓存
    // ...
}

networkRequest cacheResponse 是否启用缓存 说明
null null 缓存策略决定可以直接使用缓存响应,无需发送网络请求。
null null 缓存中不存在有效响应,需要发送网络请求获取响应。
null null 视情况而定 缓存响应存在但需要验证其有效性,或需要更新缓存。
null null 这种情况通常不会发生,因为至少会有一个非空的请求。

ConnectInterceptor

ConnectionPool:Socket连接池,默认持有5个socket连接,连接时间最长为5min。

public class CustomConnectInterceptor implements Interceptor {
    private final OkHttpClient client;
    private final ConnectionPool connectionPool;

    public CustomConnectInterceptor(OkHttpClient client) {
        this.client = client;
        this.connectionPool = client.connectionPool(); // 获取 OkHttpClient 的连接池
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        
        // 尝试从连接池中获取连接
        RealConnection connection = null;
        try {
            connection = connectionPool.get(request.url(), null, null, false);
        } catch (IOException e) {
            // 连接获取失败,可能是连接池为空或没有合适的连接
            // 可以在这里进行日志记录或异常处理
        }

        // 如果连接为空,则需要创建一个新的连接(这部分逻辑通常由 OkHttp 内部处理)
        if (connection == null) {
            // ...(创建新连接的逻辑,这里省略)
        }

        // 假设我们已经有了连接,现在创建 HttpCodec
        HttpCodec httpCodec = new Http1Codec(client, connection); // 示例中使用 Http1Codec,实际应根据连接类型选择

        // 调用下一个拦截器,并传入请求、连接和 HttpCodec
        return chain.proceed(request, new StreamAllocation(/* ... */, connection, /* ... */), httpCodec, connection);
    }

    // ...(其他可能的方法和逻辑)
}

CallServerInterceptor

职责:Request->报文、报文->Response

  1. 获取请求和连接信息
    CallServerInterceptor 从拦截器链中接收请求(Request)和连接信息(如 RealConnectionHttpCodec)。这些信息在之前的拦截器中已经被处理和准备。
  2. 写入请求头
    使用 HttpCodecwriteRequestHeaders 方法将请求头写入到输出流中。这一步是向服务器发送请求的第一部分,它包含了请求的 HTTP 方法、URL、协议版本以及任何自定义的请求头。
  3. 处理请求体
    如果请求包含请求体(例如 POST 请求),CallServerInterceptor 将检查请求头中是否包含 "Expect: 100-continue"。如果包含,它将等待服务器发送一个 100 Continue 响应,然后再发送请求体。这样做是为了在发送大量数据之前,先确认服务器愿意接收请求体。
  4. 读取响应头
    发送完请求头和请求体后,CallServerInterceptor 将使用 HttpCodecreadResponseHeaders 方法读取服务器的响应头。这些响应头包含了服务器的状态码、响应类型以及其他重要的响应信息。
  5. 处理响应体
    根据响应头的指示,CallServerInterceptor 可能会读取响应体。响应体的处理可能包括将响应数据转换为字节流、字符流或直接写入文件等。
  6. 返回响应
    最后,CallServerInterceptor 构建一个 Response 对象,包含从服务器读取的所有信息(如状态码、响应头、响应体等),并将其返回给拦截器链中的下一个拦截器(如果有的话)。如果没有下一个拦截器,则这个 Response 对象将被返回给 OkHttp 的调用者。
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容