24、网络请求之OkHttp:OkHttp基本使用、拦截器、缓存、连接池配置

OkHttp,说实话,是Android开发中绕不开的一个网络库。我最早接触它的时候,还在用HttpURLConnection手写请求,那叫一个痛苦。后来项目里引入了OkHttp,我才发现原来网络请求可以这么优雅。今天我们就来聊聊它的核心玩法。

一、OkHttp的基本使用

先看一个最简单的GET请求怎么写。我个人习惯把OkHttpClient做成单例,避免反复创建带来的资源浪费。

// 创建OkHttpClient实例
OkHttpClient client = new OkHttpClient.Builder()
        .connectTimeout(10, TimeUnit.SECONDS)
        .readTimeout(10, TimeUnit.SECONDS)
        .build();

// 构建请求
Request request = new Request.Builder()
        .url("https://api.example.com/users")
        .get()
        .build();

// 同步请求(注意:不能在主线程调用)
try {
    Response response = client.newCall(request).execute();
    if (response.isSuccessful()) {
        String body = response.body().string();
        // 处理数据
    }
} catch (IOException e) {
    e.printStackTrace();
}

// 异步请求(推荐)
client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        // 网络错误处理
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        if (response.isSuccessful()) {
            String body = response.body().string();
            // 注意:这里是在子线程,更新UI需要切回主线程
        }
    }
});

嗯,这里要注意一点:response.body().string()只能调用一次。为什么?因为OkHttp的响应体本质是一个流,读完就没了。我在项目中遇到过有人重复调用导致返回空字符串的坑,排查了半天。

二、拦截器:请求的“中间人”

拦截器是OkHttp最强大的特性之一。说白了,它就像一道流水线,每个请求和响应都会经过你定义的拦截器。你可以在这里做日志打印、添加公共Header、重试逻辑等等。

我通常把拦截器分为两类:

  • 应用拦截器(addInterceptor):在请求发出前、响应返回后执行,能看到完整的请求和响应。
  • 网络拦截器(addNetworkInterceptor):在真正发起网络请求时执行,能看到重定向和重试的细节。
// 日志拦截器
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("发送请求 %s %s",
                request.method(), request.url()));
        
        Response response = chain.proceed(request);
        long endTime = System.nanoTime();
        
        // 打印响应信息
        Log.d("OkHttp", String.format("收到响应 %s 耗时 %.1fms",
                response.code(), (endTime - startTime) / 1e6));
        
        return response;
    }
}

// 添加公共Header的拦截器
class AuthInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request original = chain.request();
        Request newRequest = original.newBuilder()
                .header("Authorization", "Bearer " + getToken())
                .header("Accept-Language", "zh-CN")
                .build();
        return chain.proceed(newRequest);
    }
}

// 使用
OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(new AuthInterceptor())      // 应用拦截器
        .addNetworkInterceptor(new LoggingInterceptor()) // 网络拦截器
        .build();
我曾经踩过的坑:在应用拦截器里修改了请求头,但网络拦截器里看到的还是原始请求。原因是应用拦截器在更上层,网络拦截器在更下层。如果你需要确保Header一定被添加,建议用网络拦截器。

三、缓存策略:让数据“快”起来

缓存是提升用户体验的关键。你想想看,用户每次打开App都要重新请求数据,那体验得多差。OkHttp的缓存机制非常灵活,它遵循HTTP的缓存规范。

配置缓存其实就两步:

  1. 创建Cache目录和大小
  2. 在OkHttpClient中配置
// 创建缓存目录
File cacheDir = new File(context.getCacheDir(), "okhttp_cache");
int cacheSize = 10 * 1024 * 1024; // 10MB
Cache cache = new Cache(cacheDir, cacheSize);

// 配置OkHttpClient
OkHttpClient client = new OkHttpClient.Builder()
        .cache(cache)
        .build();

// 服务端控制缓存(通过响应头)
// 例如服务端返回:Cache-Control: max-age=3600
// 表示该资源在1小时内可以直接使用缓存

// 客户端强制使用缓存(离线模式)
Request request = new Request.Builder()
        .url("https://api.example.com/data")
        .header("Cache-Control", "only-if-cached")
        .build();
我的建议:缓存大小不要设得太大,10-20MB足够了。另外,记得在应用退出或版本更新时清理缓存。我曾经见过一个App缓存涨到200MB,用户直接卸载了。

四、连接池配置:复用连接,减少开销

连接池是OkHttp性能优化的关键。说白了,就是复用TCP连接,避免每次请求都重新建立连接。三次握手的开销可不小。

OkHttp默认的连接池配置是:最大空闲连接数5个,每个连接最多保持5分钟。对于大多数应用来说,这个配置已经够用了。但如果你有特殊需求,可以调整:

OkHttpClient client = new OkHttpClient.Builder()
        .connectionPool(new ConnectionPool(
                10,          // 最大空闲连接数
                10,          // 保持存活时间
                TimeUnit.MINUTES
        ))
        .build();

为什么需要调整?我举个例子。如果你做的是一个直播类App,频繁的短连接请求,默认的5个空闲连接可能不够用。这时候适当增大连接池,能明显减少连接建立的时间。

核心要点:

  • 连接池只复用空闲连接,正在使用的连接不会被回收
  • 保持存活时间不宜过长,否则会占用系统资源
  • 如果App主要做长连接(如WebSocket),连接池的作用不大

五、知识体系总览

下面这张图是我自己整理的OkHttp核心知识结构,你可以对照着回顾一下:

OkHttp核心 基本使用 拦截器 缓存策略 连接池配置 同步请求 异步请求 应用拦截器 网络拦截器 服务端控制 客户端控制 最大连接数 存活时间

六、避坑指南与最佳实践

最后,分享几个我实际项目中的经验:

  • 不要在主线程执行同步请求——这会导致ANR,我见过太多新手犯这个错了。
  • 合理设置超时时间——连接超时建议5-10秒,读取超时建议10-30秒。太短容易误判,太长浪费用户时间。
  • 缓存目录要放在私有目录——不要放在SD卡根目录,否则容易被用户清理掉。
  • 连接池大小不是越大越好——每个空闲连接都会占用内存和文件描述符,默认5个已经够用。
我曾经踩过的一个大坑:在拦截器里修改了请求的URL,但没有同步修改Host头。结果服务端返回了404,排查了半天才发现是Host和URL不匹配。所以,如果你在拦截器里动了URL,记得同步更新Host。

OkHttp虽然功能强大,但核心就这些。你只要掌握了基本使用、拦截器、缓存和连接池,就能应对绝大多数场景了。剩下的,就是在实战中慢慢积累经验了。


公众号:蓝海资料掘金营,微信deep3321