一个示例

前后分别去恳求同一个域名下的接口,经过 Charles 抓包,能够看到 Timing 下面的时刻:

  • 第2次恳求时,DNS、Connect 和 TLS Handshake 部分都是 -,阐明没有这部分的耗时,比照第一次恳求的这三个部分,节约了 1 + 35 + 97 = 133ms
  • 当然第一次恳求的 Request 和 Response 的 Size 比第二非必须大一丢丢,且 Speed 低一些,疏忽这些差异,在其他条件都共同的情况下,第2次恳求比第一次恳求能快 133ms。
第一次 第2次
Android优化篇|网络预连接
Android优化篇|网络预连接

这便是 http(s) 的衔接复用。

衔接复用

在此之前先简略温习一下发起网络 Request ->收到 Response 的粗略过程:

  1. 客户端发起网络恳求
  2. 经过 DNS 服务解析域名,获取 IP 地址(一般是 UDP 协议)
  3. 树立 TCP 衔接(三次握手)
  4. 树立 TLS 衔接(Https)
  5. 发送网络恳求 Request
  6. 服务器接收 Request 到后,履行逻辑并回来 Response
  7. 封闭 TCP 衔接(四次挥手)

经过衔接复用,上面的 2、3、4 步都不需求从头走了。运用 RTT 来界说这个时长,RTT(Round-Trip Time, 往复时刻) 是网络恳求从起点到目的地然后再回到起点所花费的时长。那么节约的时刻是:

  • DNS 一般运用 UDP 协议,最近从头温习了下 DNS 的内容,假如 DNS 呼应报文的长度大于 512 字节,则会运用 TCP 协议。事实上,很多 DNS 服务器进行配置时,也仅支撑 UDP。因此这一步能够看成节约了 1 个 RTT。
  • 树立 TCP 衔接,三次握手,需求 2 个 RTT。
  • 树立 TLS 衔接,依据 TLS 版别,有不同的 RTT。

HTTP1.1 版别开始默认便是耐久衔接,能够复用,经过在报文头部加上 Connection:Close 来封闭衔接。别的空闲的耐久衔接也能够随时被封闭,即便不发送 Connection:Close,也不意味着服务器衔接永久保持翻开。

预衔接

常用的网络框架如 OkHttp 等,都支撑 HTTP1.1 和 HTTP2 的功能,那也支撑衔接复用。

咱们能够运用这个机制来做一个预衔接,比如说在 APP 闪屏等候时,预先树立起要害页面域名的衔接,这样在用户进入相应页面后能够更快的获取到网络恳求成果,提高用户体会。

能够简略的对域名链接提前发起一个 HEAD 恳求(没有Body),这样就能提前树立好衔接,下次同域名的恳求能够直接复用。

private val client by lazy { OkHttpClient() }
btn.setOnClickListener {
    // 正式恳求
    launch(Dispatchers.IO) {
        request()
    }
}
// 预衔接
launch(Dispatchers.IO) {
    preRequest()
}
fun preRequest() {
    val request = Request.Builder()
        .head()
        .url("xxx")
        .build()
    client.newCall(request).execute()
}
fun request() {
    val request = Request.Builder()
        .get()
        .url("xxx/yyy")
        .build()
    client.newCall(request).execute()
}

能够抓包看到首次进入时发送的 head 恳求和实践上点击发送的 get 恳求:

预衔接 正式恳求
Android优化篇|网络预连接
Android优化篇|网络预连接

能够看到正式恳求时,的确少了上述三个步骤的耗时。还能够分别看下 Connection 和 TLS 的信息:

预衔接 正式恳求
Android优化篇|网络预连接
Android优化篇|网络预连接

能看出来正式恳求时,这俩是复用的(重视 TLS 的 Session Resumed 和 Connection 的 Server Connection 部分)。

别的 OkHttp 中有个 ConnectionPool 衔接池,在运用 Connect 衔接时,会优先复用已有的衔接,无可用时才创建新衔接。衔接池里包容的衔接数是限制的(貌似默认是 5 个),假如事务比较复杂,恳求比较多的话,可能会导致衔接池占满,这样就会开释之前做好的预衔接。因此一个比较简略的方式便是适当调大衔接池的容量和超时时刻。

总结

经过 http(s) 的衔接复用机制,咱们能够考虑运用预衔接来优化 APP 中某些场景的网络恳求速度,这需求咱们依据实践事务场景以及服务器压力来判别是否进行预衔接。

别的咱们能够适当调大衔接池的容量和超时时刻,由于衔接是双向的,即便客户端把 Connection 一直保留,服务端也会依据实践衔接数量和时长来自动封闭衔接的,所以调大衔接池一般不会增大服务器压力。

预衔接的效果实践和服务器配置有关,假如服务器把衔接超时设置得很小,那每次恳求可能都需求从头树立衔接,这样客户端的预衔接会失效,且服务器也需求不断创建和毁掉 TCP 衔接而糟蹋更多资源;假如服务器把衔接超时设置得很大,那之前的衔接长时刻都不会开释,导致服务器服务的并发数受到影响,影响新的恳求。因此调优需求多端协调,综合考虑

Android视图体系:Android 视图体系相关的底层原理解析,看完定有收获。

Kotlin专栏:Kotlin 学习相关的博客,包含协程, Flow 等。

Android架构学习之路:架构不是一蹴即至的,希望咱们有一天的时候,能够从自己写的代码中找到架构的成就感,而不是干几票就跑路。作业太忙,更新比较慢,我们有兴趣的话能够一同学习。

Android实战系列:记载实践开发中遇到和处理的一些问题。

Android优化系列:记载Android优化相关的文章,持续更新。

文中内容如有过错欢迎指出,共同进步!更新不易,觉得不错的留个再走哈~