18、性能优化:连接池复用、数据压缩(Gzip)、减少数据包大小、使用Protocol Buffers

网络层的性能优化,说白了就是两件事:少发数据快发数据

我见过不少团队,接口调得飞起,但从来没想过每次请求背后有多少重复的TCP握手、多少冗余的JSON字段在浪费带宽。今天我们就来把这几个硬骨头啃掉。

连接池复用:别让TCP反复握手

每次HTTP请求都要经历三次握手、TLS协商,这开销可不小。尤其在高并发场景下,频繁创建和销毁连接,简直就是性能杀手。

核心思路:复用已经建立的连接,避免重复握手。

OkHttp默认就支持连接池复用,但很多人不知道它的默认配置其实很保守。我个人习惯根据业务场景调整参数:

val connectionPool = ConnectionPool(
    maxIdleConnections = 5,   // 最大空闲连接数
    keepAliveDuration = 5,    // 存活时间(分钟)
    timeUnit = TimeUnit.MINUTES
)

val okHttpClient = OkHttpClient.Builder()
    .connectionPool(connectionPool)
    .build()

这里有个坑,我曾经遇到过:某个直播App的弹幕服务,短连接请求特别频繁,默认的1分钟存活时间根本不够用,导致大量连接被回收重建。后来我把 keepAliveDuration 调到了5分钟,CPU占用率直接降了30%。

我的建议:如果你的App有大量短时请求(比如日志上报、心跳检测),适当增大连接池的存活时间。但别太大,否则空闲连接会占用系统资源。

数据压缩(Gzip):让数据瘦身

你想想看,一个JSON响应里可能90%都是重复的字段名。Gzip压缩就是专门对付这种冗余的。

OkHttp默认支持Gzip解压,但需要服务端配合。客户端这边,我们只需要在请求头里声明一下:

val client = OkHttpClient.Builder()
    .addInterceptor { chain ->
        val request = chain.request().newBuilder()
            .header("Accept-Encoding", "gzip")
            .build()
        chain.proceed(request)
    }
    .build()

等等,其实OkHttp 4.x版本已经自动添加了这个头。但有个细节要注意:如果服务端返回了压缩数据,OkHttp会自动解压,但会移除 Content-Length。这意味着你不能依赖 Content-Length 来判断数据大小。

避坑指南:我曾经接手过一个项目,他们自己实现了下载进度监听,依赖 Content-Length 计算百分比。结果开启Gzip后,进度条直接崩了。后来改用 response.body().contentLength() 才解决。

减少数据包大小:从源头优化

Gzip是后端压缩,但前端也能做很多事。我总结了几条实战经验:

  • 精简字段:别把整个对象序列化传过去。比如用户信息,只传id和name,别把头像URL、注册时间这些无关字段也带上。
  • 使用短字段名"user_name" 改成 "un""create_time" 改成 "ct"。虽然看着丑,但省带宽。
  • 分页控制:一次请求别拉太多数据。我习惯默认20条,用户滚动到底部再加载更多。
  • 增量更新:别每次都全量拉取。用 If-Modified-SinceETag 做条件请求。

一个小技巧:用 @SerializedName 注解给字段起别名。比如 @SerializedName("uid") 代替 userId,一个响应能省几十个字节。积少成多嘛。

使用Protocol Buffers:比JSON更狠的序列化方案

JSON虽然方便,但它的文本格式天生就有冗余。Protocol Buffers(简称Protobuf)是Google推出的二进制序列化协议,体积小、解析快。

先定义一个 .proto 文件:

syntax = "proto3";

message UserResponse {
    int32 id = 1;
    string name = 2;
    string email = 3;
    int32 age = 4;
}

然后在Android项目里集成Protobuf插件,生成对应的Java/Kotlin类。Retrofit也支持Protobuf:

// 添加依赖
implementation("com.squareup.retrofit2:converter-protobuf:2.9.0")

// 创建Retrofit实例
val retrofit = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .addConverterFactory(ProtoConverterFactory.create())
    .build()

使用起来和JSON几乎一样,但数据体积能减少60%-80%。

实际对比:我做过一个测试,同样的用户列表数据,JSON是2.3KB,Protobuf只有0.8KB。在弱网环境下,这个差距就是秒开和转圈的区别。

不过Protobuf也有缺点:调试不方便,不能直接看原始数据。我建议在开发阶段用JSON,上线前再切到Protobuf。或者用 @Headers("Accept: application/x-protobuf") 做动态切换。

知识体系总览

下面这张图帮你理清这四块内容的关系:

网络层性能优化 连接池复用 减少TCP握手 OkHttp默认支持 调整存活时间 Gzip压缩 服务端配合 自动解压 注意Content-Length 减少数据包大小 精简字段 短字段名 Protocol Buffers 二进制序列化 体积减少60%+

总结一下

这四个优化点,优先级我建议这样排:

  1. 连接池复用:零成本,收益大,必须做。
  2. Gzip压缩:服务端配合一下就行,几乎无侵入。
  3. 减少数据包大小:需要前后端对齐,但效果立竿见影。
  4. Protocol Buffers:适合对性能有极致要求的场景,比如直播、实时通信。

嗯,网络优化没有银弹。但把这四招用好了,你的App在网络层基本就稳了。


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