第一章:OkHttp核心架构与初始化

219 阅读6分钟

1.1 OkHttp整体架构设计

1.1.1 核心设计理念

OkHttp的设计遵循了以下几个核心原则:

  • 分层架构:将网络请求分解为多个独立组件
  • 责任链模式:通过拦截器链实现请求处理的模块化
  • 连接复用:通过连接池减少TCP握手开销
  • 异步优先:默认支持高效的非阻塞I/O操作

1.1.2 核心组件关系

deepseek_mermaid_20250717_e36650.png

1.2 OkHttpClient初始化过程

1.2.1 构建者模式源码解析

// 源码路径: okhttp3/OkHttpClient.java
public class OkHttpClient implements Cloneable, Call.Factory {
    final Dispatcher dispatcher;
    final ConnectionPool connectionPool;
    final List<Interceptor> interceptors;
    
    public OkHttpClient() {
        this(new Builder());
    }
    
    OkHttpClient(Builder builder) {
        // 1. 调度器初始化
        this.dispatcher = builder.dispatcher != null ? builder.dispatcher : new Dispatcher();
        
        // 2. 连接池初始化
        this.connectionPool = builder.connectionPool != null ? 
                             builder.connectionPool : new ConnectionPool();
        
        // 3. 拦截器列表初始化
        this.interceptors = Util.immutableList(builder.interceptors);
        
        // 4. 超时设置
        this.connectTimeout = builder.connectTimeout;
        this.readTimeout = builder.readTimeout;
        this.writeTimeout = builder.writeTimeout;
        
        // ...其他参数初始化
    }
    
    // 构建者类
    public static final class Builder {
        Dispatcher dispatcher;
        ConnectionPool connectionPool;
        final List<Interceptor> interceptors = new ArrayList<>();
        int connectTimeout;
        int readTimeout;
        int writeTimeout;
        
        public Builder() {
            // 默认值设置
            this.dispatcher = new Dispatcher();
            this.connectionPool = new ConnectionPool();
            this.connectTimeout = 10_000; // 10秒
            this.readTimeout = 10_000;    // 10秒
            this.writeTimeout = 10_000;   // 10秒
        }
        
        public Builder connectTimeout(long timeout, TimeUnit unit) {
            // 参数验证
            if (timeout < 0) throw new IllegalArgumentException("timeout < 0");
            if (unit == null) throw new NullPointerException("unit == null");
            
            // 转换为毫秒
            long millis = unit.toMillis(timeout);
            if (millis > Integer.MAX_VALUE) {
                throw new IllegalArgumentException("Timeout too large.");
            }
            this.connectTimeout = (int) millis;
            return this;
        }
        
        // 其他设置方法类似...
        
        public OkHttpClient build() {
            return new OkHttpClient(this);
        }
    }
}

1.2.2 关键配置项说明

配置项默认值说明
dispatchernew Dispatcher()请求调度器,管理异步请求队列
connectionPoolnew ConnectionPool()连接池,默认最大空闲连接数5,存活时间5分钟
connectTimeout10秒TCP连接建立超时时间
readTimeout10秒读取响应超时时间
writeTimeout10秒发送请求超时时间
interceptors空列表全局拦截器,在所有拦截器之前执行

1.3 Request请求构建过程

1.3.1 Request类结构解析

// 源码路径: okhttp3/Request.java
public class Request {
    final HttpUrl url;
    final String method;
    final Headers headers;
    final RequestBody body;
    final Object tag;
    
    Request(Builder builder) {
        this.url = builder.url;
        this.method = builder.method;
        this.headers = builder.headers.build();
        this.body = builder.body;
        this.tag = builder.tag != null ? builder.tag : this;
    }
    
    public static class Builder {
        HttpUrl url;
        String method;
        Headers.Builder headers;
        RequestBody body;
        Object tag;
        
        public Builder() {
            this.method = "GET";
            this.headers = new Headers.Builder();
        }
        
        public Builder url(HttpUrl url) {
            if (url == null) throw new NullPointerException("url == null");
            this.url = url;
            return this;
        }
        
        public Builder url(String url) {
            if (url == null) throw new NullPointerException("url == null");
            // 解析URL并验证
            if (url.regionMatches(true, 0, "ws:", 0, 3)) {
                url = "http:" + url.substring(3);
            } else if (url.regionMatches(true, 0, "wss:", 0, 4)) {
                url = "https:" + url.substring(4);
            }
            return url(HttpUrl.get(url));
        }
        
        public Builder header(String name, String value) {
            headers.set(name, value);
            return this;
        }
        
        public Builder addHeader(String name, String value) {
            headers.add(name, value);
            return this;
        }
        
        public Builder post(RequestBody body) {
            return method("POST", body);
        }
        
        public Builder method(String method, RequestBody body) {
            // 方法验证
            if (method == null) throw new NullPointerException("method == null");
            if (method.length() == 0) throw new IllegalArgumentException("method.length() == 0");
            
            // 对于需要请求体的方法验证
            if (body != null) {
                if (!HttpMethod.permitsRequestBody(method)) {
                    throw new IllegalArgumentException("method " + method + " must not have a request body.");
                }
            } else if (HttpMethod.requiresRequestBody(method)) {
                throw new IllegalArgumentException("method " + method + " must have a request body.");
            }
            
            this.method = method;
            this.body = body;
            return this;
        }
        
        public Request build() {
            if (url == null) throw new IllegalStateException("url == null");
            return new Request(this);
        }
    }
}

1.3.2 RequestBody类型体系

deepseek_mermaid_20250717_bc2255.png

1.3.3 请求构建实践

// 1. GET请求示例
Request getRequest = new Request.Builder()
    .url("https://api.example.com/users")
    .header("Authorization", "Bearer token123")
    .build();

// 2. POST表单请求
RequestBody formBody = new FormBody.Builder()
    .add("username", "john_doe")
    .add("password", "secret")
    .build();

Request postRequest = new Request.Builder()
    .url("https://api.example.com/login")
    .post(formBody)
    .build();

// 3. POST JSON请求
MediaType JSON = MediaType.get("application/json; charset=utf-8");
String json = "{"name":"John", "age":30}";
RequestBody jsonBody = RequestBody.create(json, JSON);

Request jsonRequest = new Request.Builder()
    .url("https://api.example.com/users")
    .post(jsonBody)
    .build();

1.4 核心类职责详解

1.4.1 OkHttpClient核心职责

  • 请求调度:通过Dispatcher管理请求队列
  • 连接管理:通过ConnectionPool复用HTTP连接
  • 协议支持:处理HTTP/1.1、HTTP/2和WebSocket
  • 超时控制:管理连接、读写超时
  • 拦截器链:组织请求处理流程

1.4.2 Dispatcher工作原理

Dispatcher管理三个队列:

  1. runningAsyncCalls:正在执行的异步请求
  2. readyAsyncCalls:等待执行的异步请求
  3. runningSyncCalls:正在执行的同步请求

默认并发策略:

  • 最大并发请求数:64
  • 每个主机最大并发数:5

1.4.3 ConnectionPool连接复用机制

连接池维护TCP连接,减少重复握手开销:

  • 最大空闲连接数:5
  • 空闲连接存活时间:5分钟
  • 自动清理过期连接

1.5 最佳实践:OkHttpClient单例模式

1.5.1 为什么要使用单例?

// 错误做法:每次请求创建新客户端
public void fetchData(String url) {
    OkHttpClient client = new OkHttpClient(); // 每次创建新实例
    Request request = new Request.Builder().url(url).build();
    try (Response response = client.newCall(request).execute()) {
        // 处理响应
    }
}

问题分析:

  1. 每次请求都创建新线程池,增加系统开销
  2. 无法复用TCP连接,失去连接池优势
  3. 可能导致文件描述符耗尽

1.5.2 正确实现方式

// 单例实现
public class HttpClient {
    private static final OkHttpClient INSTANCE = new OkHttpClient.Builder()
        .connectTimeout(15, TimeUnit.SECONDS)
        .readTimeout(15, TimeUnit.SECONDS)
        .connectionPool(new ConnectionPool(10, 5, TimeUnit.MINUTES))
        .build();
    
    public static OkHttpClient getInstance() {
        return INSTANCE;
    }
}

// 使用示例
public void fetchUserData() {
    OkHttpClient client = HttpClient.getInstance();
    Request request = new Request.Builder()
        .url("https://api.example.com/users")
        .build();
    
    try (Response response = client.newCall(request).execute()) {
        // 处理响应
    }
}

1.5.3 自定义配置单例

public class CustomHttpClient {
    private static volatile OkHttpClient instance;
    
    public static OkHttpClient getInstance() {
        if (instance == null) {
            synchronized (CustomHttpClient.class) {
                if (instance == null) {
                    instance = createClient();
                }
            }
        }
        return instance;
    }
    
    private static OkHttpClient createClient() {
        // 创建自定义DNS解析器
        Dns customDns = hostname -> {
            // 自定义DNS解析逻辑
            return InetAddress.getAllByName(hostname);
        };
        
        // 创建线程安全的CookieJar
        CookieJar cookieJar = new CookieJar() {
            private final Map<HttpUrl, List<Cookie>> cookieStore = new ConcurrentHashMap<>();
            
            @Override
            public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
                cookieStore.put(url, cookies);
            }
            
            @Override
            public List<Cookie> loadForRequest(HttpUrl url) {
                return cookieStore.getOrDefault(url, Collections.emptyList());
            }
        };
        
        // 构建客户端
        return new OkHttpClient.Builder()
            .connectTimeout(20, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .dns(customDns)
            .cookieJar(cookieJar)
            .addInterceptor(new LoggingInterceptor())
            .build();
    }
}

1.6 初始化过程性能优化

1.6.1 关键配置优化项

OkHttpClient client = new OkHttpClient.Builder()
    // 1. 连接池优化
    .connectionPool(new ConnectionPool(
        10,     // 最大空闲连接数
        5,      // 保持存活时间(分钟)
        TimeUnit.MINUTES))
    
    // 2. 超时设置优化
    .connectTimeout(15, TimeUnit.SECONDS)  // 连接超时
    .readTimeout(30, TimeUnit.SECONDS)     // 读取超时
    .writeTimeout(30, TimeUnit.SECONDS)    // 写入超时
    
    // 3. 协议配置优化
    .protocols(Arrays.asList(Protocol.HTTP_2, Protocol.HTTP_1_1))
    
    // 4. 连接规范优化
    .connectionSpecs(Arrays.asList(
        ConnectionSpec.MODERN_TLS, 
        ConnectionSpec.CLEARTEXT))
    
    // 5. 自定义DNS解析
    .dns(new CustomDnsResolver())
    
    // 6. 事件监听器(用于监控)
    .eventListener(new PerformanceEventListener())
    
    .build();

1.6.2 DNS解析优化

public class CustomDnsResolver implements Dns {
    private static final long CACHE_DURATION = 5 * 60 * 1000; // 5分钟缓存
    private final Map<String, DnsCacheEntry> cache = new ConcurrentHashMap<>();
    
    @Override
    public List<InetAddress> lookup(String hostname) throws UnknownHostException {
        // 1. 检查缓存
        DnsCacheEntry cached = cache.get(hostname);
        if (cached != null && !cached.isExpired()) {
            return cached.addresses;
        }
        
        // 2. 系统DNS解析
        List<InetAddress> addresses = Dns.SYSTEM.lookup(hostname);
        
        // 3. 缓存结果
        cache.put(hostname, new DnsCacheEntry(addresses));
        
        return addresses;
    }
    
    private static class DnsCacheEntry {
        final List<InetAddress> addresses;
        final long expireTime;
        
        DnsCacheEntry(List<InetAddress> addresses) {
            this.addresses = addresses;
            this.expireTime = System.currentTimeMillis() + CACHE_DURATION;
        }
        
        boolean isExpired() {
            return System.currentTimeMillis() > expireTime;
        }
    }
}

1.7 核心设计模式应用

1.7.1 构建者模式(Builder Pattern)

  • 应用场景:创建复杂对象(OkHttpClient, Request)

  • 优势

    • 分离对象的构建与表示
    • 支持链式调用,提高可读性
    • 允许创建不可变对象

1.7.2 工厂模式(Factory Pattern)

  • 应用场景:Call对象创建
// OkHttpClient实现了Call.Factory接口
public interface Call.Factory {
    Call newCall(Request request);
}

// 实现
public class OkHttpClient implements Call.Factory {
    @Override public Call newCall(Request request) {
        return RealCall.newRealCall(this, request, false);
    }
}

1.7.3 责任链模式(Chain of Responsibility)

  • 应用场景:拦截器链处理请求

  • 优势

    • 解耦请求发送者和接收者
    • 动态添加处理逻辑
    • 支持请求/响应的预处理和后处理

1.8 常见问题与解决方案

问题1:如何添加全局请求头?

解决方案:使用应用拦截器

OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(chain -> {
        Request original = chain.request();
        Request modified = original.newBuilder()
            .header("User-Agent", "MyApp/1.0")
            .header("Authorization", "Bearer token")
            .build();
        return chain.proceed(modified);
    })
    .build();

问题2:如何实现统一错误处理?

解决方案:使用网络拦截器

OkHttpClient client = new OkHttpClient.Builder()
    .addNetworkInterceptor(chain -> {
        Response response = chain.proceed(chain.request());
        if (!response.isSuccessful()) {
            throw new HttpException(response.code(), response.message());
        }
        return response;
    })
    .build();

问题3:如何启用响应缓存?

解决方案:配置缓存目录

// 创建10MB缓存
int cacheSize = 10 * 1024 * 1024; 
File cacheDirectory = new File(context.getCacheDir(), "http-cache");
Cache cache = new Cache(cacheDirectory, cacheSize);

OkHttpClient client = new OkHttpClient.Builder()
    .cache(cache)
    .build();

本章小结

第一章深入分析了OkHttp的核心架构与初始化过程:

  1. OkHttpClient是核心入口,采用构建者模式配置
  2. Request封装HTTP请求,支持多种请求类型
  3. Dispatcher管理请求队列和线程池
  4. ConnectionPool实现TCP连接复用
  5. 单例模式是使用OkHttp的最佳实践
  6. 合理配置超时、连接池和DNS可提升性能

在下一章中,我们将深入探讨请求调度与执行机制,分析同步和异步请求的内部实现原理。