10、网络优化(下):OkHttp拦截器链、Retrofit缓存策略、弱网环境优化、预连接技术
网络优化这个话题,咱们上回聊了基础层面。今天接着往下挖,聊聊那些真正能让App在网络层面“飞起来”的硬核技术。说白了,就是OkHttp的拦截器链怎么玩、Retrofit的缓存怎么配、弱网下怎么保命、以及预连接这种“预判”技术。
我个人习惯把网络优化分成两派:一派是“防守型”,比如重试、降级、缓存;另一派是“进攻型”,比如预连接、预加载。今天咱们攻守兼备,全盘拿下。
一、OkHttp拦截器链:你以为是排队,其实是套娃
OkHttp的拦截器链,很多人刚接触时觉得就是“请求进来,一层层过,最后出去”。其实没那么简单。它分两种:应用拦截器和网络拦截器。区别在哪?我直接说结论:应用拦截器只调用一次,网络拦截器可能被调用多次(比如重定向或重试时)。
举个例子,你加了一个日志拦截器。如果放在应用拦截器里,你只会看到一次请求日志;如果放在网络拦截器里,重试三次你就看到三条日志。嗯,这里要注意,别被日志数量吓到。
- 应用拦截器(addInterceptor):在请求发出前、响应返回后各执行一次。适合加公共Header、打印日志、统计流量。
- 网络拦截器(addNetworkInterceptor):在每次网络请求前后执行。适合处理重定向、重试、缓存策略。
我在项目中遇到过一个问题:有个同事把Token刷新逻辑放在了网络拦截器里,结果一次请求因为重定向触发了三次Token刷新,直接把服务器打挂了。你想想看,这得多冤?所以我的建议是:业务逻辑放应用拦截器,网络层面的操作放网络拦截器。
// 应用拦截器示例:统一添加Token
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(chain -> {
Request original = chain.request();
Request newRequest = original.newBuilder()
.header("Authorization", "Bearer " + getToken())
.build();
return chain.proceed(newRequest);
})
.addNetworkInterceptor(chain -> {
// 这里只做网络层面的监控
Request request = chain.request();
Response response = chain.proceed(request);
// 记录网络耗时
return response;
})
.build();
二、Retrofit缓存策略:别让服务器背锅
Retrofit本身不负责缓存,它只是OkHttp的封装。缓存策略其实是在OkHttp层面配的。但很多人只配了OkHttp的缓存,却忘了在Retrofit的接口注解里加Cache-Control。结果呢?缓存没生效,还怪服务器。
我习惯的做法是:服务器端和客户端双管齐下。服务器返回的响应头里带上Cache-Control,客户端再根据业务需求覆盖或补充。
| 缓存策略 | 适用场景 | 注意事项 |
|---|---|---|
| no-cache | 每次都要验证,但可以用缓存 | 配合ETag或Last-Modified使用 |
| no-store | 敏感数据,完全不缓存 | 比如支付Token、验证码 |
| max-age=3600 | 一小时内的数据可复用 | 适合列表页、配置信息 |
| only-if-cached | 离线模式,只用缓存 | 配合离线策略使用 |
我曾经接手过一个项目,用户反馈“列表数据一直不更新”。查了半天,发现是OkHttp的缓存目录没配权限,缓存根本写不进去。嗯,这种坑,你不踩一次真的想不到。
// 配置OkHttp缓存
int cacheSize = 10 * 1024 * 1024; // 10MB
Cache cache = new Cache(cacheDir, cacheSize);
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.addInterceptor(chain -> {
Request request = chain.request();
// 弱网环境下强制使用缓存
if (isWeakNetwork()) {
request = request.newBuilder()
.header("Cache-Control", "only-if-cached")
.build();
}
return chain.proceed(request);
})
.build();
三、弱网环境优化:别让用户干等
弱网优化,说白了就是“在信号不好的时候,怎么让App不死、不卡、不崩溃”。我见过太多App在弱网下直接白屏、ANR、甚至闪退。用户本来信号就不好,你还给他添堵?
我个人习惯从三个层面入手:超时控制、重试策略、降级方案。
超时控制:别用默认的超时时间。OkHttp默认10秒,但弱网下10秒可能根本不够。我一般设成30秒,但配合“快速失败”机制——如果前两次重试都超时,第三次直接放弃。
重试策略:不要无脑重试。我见过一个App在弱网下重试了8次,每次间隔1秒,结果用户等了8秒,最后还失败了。我的做法是:指数退避 + 最大重试次数。第一次等1秒,第二次等2秒,第三次等4秒,最多重试3次。
// 自定义重试拦截器
public class RetryInterceptor implements Interceptor {
private int maxRetry = 3;
private long baseDelay = 1000; // 1秒
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = null;
IOException lastException = null;
for (int i = 0; i <= maxRetry; i++) {
try {
response = chain.proceed(request);
// 如果成功,直接返回
if (response.isSuccessful()) {
return response;
}
} catch (IOException e) {
lastException = e;
// 指数退避
long delay = baseDelay * (long) Math.pow(2, i);
try {
Thread.sleep(delay);
} catch (InterruptedException ignored) {}
}
}
throw lastException != null ? lastException : new IOException("重试失败");
}
}
降级方案:这是最关键的。弱网下,别想着“完美体验”,先保证“能用”。比如图片加载失败就显示占位图,列表加载失败就显示缓存数据,接口超时就提示“网络不给力,请稍后再试”。
四、预连接技术:提前握手,减少等待
预连接,说白了就是“在用户真正需要网络请求之前,先把TCP连接建好”。你想想看,每次HTTP请求都要经历DNS解析、TCP三次握手、TLS握手(如果是HTTPS),这些时间加起来可能几百毫秒。如果能在用户点击按钮之前就完成这些,那体验不就丝滑了吗?
OkHttp内部有一个ConnectionPool,默认最多保持5个空闲连接,每个连接最多存活5分钟。但这是“被动”的——只有请求发出去才会创建连接。预连接要做的是“主动”创建。
我常用的做法有两种:
- 启动时预连接:App启动后,立即向核心API域名发起一个“空请求”(比如一个轻量级的配置接口),这样连接池里就有了热连接。
- 空闲时预连接:在用户浏览当前页面时,提前预判他下一步可能访问的域名,提前建立连接。
// 预连接示例:启动时预热
public class NetworkPreWarm {
public static void warmUp(OkHttpClient client, String url) {
Request request = new Request.Builder()
.url(url)
.header("Connection", "keep-alive")
.build();
// 异步发起一个轻量请求,只为了建立连接
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 预连接失败不影响主流程
}
@Override
public void onResponse(Call call, Response response) {
// 关闭响应,但连接会留在连接池中
response.close();
}
});
}
}
- 不要预连接太多域名,否则会占用系统资源。我一般控制在3-5个核心域名。
- 预连接的请求一定要轻量,最好是一个空GET请求或者HEAD请求。
- 如果用户长时间没有后续请求,连接会被回收。所以预连接的时间窗口很重要——我一般在用户进入页面后的2秒内完成预连接。
我记得有一次优化一个电商App的首页加载速度。首页有6个接口,每次打开都要等所有接口返回。我做了两件事:一是把其中3个不关键的接口改成懒加载;二是对核心的2个接口做预连接。结果首页加载时间从2.8秒降到了1.2秒。你想想看,这提升多明显?
好了,网络优化这块的内容就这些。核心思路就是:能缓存就缓存,能预连就预连,弱网下别硬撑,该降级就降级。把这些技术用好,你的App在网络层面基本就稳了。