在 PC 互联网时代,网络优化已经是一项非常复杂的工作。对于移动网络来说,弱网络、网络切换、网络劫持这些问题更加突出,网络优化这项工作也变得更加艰巨。

本文主要记载

  • 1 网络库实现哪家强?
  • 2 网络需要优化哪些?
    • 2.1 HTTPDNS
    • 2.2 链接复用
    • 2.3 压缩与加密
    • 2.4 其他优化
  • 3 如何监控网络
  • 4 PLT HOOK Demo 练习

1 网络库实现哪家强?

在实际的开发工作中,我们很少会像《UNIX 网络编程》那样直接去操作底层的网络接口,一般都会使用网络库。Square 出品的 OkHttp 是目前最流行的 Android 网络库,它还被 Google 加入到 Android 系统内部,为广大开发者提供网络服务。

网络库的作用是屏蔽了下层复杂的网络接口,让我们可以更高效地使用网络请求。主要有以下三点:

  • 统一编程接口:无论是同步还是异步请求,接口都非常简单易用。同时我们可以统一做策略管理,统一进行流解析(JSON、XML、Protocol Buffers)等。
  • 全局网络控制:在网络库内部我们可以做统一的网络调度、流量监控以及容灾管理等工作。
  • 高性能:既然我们把所有的网络请求都交给了网络库,那网络库是否实现高性能就至关重要。既然要实现高性能,那我会非常关注速度,CPU、内存、I/O 的使用,以及失败率、崩溃率、协议的兼容性等方面。

接下来对比 OkHttp、Chromium 的 Cronet 以及微信 Mars 这三个网络库的内部实现。

蘑菇街 、头条、UC 浏览器、微信 Mars 都在 Chromium 网络库上做了二次开发,而微信 Mars 在弱网络方面做了大量优化,拼多多、虎牙、链家、美丽说这些应用都在使用 Mars。

为什么那么多大厂都在用Chromium 的 Cronet 做二次开发,而不用 OkHTTP 呢?主要因为它并不支持跨平台,对于大型应用来说跨平台是非常重要的。我们不希望所有的优化 Android 和 iOS 都要各自去实现一套,不仅浪费人力而且还容易出问题。对于 Mars 来说,它是一个跨平台的 Socket 层解决方案,并不支持完整的 HTTP 协议,所以 Mars 从严格意义上来讲并不是一个完整的网络库。但是它在弱网络和连接上做了大量的优化,并且支持长连接。

Chromium 网络库作为标准的网络库,基本上可以说是找不到太大的缺点。而且我们可以享受 Google 后续网络优化的成果,类似 TLS 1.3、QUIC 支持等。但是它针对弱网络场景没有做太多定制的优化,也不支持长连接。

2 网络需要优化哪些?

在讲怎么去优化网络之前,先明确一下所谓的网络优化,究竟指的是什么?

  • 速度:在网络正常或者良好的时候,怎样更好地利用带宽,进一步提升网络请求速度。
  • 弱网络:移动端网络复杂多变,在出现网络连接不稳定的时候,怎样最大程度保证网络的连通性。
  • 安全:网络安全不容忽视,怎样有效防止被第三方劫持、窃听甚至篡改。

网络请求的整个过程

图片来源于 Android 开发高手课

  • DNS 解析:通过 DNS 服务器,拿到对应域名的 IP 地址。在这个步骤,我们比较关注 DNS 解析耗时情况、运营商 LocalDNS 的劫持、DNS 调度这些问题。
  • 创建连接:跟服务器建立连接,这里包括 TCP 三次握手、TLS 密钥协商等工作。多个 IP/ 端口该如何选择、是否要使用 HTTPS、能否可以减少甚至省下创建连接的时间,这些问题都是我们优化的关键。
  • 发送 / 接收数据:在成功建立连接之后,就可以愉快地跟服务器交互,进行组装数据、发送数据、接收数据、解析数据。我们关注的是,如何根据网络状况将带宽利用好,怎么样快速地侦测到网络延时,在弱网络下如何调整包大小等问题。
  • 关闭连接:连接的关闭看起来非常简单,其实这里的水也很深。这里主要关注主动关闭和被动关闭两种情况,一般我们都希望客户端可以主动关闭连接。

🔥🔥 所谓的网络优化,就是围绕速度、弱网络、安全这三个核心内容,减少每一个步骤的耗时,打造快速、稳定且安全的高质量网络。

2.1 HTTPDNS

DNS 的解析是网络请求的第一项工作,默认使用运营商的 LocalDNS 服务。这块耗时在 3G 网络下可能是 200~300ms,4G 网络也需要 100ms。

解析慢并不是默认 LocalDNS 最大的 “原罪” ,它还存在一些其他问题:

  • 稳定性:UDP 协议,无状态,容易域名劫持(难复现、难定位、难解决),每天至少几百万个域名被劫持,一年至少十次大规模事件。
  • 准确性:LocalDNS 调度经常出现不准确,比如北京的用户调度到广东 IP,移动的运营商调度到电信的 IP,跨运营商调度会导致访问慢,甚至访问不了。
  • 及时性:运营商可能会修改 DNS 的 TTL,导致 DNS 修改生效延迟。不同运营商的服务实现不一致,我们也很难保证 DNS 解析的耗时。

为了解决这些问题,就有了 HTTPDNS。简单来说自己做域名解析的工作,通过 HTTP 请求后台去拿到域名对应的 IP 地址,直接解决上述所有问题。

《百度App网络深度优化系列《一》DNS优化》

阿里 HTTPDNS

新浪 HTTPDNS库 | Android端HttpDNS优化方案 | Android OkHttp实现HttpDns的最佳实践(非拦截器)

2.2 连接复用

利用 HTTP 协议里的 keep-alive,而 HTTP/2.0 的多路复用则可以进一步的提升连接复用率。它复用的这条连接支持同时处理多条请求,所有请求都可以并发在这条连接上进行。

虽然 H2 十分强大,不过这里还有两个问题需要解决。

  • 一个是同一条 H2 连接只支持同一个域名
  • 一个是后端支持 HTTP/2.0 需要额外的改造

这个时候我们只需要在统一接入层(传说中的中台?)做改造,接入层将数据转换到 HTTP/1.1 再转发到对应域名的服务器。

图片来源于 Android 开发高手课

这样所有的服务都不用做任何改造就可以享受 HTTP/2.0 的所有优化,不过这里需要注意的是 H2 的多路复用在本质上依然是同一条 TCP连接,如果所有的域名的请求都集中在某一条连接中,在网络拥塞的时候容易出现 TCP 队首阻塞问题。

2.3 压缩与加密

2.3.1 压缩

首先对于 HTTP 请求来说,数据主要包括三个部分:请求 URL、请求 header、请求 body。

对于请求 body 来说,一方面是数据通信协议的选择,在网络传输中目前最流行的两种数据序列化方式是 JSON 和 Protocol Buffers。Protocol Buffers 使用起来更加复杂一些,但在数据压缩率、序列化与反序列化速度上面都有很大的优势。

另外一方面是压缩算法的选择,通用的压缩算法主要是如 gzip,Google 的 Brotli 或者 Facebook 的 Z-standard 等算法,用来减少流量消耗和传输时间。

针对图片我们可以使用 webp、hevc、SharpP 等压缩率更高的格式。

关于Android okHttp Gzip的简单处理

OkHttp使用gzip时的坑 Gzip 手动解压

还有就是 gzip 拦截器要添加到签名等操作拦截器之后,我们网络库在上传前需要使用 body 生成签名,如果在生成签名之前先使用 gzip 压缩了 body 体会什么也拿不到。

2.3.2 安全

数据安全也是网络重中之重的一个环节,在大网络平台中我们都是基于 HTTPS 的 HTTP/2 通道,已经有了 TLS 加密。

《TLS 协议分析》

但是 HTTPS 带来的代价也是不小的,它需要 2-RTT 的协商成本,在弱网络下时延不可接受。同时后台服务解密的成本也十分高昂,在大型企业中需要单独的集群来做这个事情。

HTTPS 的优化有下面几个思路:

  • 连接复用率。通过多个域名共用同一个 HTTP/2 连接、长连接等方式提升连接复用率。
  • 减少握手次数。 TLS 1.3 可以实现 0-RTT 协商,事实上在 TLS 1.3 release 之前,微信的 mmtls、Facebook 的fizz、阿里的 SlightSSL 都已在企业内部大规模部署。
  • 性能提升。使用 ecc 证书代替 RSA,服务端签名的性能可以提升 4~10 倍,但是客户端校验性能降低了约 20 倍,从 10 微秒级降低到 100 微秒级。另外一方面可以通过 Session Ticket 会话复用,节省一个 RTT 耗时。

使用 HTTPS 之后,整个通道是不是就一定高枕无忧呢?如果客户端设置了代理,TLS 加密的数据可以被解开并可能被利用 。这个时候我们可以在客户端将 证书锁定(Certificate Pinning),为了老版本兼容和证书替换的灵活性,建议锁定根证书。

我们也可以对传输内容做二次加密,这块在统一接入层实现,业务服务器也同样无需关心这个流程。需要注意的是二次加密会增加客户端与服务器的处理耗时,我们需要在安全性与性能之间做一个取舍。

图片来源于 Android 开发高手课

2.4 其他优化

这个两个在直播场景经常使用

关于网络优化的手段还有很多,一些方案可能是需要用钱堆出来的,比如部署跨国的专线、加速点,多 IDC 就近接入等。

  • QUIC

    QUIC 协议由 Google 在 2013 年实现,在 2018 年基于 QUIC 协议的 HTTP 更被确认为 HTTP/3 。在连接复用中说过 HTTP/2 + TCP 会存在队首阻塞的问题,基于 UDP 的 QUIC 才是终极解决方案。

    QUIC 简单理解为 HTTP/2.0 + TLS 1.3 + UDP

    图片来源于 Android 开发高手课

3 如何监控网络?

3.1 NetWork Profiler

利用网络性能剖析器检查网络流量

涨姿势:利用AndroidStudio自带的Network Profiler来查看网络请求的相关数据

3.2 Charles

抓包工具Charles的使用教程

Charles 使用教程

3.3 插桩

360 开源的性能监控工具 ArgusAPM 就是利用 Aspect 切换插桩,实现监控系统和 OkHttp 网络库的请求。

系统网络库的插桩实现可以参考 TraceNetTrafficMonitor,主要利用 Aspect 的切面功能。OkHttp 的拦截可以参考OkHttp3Aspect,它会更加简单一些,因为 OkHttp 本身就有代理机制。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Pointcut("call(public okhttp3.OkHttpClient build())")
public void build() {
}

@Around("build()")
public Object aroundBuild(ProceedingJoinPoint joinPoint) throws Throwable {
Object target = joinPoint.getTarget();
if (target instanceof OkHttpClient.Builder && Client.isTaskRunning(ApmTask.TASK_NET)) {
OkHttpClient.Builder builder = (OkHttpClient.Builder) target;
builder.addInterceptor(new NetWorkInterceptor());
}
return joinPoint.proceed();
}

3.4 Native Hook

网络相关的一般会 Hook 下面几个方法 :

  • 连接相关:connect。
  • 发送数据相关:send 和 sendto。
  • 接收数据相关:recv 和 recvfrom。

具体 Demo 见 4 PLT HOOK Demo 练习

4 PLT HOOK Demo 练习

face book plthooks

Chapter17

它的作用是不仅仅 hook 某个 so ,而是 hook 内存中的所有 so 。但是要排除掉方法本身定义的 so ,不然运行期间会出问题。

Demo 中比较疑惑的点 ,hook_plt_method_all_lib 的第一个参数是要排除掉的 so 库。还在研究中。。。

通过 connect 函数的 hook ,我们可以做的东西很多,例如:

  • 禁用应用网络访问
  • 过滤广告 ip
  • 禁用定位功能

感谢

极客时间 Android开发高手课

以及上文中的链接

更多理论和基础知识

深入探索 Android 网络优化(二、网络优化基础篇)上

深入探索 Android 网络优化(二、网络优化基础篇)下

深入探索 Android 网络优化(三、网络优化篇)上

深入探索 Android 网络优化(三、网络优化篇)下