OkHttp 基础浅析

1 阅读8分钟

一、 OkHttp 简介

OkHttp 是一个高效的 HTTP 客户端,由 Square 公司开发,支持 Android 和 Java 应用。

核心特性

  • HTTP/2 支持:多路复用、头部压缩等
  • 连接池:减少请求延迟
  • 透明 GZIP 压缩:减少下载大小
  • 响应缓存:避免重复请求
  • 自动重连:处理网络问题
  • WebSocket 支持

二、基本使用

1. 添加依赖

implementation 'com.squareup.okhttp3:okhttp:4.12.0'

2. 同步请求

OkHttpClient client = new OkHttpClient();

Request request = new Request.Builder()
    .url("https://api.example.com/data")
    .build();

try (Response response = client.newCall(request).execute()) {
    if (response.isSuccessful()) {
        String responseBody = response.body().string();
        System.out.println(responseBody);
    }
}

3. 异步请求

OkHttpClient client = new OkHttpClient();

Request request = new Request.Builder()
    .url("https://api.example.com/data")
    .build();

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

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

三、核心组件详解

1. OkHttpClient

OkHttpClient client = new OkHttpClient.Builder()
    .connectTimeout(10, TimeUnit.SECONDS)    // 连接超时
    .writeTimeout(10, TimeUnit.SECONDS)      // 写入超时
    .readTimeout(30, TimeUnit.SECONDS)       // 读取超时
    .addInterceptor(new HttpLoggingInterceptor()) // 拦截器
    .cache(new Cache(cacheDir, cacheSize))   // 缓存
    .retryOnConnectionFailure(true)          // 自动重连
    .build();

2. Request 请求构建

// POST 请求
RequestBody formBody = new FormBody.Builder()
    .add("username", "user")
    .add("password", "pass")
    .build();

// JSON 请求
MediaType JSON = MediaType.parse("application/json; charset=utf-8");
RequestBody jsonBody = RequestBody.create(
    "{"name":"John"}", 
    JSON
);

// 文件上传
RequestBody fileBody = RequestBody.create(
    file, 
    MediaType.parse("image/png")
);
RequestBody multipartBody = new MultipartBody.Builder()
    .setType(MultipartBody.FORM)
    .addFormDataPart("file", "avatar.png", fileBody)
    .build();

Request request = new Request.Builder()
    .url(url)
    .header("Authorization", "Bearer token") // 添加头部
    .addHeader("User-Agent", "OkHttp Example")
    .post(formBody)  // GET、POST、PUT、DELETE 等
    .build();

3. Response 响应处理

try (Response response = client.newCall(request).execute()) {
    // 状态码
    int statusCode = response.code();
    
    // 响应头
    Headers headers = response.headers();
    String contentType = headers.get("Content-Type");
    
    // 响应体
    ResponseBody body = response.body();
    String stringBody = body.string();       // 只能调用一次
    InputStream inputStream = body.byteStream();
    byte[] bytes = body.bytes();
    
    // 判断响应类型
    if (response.isSuccessful()) {
        // 2xx 响应
    } else if (response.isRedirect()) {
        // 3xx 重定向
    }
}

四、高级功能

1. 拦截器(Interceptors)

// 应用拦截器(处理请求/响应)
class LoggingInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        
        // 请求前处理
        long startTime = System.nanoTime();
        Log.d("OkHttp", String.format("Sending request %s%n%s",
            request.url(), request.headers()));
        
        Response response = chain.proceed(request);
        
        // 响应后处理
        long endTime = System.nanoTime();
        Log.d("OkHttp", String.format("Received response in %.1fms%n%s",
            (endTime - startTime) / 1e6d, response.headers()));
        
        return response;
    }
}

// 网络拦截器(处理网络层请求/响应)
OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(new LoggingInterceptor())      // 应用拦截器
    .addNetworkInterceptor(new LoggingInterceptor()) // 网络拦截器
    .build();

2. 缓存配置

// 创建缓存
int cacheSize = 10 * 1024 * 1024; // 10 MB
Cache cache = new Cache(new File("cacheDir"), cacheSize);

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

// 强制使用缓存
Request request = new Request.Builder()
    .url(url)
    .cacheControl(new CacheControl.Builder()
        .onlyIfCached()
        .maxStale(7, TimeUnit.DAYS)
        .build())
    .build();

3. Cookie 管理

// 使用 CookieJar
CookieJar cookieJar = new CookieJar() {
    private final Map<String, List<Cookie>> cookieStore = 
        new ConcurrentHashMap<>();
    
    @Override
    public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
        cookieStore.put(url.host(), cookies);
    }
    
    @Override
    public List<Cookie> loadForRequest(HttpUrl url) {
        List<Cookie> cookies = cookieStore.get(url.host());
        return cookies != null ? cookies : new ArrayList<>();
    }
};

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

4. 连接池管理

ConnectionPool connectionPool = new ConnectionPool(
    5,      // 最大空闲连接数
    5,      // 保持时间(分钟)
    TimeUnit.MINUTES
);

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

5. 认证处理

// 基本认证
Authenticator authenticator = (route, response) -> {
    String credential = Credentials.basic("username", "password");
    return response.request().newBuilder()
        .header("Authorization", credential)
        .build();
};

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

6. WebSocket

Request request = new Request.Builder()
    .url("ws://echo.websocket.org")
    .build();

WebSocket webSocket = client.newWebSocket(request, new WebSocketListener() {
    @Override
    public void onOpen(WebSocket webSocket, Response response) {
        webSocket.send("Hello!");
    }
    
    @Override
    public void onMessage(WebSocket webSocket, String text) {
        System.out.println("Received: " + text);
    }
    
    @Override
    public void onClosing(WebSocket webSocket, int code, String reason) {
        webSocket.close(1000, null);
    }
});

五、最佳实践

1. 单例模式使用

public class OkHttpClientManager {
    private static OkHttpClient instance;
    
    public static OkHttpClient getInstance() {
        if (instance == null) {
            synchronized (OkHttpClientManager.class) {
                if (instance == null) {
                    instance = new OkHttpClient.Builder()
                        .connectTimeout(10, TimeUnit.SECONDS)
                        .readTimeout(10, TimeUnit.SECONDS)
                        .writeTimeout(10, TimeUnit.SECONDS)
                        .build();
                }
            }
        }
        return instance;
    }
}

2. 请求取消

// 创建可取消的请求
Call call = client.newCall(request);

// 执行请求
call.enqueue(callback);

// 取消请求
call.cancel();

3. 超时重试

class RetryInterceptor implements Interceptor {
    private int maxRetry;
    
    public RetryInterceptor(int maxRetry) {
        this.maxRetry = maxRetry;
    }
    
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response = null;
        IOException exception = null;
        
        for (int i = 0; i <= maxRetry; i++) {
            try {
                response = chain.proceed(request);
                if (response.isSuccessful()) {
                    return response;
                }
            } catch (IOException e) {
                exception = e;
            }
        }
        
        throw exception != null ? exception : 
            new IOException("Request failed after " + maxRetry + " retries");
    }
}

4. 错误处理

client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        if (call.isCanceled()) {
            // 请求被取消
        } else if (e instanceof SocketTimeoutException) {
            // 超时
        } else if (e instanceof ConnectException) {
            // 连接失败
        }
    }
    
    @Override
    public void onResponse(Call call, Response response) throws IOException {
        if (!response.isSuccessful()) {
            switch (response.code()) {
                case 401:
                    // 未授权
                    break;
                case 404:
                    // 资源不存在
                    break;
                case 500:
                    // 服务器错误
                    break;
            }
        }
    }
});

六、性能优化建议

  1. 重用 OkHttpClient 实例:避免重复创建
  2. 合理设置超时时间:根据网络状况调整
  3. 使用连接池:默认已优化,无需特殊配置
  4. 启用 GZIP:自动处理,无需手动配置
  5. 合理使用缓存:减少重复请求
  6. 批量请求:减少连接次数
  7. 及时关闭 Response:避免资源泄漏

七、常见问题

1. 内存泄漏

  • 确保在 Activity/Fragment 销毁时取消请求
  • 避免持有 Context 引用

2. 线程安全问题

  • OkHttpClient 是线程安全的
  • 可在多线程环境中共享

3. 大文件下载

  • 使用 ResponseBody.byteStream() 流式处理
  • 避免一次性加载到内存

4. HTTPS 证书验证

// 信任所有证书(仅测试环境使用)
OkHttpClient client = new OkHttpClient.Builder()
    .sslSocketFactory(createSSLSocketFactory(), trustAllCerts)
    .hostnameVerifier((hostname, session) -> true)
    .build();

八、与 Retrofit 配合使用

OkHttp 是 Retrofit 的底层实现,Retrofit 在 OkHttp 基础上提供了 REST API 的抽象层:

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .client(okHttpClient)  // 使用自定义的 OkHttpClient
    .addConverterFactory(GsonConverterFactory.create())
    .build();

OkHttp 是一个功能强大且灵活的 HTTP 客户端,通过合理配置和正确使用,可以显著提升应用的网络性能和用户体验。

九、核心知识点

下图清晰地展示了从发起请求到获得响应的完整逻辑,从宏观上把握 OkHttp 的工作流程:

flowchart TD
    A["发起请求<br>(execute/enqueue)"] --> B{Dispatcher 调度请求}
    
    B -- 同步请求 --> C[加入 runningSyncCalls 队列<br>直接执行]
    B -- 异步请求 --> D{"是否满足并发条件?"}
    
    D -- "是<br>(未超64请求/每Host未超5)" --> E[加入 runningAsyncCalls 队列<br>提交至线程池执行]
    D -- 否 --> F[加入 readyAsyncCalls 等待队列]
    
    E --> G[执行完成<br>触发 finished]
    C --> G
    F --> H[等待队列中的请求<br>在 finished 中<br>被 promoteAndExecute 激活]
    H --> E
    
    G --> I[构建并执行<br>拦截器责任链]
    
    subgraph I ["拦截器责任链 (getResponseWithInterceptorChain)"]
        direction LR
        I1["应用拦截器<br>(自定义)"] --> I2[RetryAndFollowUpInterceptor<br>重试与重定向] --> I3[BridgeInterceptor<br>桥接补全头部] --> I4[CacheInterceptor<br>缓存处理] --> I5[ConnectInterceptor<br>建立/复用连接] --> I6["网络拦截器<br>(自定义)"] --> I7[CallServerInterceptor<br>发送请求/读取响应]
    end
    
    I --> J[返回最终 Response]

1. 拦截器责任链(Interceptor Chain)

这是OkHttp最核心的机制。责任链将网络请求分解为多个独立步骤(拦截器),每个拦截器处理特定任务
下图展示了各个内置拦截器在请求过程中的职责与作用:

flowchart LR
    A[发起请求] --> B[应用拦截器<br>全局请求/响应处理] --> C[重试与重定向拦截器<br>处理失败与重定向] --> D[桥接拦截器<br>补全Header/解压] --> E[缓存拦截器<br>查询/更新缓存] --> F{"缓存是否命中?"}
    
    F -- 是 --> G[直接返回缓存响应]
    F -- 否 --> H[连接拦截器<br>建立/复用TCP连接] --> I[网络拦截器<br>网络层监控] --> J[请求服务器拦截器<br>I/O读写]
    
    G --> K[返回响应]
    J --> K

2. 连接复用与连接池(ConnectionPool)

这是OkHttp性能优化的关键。连接池的核心逻辑是:在建立TCP连接前,会依次尝试从池中获取可用连接,顺序是:已分配的连接 -> 连接池匹配(无路由) -> 连接池匹配(有路由) -> 创建新连接 -> 再次确认。连接池会定期清理空闲连接(默认最多5个空闲连接,保活5分钟)以释放资源

3. 调度器与线程池(Dispatcher)

Dispatcher 负责管理异步请求的调度,其内部维护着三个双端队列(runningAsyncCallsreadyAsyncCallsrunningSyncCalls)和一个线程池

  • 并发控制:默认最大并发请求数为 64,单个主机(Host)最大并发数为 5
  • 线程池:本质是一个 newCachedThreadPool,核心线程数为0,使用 SynchronousQueue,能快速响应网络请求

4. 核心设计模式

OkHttp优雅的架构离不开设计模式的应用:

  • 责任链模式:核心,用于拦截器链
  • 建造者模式:用于构建 OkHttpClient 和 Request 等复杂对象
  • 外观模式OkHttpClient 封装了内部复杂子系统,提供统一接口
  • 策略模式:体现在缓存策略(如 CacheStrategy)等方面

十、常见面试问答

具体可参考上面核心知识点

1. OkHttp工作流程(核心:拦截器链)

一句话概括:请求通过调度器进入拦截器责任链,依次处理,最终由服务器拦截器完成网络I/O。

详细流程如下

  • 请求调度:同步请求直接执行,异步请求由Dispatcher管理

  • 拦截器链处理(关键流程): 应用拦截器 → 重试拦截器 → 桥接拦截器 → 缓存拦截器 → 连接拦截器(建立/复用连接)→ 网络拦截器 → 服务器拦截器

  • 返回响应:经过反向传递,最终返回给调用方

2. OkHttp连接复用实现

核心机制:连接池(ConnectionPool)

实现原理

  • 复用条件:相同主机(host + port + TLS配置)

  • 查找顺序

    • 已分配的连接 → 连接池匹配(无路由) → 连接池匹配(有路由) → 创建新连接
  • 连接管理

    • 默认最多保持5个空闲连接
    • 空闲连接保活5分钟
    • 定期清理超时空闲连接

性能优势:减少TCP握手和TLS握手开销,显著提升性能。

3. 应用拦截器 vs. 网络拦截器的区别

维度应用拦截器(addInterceptor网络拦截器(addNetworkInterceptor
位置在重试/重定向拦截器之前在连接建立之后,服务器拦截器之前
触发次数一次(即使重试)多次(重试/重定向会重新触发)
获取信息原始请求,无网络细节携带网络信息(连接、IP等)
典型用途添加全局Header、日志记录监控网络数据、修改重试策略

4. Dispatcher的作用

核心职责:异步请求调度中心

具体功能

  • 队列管理:维护三个队列

    • runningAsyncCalls:正在执行的异步请求
    • readyAsyncCalls:等待执行的异步请求
    • runningSyncCalls:正在执行的同步请求
  • 并发控制

    • 最大并发请求数:64个
    • 单主机最大并发数:5个
  • 线程池管理:使用CachedThreadPool(核心线程数0,适合I/O密集型)

  • 调度逻辑:请求完成后自动从等待队列提升新请求

5. OkHttp中的设计模式

  • 责任链模式(核心):拦截器链处理请求

  • 建造者模式:构建OkHttpClientRequest等复杂对象

  • 外观模式OkHttpClient封装内部复杂子系统

  • 策略模式:缓存策略(CacheStrategy

6. OkHttp缓存工作原理

负责组件CacheInterceptor

缓存机制

  • 存储位置:磁盘LRU缓存(基于DiskLruCache

  • 缓存决策:遵循HTTP缓存标准

  • 关键逻辑

    • 检查请求是否允许缓存(Cache-Control头)
    • 计算缓存策略:网络请求、缓存响应或两者
    • 处理条件请求(If-Modified-Since等)
  • 缓存流程

    收到请求 → 检查缓存 → 缓存有效 → 直接返回
                          → 缓存无效/需要验证 → 发送条件请求
                                               → 304(未修改) → 更新缓存后返回
                                               → 200(新内容) → 更新缓存后返回
    

7. OkHttp网络监控方法

核心工具:自定义拦截器

监控实现

// 网络拦截器(可监控实际网络数据)
class NetworkMonitorInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        // 1. 获取网络层请求信息
        Request request = chain.request();
        Connection connection = chain.connection(); // 获取连接信息
        long startNs = System.nanoTime();
        
        // 2. 记录请求信息
        logRequest(request, connection);
        
        // 3. 继续请求
        Response response = chain.proceed(request);
        
        // 4. 记录响应信息
        long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
        logResponse(response, tookMs, connection);
        
        return response;
    }
}

监控要点

  • 网络拦截器可获取:实际IP、连接协议、握手信息
  • 应用拦截器适合:统计请求耗时、记录业务日志