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

本文主要记载

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

1 网络库实现哪家强?

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

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

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

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

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

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

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

2 网络需要优化哪些?

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

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

网络请求的整个过程

图片来源于 Android 开发高手课

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

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

2.1 HTTPDNS

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

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

  • 稳定性:UDP 协议,无状态,容易域名劫持(难复现、难定位、难解决),每天至少几百万个域名被劫持,一年至少十次大规模事件。
  • 准确性:LocalDNS 调度经常出现不准确,比如北京的用户调度到广东 IP,移动的运营商调度到电信的 IP,跨运营商调度会导致访问慢,甚至访问不了。
  • 及时性:运营商可能会修改 DNSTTL,导致 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 来说,一方面是数据通信协议的选择,在网络传输中目前最流行的两种数据序列化方式是 JSONProtocol BuffersProtocol Buffers 使用起来更加复杂一些,但在数据压缩率、序列化与反序列化速度上面都有很大的优势。

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

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

关于Android okHttp Gzip的简单处理

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

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

2.3.2 安全

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

《TLS 协议分析》

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

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

  • 连接复用率。通过多个域名共用同一个 HTTP/2 连接、长连接等方式提升连接复用率。
  • 减少握手次数。 TLS 1.3 可以实现 0-RTT 协商,事实上在 TLS 1.3 release 之前,微信的 mmtlsFacebookfizz、阿里的 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 会存在队首阻塞的问题,基于 UDPQUIC 才是终极解决方案。

    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
  • 发送数据相关:sendsendto
  • 接收数据相关:recvrecvfrom

具体 Demo4 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 网络优化(三、网络优化篇)下