网络优化:HTTP/2与连接池、OkHttp拦截器链、DNS优化、数据压缩、弱网环境适配

大家好,我是你们的老朋友。今天我们来聊聊网络优化。说实话,网络这块水挺深的。我见过太多App,功能做得不错,但一进页面就转圈圈,用户早跑了。网络优化,说白了就是让数据更快、更稳地到达用户手里。

我个人习惯把网络优化分成五个维度:协议层面、连接复用、请求拦截、数据压缩、弱网适配。咱们一个一个来拆解。

核心观点:网络优化的本质是减少等待、减少传输、减少失败。

1. HTTP/2 与连接池:别再让连接排队了

先问大家一个问题:HTTP/1.1 有什么毛病?

嗯,最大的问题就是队头阻塞。一个连接上只能串行发请求,前一个没回来,后面的就得等着。虽然可以开多个连接(通常是6个),但每个连接都要三次握手、TLS握手,成本很高。

HTTP/2 解决了这个问题。它引入了多路复用,一个连接上可以同时跑多个请求。说白了,就是一条高速公路上可以并排跑很多辆车,不用一辆一辆排队了。

我在项目中遇到过这样一个场景:一个页面需要同时加载10张小图,用HTTP/1.1的时候,浏览器最多开6个连接,剩下的4张得等。换成HTTP/2之后,所有图片几乎同时到达,加载速度提升了40%以上。

但光有HTTP/2还不够,你还得配合连接池。OkHttp默认就支持连接池,它会复用已经建立的连接,避免重复握手。

// OkHttp 连接池配置示例
ConnectionPool connectionPool = new ConnectionPool(
    5,          // 最大空闲连接数
    5,          // 保持存活时间
    TimeUnit.MINUTES
);

OkHttpClient client = new OkHttpClient.Builder()
    .connectionPool(connectionPool)
    .protocols(Arrays.asList(Protocol.HTTP_2, Protocol.HTTP_1_1))
    .build();
小提示:连接池不是越大越好。我见过有人设了50个空闲连接,结果内存占用飙升。一般5-10个就够用了。

2. OkHttp 拦截器链:请求的"流水线"

OkHttp 的拦截器链,说白了就是一个请求处理流水线。每个拦截器负责一件事,比如加Header、打日志、重试、缓存等。

拦截器分两种:应用拦截器网络拦截器。应用拦截器在请求发出前和响应回来后各执行一次,网络拦截器在真正的网络请求前后执行。

我个人习惯用网络拦截器来做重试和日志。为什么呢?因为网络拦截器能拿到真正的网络请求信息,比如IP地址、TLS版本等。

// 自定义重试拦截器
class RetryInterceptor implements Interceptor {
    private static final int MAX_RETRY = 3;

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response = null;
        IOException lastException = null;

        for (int i = 0; i < MAX_RETRY; i++) {
            try {
                response = chain.proceed(request);
                if (response.isSuccessful()) {
                    return response;
                }
            } catch (IOException e) {
                lastException = e;
                // 等待一段时间再重试
                try {
                    Thread.sleep(1000 * (i + 1));
                } catch (InterruptedException ignored) {}
            }
        }

        throw lastException != null ? lastException : new IOException("重试失败");
    }
}
注意:重试要谨慎。如果是POST请求,重试可能导致重复提交。我建议只对GET请求做自动重试。

3. DNS 优化:别让域名解析拖后腿

DNS解析,很多人不重视。但你想想看,每次请求都要先查一下域名对应的IP,如果DNS服务器慢,或者被劫持了,那体验就很糟糕了。

我曾经遇到过一个问题:某个地区的用户反馈App加载慢,查了半天发现是当地DNS服务器响应慢,每次解析要2-3秒。后来我们做了DNS预解析HTTPDNS,问题就解决了。

DNS预解析很简单,就是在App启动时提前把常用域名的IP解析好,缓存起来。

// OkHttp 自定义 DNS 解析
class CustomDns implements Dns {
    private final Map<String, List<InetAddress>> cache = new ConcurrentHashMap<>();

    @Override
    public List<InetAddress> lookup(String hostname) throws UnknownHostException {
        List<InetAddress> cached = cache.get(hostname);
        if (cached != null) {
            return cached;
        }

        // 使用 HTTPDNS 或自定义解析
        List<InetAddress> addresses = Dns.SYSTEM.lookup(hostname);
        cache.put(hostname, addresses);
        return addresses;
    }
}

HTTPDNS 就更高级了,它直接用HTTP请求去获取IP,绕过了本地DNS,能有效避免DNS劫持。像阿里云、腾讯云都有现成的HTTPDNS服务。

4. 数据压缩:让传输更轻量

数据压缩,说白了就是让要传输的数据变小。常用的方式有两种:GzipProtobuf

Gzip 是文本压缩的利器。JSON 数据经过 Gzip 压缩后,体积能减少 60%-80%。OkHttp 默认就支持 Gzip 解压,你只需要在请求头里加上 Accept-Encoding: gzip 就行。

但 Gzip 有个问题:它是在传输层面压缩的,数据到了客户端还得解压。如果数据量特别大,解压也会消耗CPU。

Protobuf 就不一样了。它是结构化数据序列化的格式,比 JSON 更小、更快。我做过对比:同样的数据,JSON 是 1KB,Protobuf 只有 300B 左右。

// Protobuf 定义示例
syntax = "proto3";

message User {
    int32 id = 1;
    string name = 2;
    string email = 3;
}

// 序列化
User user = User.newBuilder()
    .setId(1001)
    .setName("张三")
    .setEmail("zhangsan@example.com")
    .build();

byte[] data = user.toByteArray();  // 只有几十个字节
我的建议:如果你们的接口返回的是纯文本数据,用 Gzip 就够了。如果数据量大、结构复杂,或者对性能要求极高,可以考虑 Protobuf。

5. 弱网环境适配:别让用户干等

弱网环境,是每个Android开发者都绕不开的坎。地铁里、电梯里、偏远地区,网络质量都很差。这时候如果App还傻傻地等请求回来,用户早就关掉了。

我总结了几条弱网适配的经验:

  • 超时设置要合理:默认的超时时间往往太长。我一般设成 10秒连接超时、15秒读取超时。
  • 使用缓存:能缓存的数据尽量缓存,弱网时直接展示缓存内容。
  • 请求合并:把多个小请求合并成一个,减少连接次数。
  • 图片降级:弱网时加载低分辨率图片,甚至只加载文字。
// 弱网环境下的超时配置
OkHttpClient client = new OkHttpClient.Builder()
    .connectTimeout(10, TimeUnit.SECONDS)   // 连接超时
    .readTimeout(15, TimeUnit.SECONDS)      // 读取超时
    .writeTimeout(10, TimeUnit.SECONDS)     // 写入超时
    .retryOnConnectionFailure(true)         // 连接失败重试
    .build();

还有一个技巧:检测网络状态。当网络从WiFi切到移动数据,或者信号变弱时,主动调整请求策略。

// 网络状态监听
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkCallback callback = new NetworkCallback() {
    @Override
    public void onAvailable(Network network) {
        // 网络恢复,正常请求
    }

    @Override
    public void onLost(Network network) {
        // 网络断开,停止请求
    }

    @Override
    public void onCapabilitiesChanged(Network network, NetworkCapabilities capabilities) {
        // 网络质量变化,调整策略
        boolean isMetered = !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
        if (isMetered) {
            // 移动网络,降低图片质量
        }
    }
};
cm.registerNetworkCallback(new NetworkRequest.Builder().build(), callback);

知识体系总览

下面这张图,是我整理的网络优化知识体系。你可以把它当成一个检查清单,看看自己还有哪些没做到位。

网络优化知识体系 网络优化 HTTP/2 与连接池 OkHttp 拦截器链 DNS 优化 数据压缩 弱网环境适配 多路复用 连接复用 应用拦截器 网络拦截器 DNS预解析 HTTPDNS Gzip Protobuf 超时设置 缓存策略 请求合并 减少等待 → 减少传输 → 减少失败

好了,网络优化这块就聊到这儿。记住一句话:网络优化不是一蹴而就的,而是一个持续迭代的过程。每次上线前,都问问自己:弱网下能跑吗?数据够小吗?连接够快吗?

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