深刻探索 Android 網絡優化(3、網絡優化篇)下

前言

成爲一名優秀的Android開發,須要一份完備的知識體系,在這裏,讓咱們一塊兒成長爲本身所想的那樣~。

本文思惟導圖

歡迎來到 《深刻探索 Android 網絡優化(3、網絡優化篇)下》~css

5、網絡請求質量優化(🔥)

一、Http 請求過程

  • 1)、請求到達運營商的 DNS 服務器並* 解析* 成對應的 IP 地址。
    • HTTPDNS
  • 2)、根據 IP 地址找到相應的服務器,進行 TCP 三次握手, 建立鏈接
    • 鏈接複用
    • 網絡庫的鏈接管理
  • 3)、發送/接收數據。
    • 壓縮
    • 加密
  • 4)、關閉鏈接。

二、HTTPDNS

問題:DNS 解析慢/被劫持?html

使用 HTTPDSN,HTTPDNS 不是使用 DNS 協議,向 DNS 服務器傳統的 53 端口發送請求,而是使用 HTTP 協議向 DSN 服務器的 80 端口發送請求。java

1)、HTTPDNS 優點

  • 一、繞過運營商域名解析的過程,避免 Local DNS 的劫持。
  • 二、下降平均訪問時延,提供鏈接成功率。
  • 三、HTTPDNS 服務器會增長流量調度、網絡撥測/灰度、網絡容災等功能。

2)、HTTPDNS + OKHttp 實踐

在 Awesome-WanAndroid 中已經實現了 HTTPDNS 優化,其優化代碼以下所示:android

// HttpModule-provideClient:httpDns 優化
builder.dns(OkHttpDns.getIns(WanAndroidApp.getAppComponent().getContext()));  /**  * FileName: OkHttpDNS  * Date: 2020/5/8 16:08  * Description: HttpDns 優化  * @author JsonChao  */ public class OkHttpDns implements Dns {   private HttpDnsService dnsService;  private static OkHttpDns instance = null;   private OkHttpDns(Context context) {  dnsService = HttpDns.getService(context, "161133");  // 一、設置預解析的 IP 使用 Https 請求。  dnsService.setHTTPSRequestEnabled(true);  // 二、預先註冊要使用到的域名,以便 SDK 提早解析,減小後續解析域名時請求的時延。  ArrayList<String> hostList = new ArrayList<>(Arrays.asList("www.wanandroid.com"));  dnsService.setPreResolveHosts(hostList);  }   public static OkHttpDns getIns(Context context) {  if (instance == null) {  synchronized (OkHttpDns.class) {  if (instance == null) {  instance = new OkHttpDns(context);  }  }  }  return instance;  }   @Override  public List<InetAddress> lookup(String hostname) throws UnknownHostException {  String ip = dnsService.getIpByHostAsync(hostname);  LogHelper.i("httpDns: " + ip);  if(ip != null){  List<InetAddress> inetAddresses = Arrays.asList(InetAddress.getAllByName(ip));  return inetAddresses;  }  // 三、若是從阿里雲 DNS 服務器獲取不到 ip 地址,則走運營商域名解析的過程。  return Dns.SYSTEM.lookup(hostname);  } } 複製代碼

從新安裝 App,經過 HTTPDNS 獲取到 IP 地址 log 以下所示:git

2020-05-11 10:41:55.139 4036-4184/json.chao.com.wanandroid I/WanAndroid-LOG: │ [OkHttpDns.java | 52 | lookup] httpDns: 47.104.74.169
2020-05-11 10:41:55.142 4036-4185/json.chao.com.wanandroid I/WanAndroid-LOG: │ [OkHttpDns.java | 52 | lookup] httpDns: 47.104.74.169 複製代碼

三、網絡庫的鏈接管理

利用 HTTP 協議的 keep-alive,創建鏈接後,會先將鏈接放入鏈接池中,若是有另外一個請求的域名和端口是同樣的,就直接使用鏈接池中對應的鏈接發送和接收數據。在實現網絡庫的鏈接管理時須要注意如下4點:github

  • 1)、同一個鏈接僅支持同一個域名。
  • 2)、後端支持 HTTP 2.0 須要改造,這裏能夠經過在網絡平臺的統一接入層將數據轉換到 HTTP 1.1 後再轉發到對應域名的服務器便可。
  • 3)、當全部請求都集中在一條鏈接中時,在網絡擁塞時容易出現 TCP 隊首阻塞問題。
  • 4)、在文件下載、視頻播放等場景下可能會遇到三方服務器單鏈接限速的問題,此時能夠禁用 HTTP 2.0。

四、協議版本升級

HTTP 1.0

TCP 鏈接不復用,也就是每發起一個網絡請求都要從新創建鏈接,而剛開始鏈接都會經歷一個慢啓動的過程,可謂是慢上加慢,所以 HTTP 1.0 性能很是差。web

HTTP 1.1

引入了持久鏈接,即 TCP 鏈接能夠複用,但數據通訊必須按次序來,也就是後面的請求必須等前面的請求完成才能進行。當全部請求都集中在一條鏈接中時,在網絡擁塞時容易出現 TCP 隊首阻塞問題。算法

HTTP 2

  • 二進制協議
  • 多工
  • 服務端與客戶端能夠雙向實時通訊。

QUIC

Google 2013 實現,2018 基於 QUIC 協議的 HTTP 被確認爲 HTTP3。chrome

QUIC 簡單理解爲 HTTP/2.0 + TLS 1.3 + UDP。弱網環境下表現好與 TCP。數據庫

優點

  • 1)、解決了在鏈接複用中 HTTP2 + TCP 存在的隊首阻塞問題,
  • 2)、因爲是基於 UDP,因此能夠靈活控制擁塞協議。例如 Client 端能夠直接使用 Google 的 BBR 算法
  • 3)、鏈接遷:因爲 UDP 經過相似connection id 的特性,使得客戶端網絡切換的時候不須要重連,用戶使用 App 的體驗會更加流暢。

目前的缺點

  • 1)、NAT 局域網路由、交換機、防火牆等會禁止 UDP 443 通行,所以 QUIC 建立鏈接成功率只有95%。
  • 2)、運營商針對 UDP 通道不支持/支持不足。
  • 3)、使用 UDP 不必定會比 TCP 更快,客戶端可同時使用 TCP 和 QUIC 競速,從而選擇更優鏈路。

使用場景

  • 1)、實時性
  • 2)、可丟棄
  • 3)、請求互相依賴
  • 4)、可同時使用 TCP & QUIC

QUIC 加密協議原理

  • 1)、當 Client 與 Server 第一次通訊時,會發送 Inchoate Client Hello 消息下載 Server Config(SCFG) 暫存消息。
  • 2)、SCFG 中包含一個 Diffie-Hellman 共享,下一次 Client 將使用它派生初始密鑰(即 0-RTT 密鑰)並利用其加密數據給 Server。
  • 3)、以後,Server 將發出一個新的暫存 Diffie-Hellman 共享,並由此派生出一組 前向安全密鑰去進行數據的加密通訊。

五、網絡請求質量監控

1)、接口請求耗時、成功率、錯誤碼

在 Awesome-WanAndroid 中已經使用 OkHttpEventListener 實現了網絡請求的質量監控,其代碼以下所示:

// 網絡請求質量監控
builder.eventListenerFactory(OkHttpEventListener.FACTORY);  /**  * FileName: OkHttpEventListener  * Date: 2020/5/8 16:28  * Description: OkHttp 網絡請求質量監控  * @author quchao  */ public class OkHttpEventListener extends EventListener {   public static final Factory FACTORY = new Factory() {  @Override  public EventListener create(Call call) {  return new OkHttpEventListener();  }  };   OkHttpEvent okHttpEvent;  public OkHttpEventListener() {  super();  okHttpEvent = new OkHttpEvent();  }   @Override  public void callStart(Call call) {  super.callStart(call);  LogHelper.i("okHttp Call Start");  okHttpEvent.callStartTime = System.currentTimeMillis();  }   /**  * DNS 解析開始  *  * @param call  * @param domainName  */  @Override  public void dnsStart(Call call, String domainName) {  super.dnsStart(call, domainName);  okHttpEvent.dnsStartTime = System.currentTimeMillis();  }   /**  * DNS 解析結束  *  * @param call  * @param domainName  * @param inetAddressList  */  @Override  public void dnsEnd(Call call, String domainName, List<InetAddress> inetAddressList) {  super.dnsEnd(call, domainName, inetAddressList);  okHttpEvent.dnsEndTime = System.currentTimeMillis();  }   @Override  public void connectStart(Call call, InetSocketAddress inetSocketAddress, Proxy proxy) {  super.connectStart(call, inetSocketAddress, proxy);  okHttpEvent.connectStartTime = System.currentTimeMillis();  }   @Override  public void secureConnectStart(Call call) {  super.secureConnectStart(call);  okHttpEvent.secureConnectStart = System.currentTimeMillis();  }   @Override  public void secureConnectEnd(Call call, @Nullable Handshake handshake) {  super.secureConnectEnd(call, handshake);  okHttpEvent.secureConnectEnd = System.currentTimeMillis();  }   @Override  public void connectEnd(Call call, InetSocketAddress inetSocketAddress, Proxy proxy, @Nullable Protocol protocol) {  super.connectEnd(call, inetSocketAddress, proxy, protocol);  okHttpEvent.connectEndTime = System.currentTimeMillis();  }   @Override  public void connectFailed(Call call, InetSocketAddress inetSocketAddress, Proxy proxy, @Nullable Protocol protocol, IOException ioe) {  super.connectFailed(call, inetSocketAddress, proxy, protocol, ioe);  }   @Override  public void connectionAcquired(Call call, Connection connection) {  super.connectionAcquired(call, connection);  }   @Override  public void connectionReleased(Call call, Connection connection) {  super.connectionReleased(call, connection);  }   @Override  public void requestHeadersStart(Call call) {  super.requestHeadersStart(call);  }   @Override  public void requestHeadersEnd(Call call, Request request) {  super.requestHeadersEnd(call, request);  }   @Override  public void requestBodyStart(Call call) {  super.requestBodyStart(call);  }   @Override  public void requestBodyEnd(Call call, long byteCount) {  super.requestBodyEnd(call, byteCount);  }   @Override  public void responseHeadersStart(Call call) {  super.responseHeadersStart(call);  }   @Override  public void responseHeadersEnd(Call call, Response response) {  super.responseHeadersEnd(call, response);  }   @Override  public void responseBodyStart(Call call) {  super.responseBodyStart(call);  }   @Override  public void responseBodyEnd(Call call, long byteCount) {  super.responseBodyEnd(call, byteCount);  // 記錄響應體的大小  okHttpEvent.responseBodySize = byteCount;  }   @Override  public void callEnd(Call call) {  super.callEnd(call);  okHttpEvent.callEndTime = System.currentTimeMillis();  // 記錄 API 請求成功  okHttpEvent.apiSuccess = true;  LogHelper.i(okHttpEvent.toString());  }   @Override  public void callFailed(Call call, IOException ioe) {  LogHelper.i("callFailed ");  super.callFailed(call, ioe);  // 記錄 API 請求失敗及緣由  okHttpEvent.apiSuccess = false;  okHttpEvent.errorReason = Log.getStackTraceString(ioe);  LogHelper.i("reason " + okHttpEvent.errorReason);  LogHelper.i(okHttpEvent.toString());  } } 複製代碼

成功 log 以下所示:

2020-05-11 11:00:42.678 6682-6847/json.chao.com.wanandroid D/OkHttp: --> GET https://www.wanandroid.com/banner/json
2020-05-11 11:00:42.687 6682-6848/json.chao.com.wanandroid I/WanAndroid-LOG: ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────── 2020-05-11 11:00:42.688 6682-6848/json.chao.com.wanandroid I/WanAndroid-LOG: │ Thread: RxCachedThreadScheduler-3 2020-05-11 11:00:42.688 6682-6848/json.chao.com.wanandroid I/WanAndroid-LOG: ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ 2020-05-11 11:00:42.688 6682-6848/json.chao.com.wanandroid I/WanAndroid-LOG: │ OkHttpEventListener.callStart (OkHttpEventListener.java:46) 2020-05-11 11:00:42.688 6682-6848/json.chao.com.wanandroid I/WanAndroid-LOG: │ LogHelper.i (LogHelper.java:37) 2020-05-11 11:00:42.688 6682-6848/json.chao.com.wanandroid I/WanAndroid-LOG: ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ 2020-05-11 11:00:42.688 6682-6848/json.chao.com.wanandroid I/WanAndroid-LOG: │ [OkHttpEventListener.java | 46 | callStart] okHttp Call Start 2020-05-11 11:00:42.688 6682-6848/json.chao.com.wanandroid I/WanAndroid-LOG: └──────────────────────────────────────────────────────────────────────────────────────────────────────────────── 2020-05-11 11:00:43.485 6682-6847/json.chao.com.wanandroid D/OkHttp: <-- 200 OK https://www.wanandroid.com/banner/json (806ms, unknown-length body) 2020-05-11 11:00:43.496 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────── 2020-05-11 11:00:43.498 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: │ Thread: RxCachedThreadScheduler-2 2020-05-11 11:00:43.498 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ 2020-05-11 11:00:43.498 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: │ OkHttpEventListener.callEnd (OkHttpEventListener.java:162) 2020-05-11 11:00:43.498 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: │ LogHelper.i (LogHelper.java:37) 2020-05-11 11:00:43.498 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ 2020-05-11 11:00:43.498 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: │ [OkHttpEventListener.java | 162 | callEnd] NetData: [ 2020-05-11 11:00:43.498 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: │ callTime: 817 2020-05-11 11:00:43.498 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: │ dnsParseTime: 6 2020-05-11 11:00:43.498 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: │ connectTime: 721 2020-05-11 11:00:43.499 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: │ secureConnectTime: 269 2020-05-11 11:00:43.499 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: │ responseBodySize: 975 2020-05-11 11:00:43.499 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: │ apiSuccess: true 2020-05-11 11:00:43.499 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: │ ] 2020-05-11 11:00:43.499 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: └──────────────────────────────────────────────────────────────────────────────────────────────────────────────── 複製代碼

2)、根據網絡質量來動態設定網絡服務的重要參數(超時、併發線程數)

  • 根據用戶 2G/3G/4G/WIFI 的網絡環境。
  • 根據用戶當前網絡的 RTT。

六、壓縮

1)、header(HTTP 2.0 頭部壓縮)

深刻探索 Android 網絡優化(2、網絡優化基礎篇)下 - 首部壓縮

2)、URL

不變參數客戶端只需上傳以此,其它參數均在接入層進行擴展。

3)、body

使用 Protocol Buffers 替代 JSON 序列化。

4)、圖片

  • 1)、webp
  • 2)、hevc
  • 3)、SharpP
  • 4)、基於 AI 的圖片超清化
    • 深度學習 CNN(Convolutional Neural Network,卷積神經網絡)。
    • CNN 學習到的是高分辨率圖像和低分辨率圖像的差。

5)、壓縮算法

  • 1)、GZIP
  • 2)、 Google Brotli
  • 3)、 Facebook Z-standard(推薦):經過業務數據樣本訓練處合適的字典,所以是壓縮率最好的算法,因爲各業務線維護字典成本較大,能夠在網絡平臺的統一接入層進行壓縮與解壓。咱們能夠抽樣1%的數據來訓練字典,而字典的下發與更新由統一接入層負責。

七、加密

HTTPS 一般須要多消耗 2 RTT 的協商時延。

1)、HTTPS 優化

一、提升鏈接複用率

  • 1)、多個域名共用同一個 HTTP2 鏈接。
  • 2)、長鏈接。

二、減小握手次數(TLS 1.3 實現 0 RTT 協商)

TLS 1.2 引入了 SHA-256 哈希算法,摒棄了 SHA-1,對加強數據完整性有着顯著優點。

IETF(Internet Engineering Task Froce,互聯網工程任務組)制定的 TLS 1.3 是有史以來最安全、複雜的 TLS 協議。它具備以下特色:

1)、更快的訪問速度

相比於 TLS 1.2 及以前的版本,TLS 1.3 的握手再也不支持靜態的 RSA 密鑰交換,使用的是帶有前向安全的 Diffie-Hellman 進行全面握手。所以 TLS 1.3 只需 1-RTT 握手時間。

2)、更強的安全性

刪除了以前版本的不安全的加密算法。

  • 1)、RSA 密鑰傳輸:不支持前向安全性。
  • 2)、CBC 模式密碼:易受 BEAST 和 Lucky 13 攻擊。
  • 3)、RC4 流密碼:在 HTTPS 中使用並不安全。
  • 4)、SHA-1 哈希函數:建議以 SHA-2 替代。
  • 5)、任意 Diffie-Hellman 組:CVE-2016-0701 漏洞。
  • 6)、輸出密碼:易受 FREAK 和 LogJam 攻擊。

此外,咱們能夠在 Google 瀏覽器設置 TLS 1.3

三、slight-ssl

參考 TLS 1.3 協議,合併請求,優化加密算法,使用 session-ticket 等策略,力求在安全和體驗間找到一個平衡點。

在 TLS 中性能開銷最大的是 TLS 握手階段的 RSA 加解密。在 slight-ssl 中又嘗試以下幾種解決方案:

  • 1)、硬件加速:使用單獨的硬件加速卡處理 RSA 加解密。
  • 2)、ECDSA:ECSDSA 最底層的算法和成本對性能的消耗遠低於 RSA,相差5~6倍。
  • 3)、Session Ticket 機制:將 TLS 握手從 2RTT 下降爲 1RTT。

四、微信 mmtls 原理

基於 TLS 1.3 草案標準而實現。

相似於 TLS 協議,mmtls 協議也是位於業務層與網絡鏈接層中間。

mmtls 協議組成圖
  • 1)、Handshake、Alert 和 Application Protocol 都是 record 協議的上層協議。
  • 2)、Record 協議包中有字段用於區分器上層協議是上述3種任一協議。
  • 3)、在 mmtls/TLS 中Handshake 子協議負責密鑰協商, Record 子協議負責數據對稱加密傳輸。除了性能與效率的因素以外,更利於隔離複雜性。
Handshake 協議

TLS 1.3 Handshake 協議有以下幾類:

  • 1-RTT 密鑰協商方式
    • 1-RTT ECDHE
    • 1-RTT PSK(Pre-Shared Key)
  • 0-RTT 密鑰協商方式
    • 0-RTT PSK
    • 0-RTT ECDH
    • 0-RTT PSK-ECDHE
    • 0-RTT ECDH-ECDHE

而 mmtls Handshake 協議有以下幾種:

  • 1-RTT ECDHE
  • 1-RTT PSK
  • 0-RTT PSK

1-RTT ECDHE 密鑰協商原理

ECDH 密鑰交換協議須要使用兩個算法:

  • 1)、密鑰生成算法 ECDH_Generate_Key:生成公私鑰對(ECDH_pub_key、ECDH_pri_key),其中保存私鑰,將公鑰互相發送給對方。
  • 2)、密鑰協商算法 ECDH_compute_key:輸入對方公鑰與自身私鑰,計算出通訊雙方一致的對稱密鑰 Key。

可是 1-RTT ECDHE 算法容易被中間人攻擊,中間人能夠截獲雙方的公鑰運行 ECDH_Generate_key 生成本身的公私鑰對,而後將公鑰發送給某一方。

如何解決中間人攻擊?

中間人攻擊產生的本質緣由是沒有通過端點認證,須要」帶認證的密鑰協商「。

數據認證的方式?

數據認證有對稱與非對稱兩種方式:

  • 1)、基於 MAC(Message Authentication Code,消息認證碼)的對稱認證
  • 2)、基於簽名算法的非對稱認證。

ECDH 認證密鑰協商就是 ECDH 密鑰協商 + 數字簽名算法 ECDSA。

雙方密鑰協商會對自身發出的公鑰使用簽名算法,因爲簽名算法中的公鑰 ECDSA_verify_key 是公開的,中間人沒有辦法阻止別人獲取公鑰。

而 mmtls 僅對 Server 作認證,由於通訊一方簽名其協商數據就不會被中間人攻擊。

在 TLS 中,提供了可選的雙方相互認證的能力:

  • Client 經過選擇 CipherSuite 是什麼類型來決定是否要對 Server 進行認證。
  • Server 經過是否發送 CertificateRequest 握手消息來決定是否要對 Client 進行認證。

1-RTT PSK 密鑰協商原理

在以前的 ECDH 握手下,Server 會下發加密的 PSK{key, ticket{key}},其中:

  • key:用來作對稱加密密鑰的 key 明文。
  • ticket{key}:用 server 私密保存的 ticket_key 對 key 進行加密的密文 ticket。

1)、首先,Client 將 ticket{key}、Client_Random 發送給 Server。

2)、而後,Server 使用 ticket_key 解密獲得 key、Server_Random、Client_Random 計算 MAC 來認證。

3)、最後,Server 將 Server_Random、MAC 發送給 Client,Client 同 Server 使用 ticket_key 解密獲得 key、Server_Random、Client_Random 去計算 MAC 來驗證是否與收到的 MAC 匹配。

0-RTT ECDH 密鑰協商原理

要想實現 0-RTT 密鑰協商,就必須在協商一開始就將業務數據安全地傳遞到對端。

預先生成一對公私鑰(static_svr_pub_key, static_svr_pri_key),並將公鑰預置在 Client,私鑰持久保存在 Server。

1)、首先,Client 經過 static_svr_pub_key 與 cli_pri_key 生成一個對稱密鑰SS(Static Secret),用 SS 衍生的密鑰對業務數據加密。

2)、而後,Client cli_pub_key、Client_Random、SS 加密的 AppData 發送給 Server,Sever 經過 cli_pub_key 和 static_svr_pri_key 算出 SS,解密業務數據包。

1-RTT PSK 密鑰協商原理

在進行 1-RTT PSK 握手以前,Client 已經有一個對稱加密密鑰 key 了,直接使用此 key 與 ticket{key} 一塊兒傳遞給 Server 便可。

TLS 1.3 爲何要廢除 RSA?

  • 1)、2015年發現了 FREAK 攻擊,出現了 RSA 漏洞。
  • 2)、一旦私鑰泄露,中間人就能夠經過私鑰計算出以前全部報文的密鑰,破解以前全部的密文。

所以 TLS 1.3 引入了 PFS(perfect forward secrecy,前向安全性),即徹底向前保密,一個密鑰被破解,並不會影響其它密鑰的安全性。

例如 0-RTT ECDH 密鑰協商加密依賴了靜態 static_svr_pri_key,不符合 PFS,咱們可使用 0-RTT ECDH-ECDHE 密鑰協商,即進行 0-RTT ECDH 協商的過程當中也進行 ECDHE 協商。0-RTT PSK 密鑰協商的靜態 ticket_key 同理也能夠加入 ECDHE 協商。

verify_key 如何下發給客戶端?

爲避免證書鏈驗證帶來的時間消耗及傳輸帶來的帶寬消耗,直接將 verify_Key 內置客戶端便可。

如何避免簽名密鑰 sign_key 泄露帶來的影響?

由於 mmtls 內置了 verify_key 在客戶端,必要時及時經過強制升級客戶端的方式來撤銷公鑰並更新。

爲何要在上述密鑰協商過程當中都要引入 client_random、server_random、svr_pub_key 一塊兒作簽名?

由於 svr_pri_Key 可能會泄露,全部單獨使用 svr_pub_key 時會有隱患,由於須要引入 client_random、server_random 來保證獲得的簽名值惟一對應一次握手。

Record 協議

一、認證加密

  • 1)、使用對稱密鑰進行安全通訊。
  • 2)、加密 + 消息認證碼:Encrypt-then-MAC
  • 3)、TLS 1.3 只使用 AEAD(Authenticated-Encryption With Addtional data)類算法:Encrypt 與 MAC 都集成在一個算法內部,讓有經驗的密碼專家在算法內部解決安全問題。
  • 4)、mmtls 使用 AES-GCM 這種 AEAD 類算法。

二、密鑰擴展

雙方使用相同的對稱密鑰進行加密通訊容易被某些對稱密鑰算法破解,所以,須要對原始對稱密鑰作擴展變換獲得相應的對稱加密參數。

密鑰變長鬚要使用密鑰延時函數(KDF,Key Derivation Function),而 TLS 1.3 與 mmtls 都使用了 HKDF 作密鑰擴展。

三、防重放

爲解決防重放,咱們能夠爲鏈接上的每個業務包都添加一個遞增的序列號,只要 Server 檢查到新收到的數據包的序列號小於等於以前收到的數據包的序列號,就判斷爲重放包,mmtls 將序列號做爲構造 AES-GCM 算參數 nonce 的一部分,這樣就不須要對序列號單獨認證。

在 0-RTT 握手下,第一個業務數據包和握手數據包沒法使用上述方案,此時須要客戶端在業務框架層去協調支持防重放。

小結

mmtls 的 工做過程 以下所示:

  • 1)、使用 ECDH 作密鑰協商。
  • 2)、使用 ECDSA 進行簽名認證。
  • 3)、使用 AES-GCM 對稱加密算法對業務數據進行加密。
  • 4)、使用 HKDF 進行密鑰擴展。
  • 5)、使用的摘要算法爲 SHA256。

其優點具備以下4點:

  • 1)、輕量級:去除客戶端認證,內置簽名公鑰,減小驗證時網絡交換次數。
  • 2)、安全性:TLS 1.3 推薦安全性最高的基礎密碼組件,0-RTT 防重放由服務端、客戶端框架層協同處理。
  • 3)、高性能:使用了 0-RTT 握手,優化了 TLS 1.3 中的握手方式和密鑰擴展方式。
  • 4)、高可用:服務器添加了過載保護,確保其能在容災模式下提供安全級別稍低的有損服務。

3)、複用 Session Ticket 會話,節省一個 RTT 耗時。

最後,咱們能夠在統一接入層對傳輸數據二次加密,須要注意二次加密會增長客戶端與服務器的處理耗時。

若是手機設置了代理,TLS 加密的數據能夠被解開並被利用,如何處理?

能夠在 客戶端鎖定根證書,能夠同時兼容老版本與保證證書替換的靈活性。

八、網絡容災機制

  • 1)、備用服務器分流。
  • 2)、屢次失敗後必定時間內不進行請求,避免雪崩效應。

九、資本手段優化

  • 1)、CDN 加速,更新後須要記住清理緩存
  • 2)、提升帶寬
  • 3)、動靜資源分離
  • 4)、部署跨國的專線、加速點
  • 5)、多 IDC 就進接入
  • 6)、P2P 技術

6、網絡庫設計

一、統一的網絡中臺

在一線互聯網公司,都會有統一的網絡中臺:

  • 負責提供先後臺一整套的網絡解決方案。
  • 網關用於解決中間網絡的通信,爲上層服務提供高質量的雙向通信能力。

二、如何設計一個優秀的統一網絡庫?

  • 1)、統一 API:統一的策略管理、流解析(兼容JSON、XML、Protocol Buffers)等
  • 2)、全局網絡控制:統一的網絡調度、流量監控、容災管理等
  • 3)、高性能:速度、CPU、內存、I/O、失敗率、崩潰率、協議兼容性等

三、統一網絡庫的核心模塊有哪些?

  • 1)、DNS 管理
  • 2)、鏈接管理
  • 3)、協議處理
  • 4)、併發模型
  • 5)、IO 模型
  • 6)、預鏈接
  • 7)、錯誤兼容處理
  • 8)、數據解析
  • 9)、網絡質量監控
  • 10)流量監控
  • 11)、代理 WebView 網絡請求

四、高質量網絡庫

1)、Chromium 網絡庫

  • Google 出品,咱們能夠基於 Chromium 網絡庫二次開發本身的網絡庫, 以便享受 Google 後續網絡優化的成果,例如 TLS 1.三、QUIC 支持等等。
  • 跨平臺。
  • 須要補足 Mars 的 弱網/鏈接優化 功能。
  • 自定義協議:改造 TLS,將 RSA 更換爲 ECDHE,以提高加解密速度。

2)、微信 Mars

一個跨平臺的 Socket 層解決方案,不支持完整的 HTTP 協議。

Mars 的兩個核心模塊以下:

  • SDT:網絡診斷模塊
  • STN:信令傳輸模塊,適合小數據傳輸。

其中 STN 模塊的組成圖以下所示:

包包超時

  • 每次讀取或發送的間隔。
  • 獲取 sock snd buf 內未發送的數據。
  • Android:ioctl 讀取 SIOCOUTQ。
  • iOS:getsockopt 讀取 SO_NWRITE。

動態超時

根據網絡狀況,調整其它超時的係數或絕對值。

Mars 是如何進行 鏈接優化 的?

複合鏈接

每間隔幾秒啓動一個新的鏈接,只要有鏈接創建成功,則關閉其它鏈接。=> 有效提高鏈接成功率。

自動重連優化

  • 1)、減小無效等待時間,增長重試次數。
  • 2)、但 TCP 層的重傳間隔過大時,此時斷連重連,可以讓 TCP 層保持積極的重連間隔,以提升成功率。
  • 3)、當鏈路存在較大波動或嚴重擁塞時,經過更換鏈接以得到更好的性能。

網絡切換

經過感知網絡的狀態切換到更好的網絡環境下。

Mars 是如何進行 弱網優化 的?

常規方案

1)、快速重傳
  • 減少重傳成本(SACK、FEC)
  • 儘早發現重傳(DUP ACK、FACK、RTO、NACK)
2)、HARQ(Hybrid Automatic Repeat reQuest)
  • 3 GPP 標準方案。
  • 增長併發度。
  • 儘可能準確避免擁堵(丟包和擁堵的區別)。

進階方案

TCP 丟包的恢復方式 TLP
  • 一、PTO 觸發尾包重傳。
  • 二、尾包的 ACK 帶上 SACK 信息。
  • 三、SACK 觸發 FACK 快速重傳和恢復。
  • 四、避免了 RTO 致使的慢啓動和延遲。
發圖-有損下載

在弱網下儘可能保證下載完整的圖片輪廓顯示,提升用戶體驗。

發圖-有損上傳數據

  • 在弱網下儘可能保證上傳完整的圖片輪廓顯示,提升用戶體驗。
  • 可以下降客戶端上傳失敗率 10% 以上。

有損上傳數據的流程,有損下載流程同理

  • 1)、發送漸進式圖片(例如 JPG 等)。
  • 2)、服務器接收數據且回覆數據確認包。
  • 3)、當數據足夠時(50%),回覆發送成功確認包。
  • 4)、發送方繼續補充數據
    • 網絡正常,數據完整。
    • 網絡異常,認爲已發送成功。
  • 5)、服務器通知發送者。

發圖-低成本重傳

將分包轉成流式傳輸。

  • 1)、分包
    • 下降包大小
    • 增長併發
    • 包頭損耗
  • 2)、流式 確認粒度策略靈活 單線程

7、其它優化方案

一、異地多活

一個多機房的總體方案,在多個地區同時存在對等的多個機房,以用戶維度劃分,多機房共同承擔全量用戶的流量。

在單個機房發送故障時,故障機房的流量能夠快速地被遷引到可用機房,減小故障的恢復時間。

二、抗抖動優化

應用一種有策略的重試機制,將網絡請求以是否發送到 socket 緩衝區做爲分割,將網絡請求生命週期劃分爲」請求開始到發送到 socket 緩衝區「和」已經發送到 socket 緩衝區到請求結束「兩個階段。

這樣當用戶進電梯由於網絡抖動的緣由網絡連接斷了,可是數據其實已經請求到了 socket 緩衝區,使用這種有策略的重試機制,咱們就能夠提高客戶端的網絡抗抖動能力。

三、SYNC 機制

同步差量數據,達到節省流量,提升通訊效率與請求成功率。

客戶端用戶不在線時,SYNC 服務端將差量數據保持在數據庫中。當客戶端下次鏈接到服務器時,再同步差量數據給用戶。

四、高併發流量處理:服務端接入層多級限流

核心思想是保障核心業務在體驗可接受範圍內作降級非核心功能和業務。從入口到業務接口總共分爲四個層級,以下所示:

  • 1)、LVS(幾十億級):多 VIP 多集羣。
  • 2)、接入網關(億級):TCP 限流、核心 RPC 限流。
  • 3)、API 網關(千萬級):分級限流算法(對不一樣請求量的接口使用不一樣的策略)
    • 高 QPS 限流:簡單基數算法,超過這個值直接拒絕。
    • 中 QPS 限流:令牌桶算法,接受必定的流量併發。
    • 低 QPS 限流:分佈式限流,保障限流的準確。
  • 4)、業務接口(百萬級)
    • 返回定製響應、自定義腳本。
    • 客戶端靜默、Alert、Toast。

五、JobScheduler

結合 JobScheduler 來根據實際狀況作網絡請求. 比方說 Splash 閃屏廣告圖片, 咱們能夠在鏈接到 Wifi 時下載緩存到本地; 新聞類的 App 能夠在充電, Wifi 狀態下作離線緩存。

六、網絡請求優先級排序

app應該對網絡請求劃分優先級儘量快地展現最有用的信息給用戶。(高優先級的服務優先使用長鏈接)

馬上呈現給用戶一些實質的信息是一個比較好的用戶體驗,相對於讓用戶等待那些不那麼必要的信息來講。這能夠減小用戶不得不等待的時間,增長APP在慢速網絡時的實用性。(低優先級使用短鏈接)

七、創建長連通道

實現原理

將衆多請求放入等待發送隊列中,待長連通道創建完畢後再將等待隊列中的請求放在長連通道上依次送出。

關鍵細節

HTTP 的請求頭鍵值對中的的鍵是容許相同和重複的。例如 Set-Cookie/Cookie 字段能夠包含多組相同的鍵名稱數據。在長連通訊中,若是對 header 中的鍵值對用不加處理的字典方式保存和傳輸,就會形成數據的丟失。

八、減小域名和避免重定向。

九、沒有請求的請求,纔是最快的請求。

7、網絡體系化方案建設

一、線下測試

1)、正確認識

儘量將問題在上線前暴露出來。

2)、側重點

  • 1)、請求有誤、多餘
  • 2)、網絡切換
  • 3)、弱網
  • 4)、無網

二、線上監控

1)、服務端監控

宏觀監控維度

1)、請求耗時

區分地域、時間段、版本、機型。

2)、失敗率

業務失敗與請求失敗。

3)、Top 失敗接口、異常接口

以便進行鍼對性地優化。

微觀監控維度

1)、吞吐量(requests per second)

RPS/TPS/QPS,每秒的請求次數,服務器最基本的性能指標,RPS 越高就說明服務器的性能越好。

2)、併發數(concurrency)

反映服務器的負載能力,即服務器可以同時支持的客戶端數量,越大越好。

3)、響應時間(time per request)

反映服務器的處理能力,即快慢程度,響應時間越短越好。

4)、操做系統資源

CPU、內存、硬盤和網卡等系統資源。能夠利用 top、vmstat 等工具檢測相關性能。

優化方針

  • 1)、合理利用系統資源,提升服務器的吞吐量和併發數,下降響應時間。
  • 2)、選用高性能的 Web 服務器,開啓長鏈接,提高 TCP 的傳輸效率。

2)、客戶端監控

要實現客戶端監控,首先咱們應該要統一網絡庫,而客戶端須要監控的指標主要有以下三類:

  • 1)、時延:通常咱們比較關心每次請求的 DNS 時間、建連時間、首包時間、總時間等,會有相似 1 秒快開率、2 秒快開率這些指標。
  • 2)、維度:網絡類型、國家、省份、城市、運營商、系統、客戶端版本、機型、請求域名等,這些維度主要用於分析問題。
  • 3)、錯誤:DNS 失敗、鏈接失敗、超時、返回錯誤碼等,會有 DNS 失敗率、鏈接失敗率、網絡訪問的失敗率這些指標。

爲了運算簡單咱們能夠拋棄 UV,只計算每一分鐘部分維度的 PV。

一、Aspect 插樁 — ArgusAPM

關於 ArgusAPM 的網絡監控切面源碼分析能夠參考我以前寫的 深刻探索編譯插樁技術(2、AspectJ) - 使用 AspectJ 打造本身的性能監控框架

缺點

監控不全面,由於 App 可能不使用系統/OkHttp 網絡庫,或是直接使用 Native 網絡請求。

二、Native Hook

須要 Hook 的方法有三類:

  • 1)、鏈接相關:connect
  • 2)、發送數據相關:send 和 sendto。
  • 3)、接收數據相關:recv 和 recvfrom。

不一樣版本 Socket 的實現邏輯會有差別,爲了兼容性考慮,咱們直接 PLT Hook 內存全部的 so,可是須要排除掉 Socket 函數自己所在的 libc.so。其 PLT 的 Hook 代碼以下所示:

hook_plt_method_all_lib("libc.so", "connect", (hook_func) &create_hook);
hook_plt_method_all_lib("libc.so, "send", (hook_func) &send_hook); hook_plt_method_all_lib("libc.so", "recvfrom", (hook_func) &recvfrom_hook); 複製代碼

下面,咱們使用 PLT Hook 來獲取網絡請求信息。

項目地址

其成功 log 以下所示:

2020-05-21 15:10:37.328 27507-27507/com.dodola.socket E/HOOOOOOOOK: JNI_OnLoad
2020-05-21 15:10:37.328 27507-27507/com.dodola.socket E/HOOOOOOOOK: enableSocketHook
2020-05-21 15:10:37.415 27507-27507/com.dodola.socket E/HOOOOOOOOK: hook_plt_method
2020-05-21 15:10:58.484 27507-27677/com.dodola.socket E/HOOOOOOOOK: socket_connect_hook sa_family: 10
2020-05-21 15:10:58.495 27507-27677/com.dodola.socket E/HOOOOOOOOK: stack:com.dodola.socket.SocketHook.getStack(SocketHook.java:13)
libcore.io.Linux.connect(Native Method)
libcore.io.BlockGuardOs.connect(BlockGuardOs.java:126)
libcore.io.IoBridge.connectErrno(IoBridge.java:152)
libcore.io.IoBridge.connect(IoBridge.java:130)
java.net.PlainSocketImpl.socketConnect(PlainSocketImpl.java:129)
java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:356)
java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:200)
java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:182)
java.net.SocksSocketImpl.connect(SocksSocketImpl.java:357)
java.net.Socket.connect(Socket.java:616)
com.android.okhttp.internal.Platform.connectSocket(Platform.java:145)
com.android.okhttp.internal.io.RealConnection.connectSocket(RealConnection.java:141)
com.android.okhttp.internal.io.RealConnection.connect(RealConnection.java:112)
com.android.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:184)
com.android.okhttp.internal.http.Strea
2020-05-21 15:10:58.495 27507-27677/com.dodola.socket E/HOOOOOOOOK: AF_INET6 ipv6 IP===>14.215.177.39:443
2020-05-21 15:10:58.495 27507-27677/com.dodola.socket E/HOOOOOOOOK: socket_connect_hook sa_family: 1
2020-05-21 15:10:58.495 27507-27677/com.dodola.socket E/HOOOOOOOOK: Ignore local socket connect
2020-05-21 15:10:58.523 27507-27677/com.dodola.socket E/HOOOOOOOOK: socket_connect_hook sa_family: 1
2020-05-21 15:10:58.523 27507-27677/com.dodola.socket E/HOOOOOOOOK: Ignore local socket connect
2020-05-21 15:10:58.806 27507-27677/com.dodola.socket E/HOOOOOOOOK: respond:<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus=autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn" autofocus></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>新聞</a> <a href=https://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地圖</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>視頻</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>貼吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登陸</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登陸</a>');
</script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多產品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>關於百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>©2017 Baidu <a href=http://www.baidu.com/duty/>使用百度前必讀</a>  <a href=http://jianyi.baidu.com/ class=cp-feedback>意見反饋</a> 京ICP證030173號  <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>
複製代碼

此外,咱們也可使用愛奇藝提供的 android_plt_hook 來實現 PLT Hook。

缺點

接管了系統的 Local Socket,須要在代碼中增長過濾條件。

3)、接入層監控

爲何要作接入層監控?

  • 1)、服務端更容易作到秒級的實時上報。
  • 2)、僅靠客戶端的監控數據並不徹底可靠。

監控維度

服務的入口和出口流量、服務端的處理時延、錯誤率等。

4)、監控報警

  • 1)、秒級或者分鐘級別的實時監控只有訪問量(PV)、錯誤率等幾個維度:最快速度發現問題。
  • 2)、小時或者天級別的監控能夠監控所有的維度:更好地定位出問題的區域。

監控的同時如何實現準確的自動化報警呢?

  • 1)、基於規則,例如失敗率與歷史數據相比暴漲、流量暴跌等。
  • 2)、基於時間序列算法或者神經網絡的智能化報警,使用者不須要錄入任何規則,只需有足夠長的歷史數據,就能夠實現自動報警。

一般是兩種結合使用。

三、異常監控體系搭建

1)、服務器防刷

超限拒絕訪問。

2)、客戶端

  • 1)、大文件預警
  • 2)、異常兜底策略:例如客戶端超過5次鏈接失敗,則設置更長的重試時間。

3)、單點問題追查

若是用戶反饋 App 消耗的流量過多,或後臺消耗流量較多,咱們均可以具體地分析網絡請求日誌、以及下發命令查看具體時間段的流量、客戶端線上監控 + 體系化方案建設 來實現單點問題的追查。

8、網絡優化常見問題

一、在網絡方面大家作了哪些監控,創建了哪些指標?

注意:體現演進的過程。

網絡優化及監控咱們剛開始並無去作,所以咱們在 APP 的初期並無注意到網絡的問題,而且咱們一般是在 WIFI 場景下進行開發,因此並無注意到網絡方面的問題。

當 APP 增大後,用戶增多,逐漸由用戶反饋 界面打不開或界面顯示慢,也有用戶反饋咱們 APP 消耗的流量比較多。在咱們接受到這些反饋的時候,咱們沒有數據支撐,沒法判斷用戶反饋是否是正確的。同時,咱們也不知道線上用戶真實的體驗是怎樣的。因此,咱們就 創建了線上的網絡監控,主要分爲 質量監控與流量監控

1)、質量監控

首先,最重要的是接口的請求成功率與每步的耗時,好比 DNS 的解析時間、創建鏈接的時間、接口失敗的緣由,而後在合適的時間點上報給服務器。

2)、流量監控

首先,咱們獲取到了精準的流量消耗狀況,而且在 APM 後臺,能夠下發指令獲取用戶在具體時間段的流量消耗狀況。 => 引出亮點 => 先後臺流量獲取方案。 關於指標 => 網絡監控。

二、怎麼有效地下降用戶的流量消耗?

注意:結合實際案例

1)、數據:緩存、增量更新(這一步減小了很是多的流量消耗)

首先,咱們處理了項目當中展現數據相關的接口,同時,對時效性沒那麼強的接口作了數據的緩存,也就是一段時間內的重複請求直接走緩存,而不走網絡請求,從而避免流量浪費。對於一些數據的更新,例如省市區域、配置信息、離線包等信息,咱們 加上版本號的概念,以實現每次更新只傳遞變化的數據,即實現了增量更新 => 亮點:離線包增量更新實現原理與關鍵細節。

2)、上傳:壓縮

而後,咱們在上傳流量這方面也作了處理,好比針對 POST 請求,咱們對 Body 作了 GZip 壓縮,而對於圖片的發送,必需要通過壓縮,它可以在保證清晰度的前提下極大地減小其體積。

3)、圖片:縮略圖、webp

對於圖片展現,咱們採用了不一樣場景展現不一樣圖片的策略,好比在列表展現界面,咱們只展現了縮略圖,而到用戶顯示大圖的時候,咱們纔去展現原圖。 => 引出 webp 的使用策略。

三、用戶反饋消耗流量多這種問題怎麼排查?

首先,部分用戶遇到流量消耗多的狀況是確定會存在的,由於線上用戶很是多,每一個人遇到的狀況確定是不同的,好比有些用戶他的操做路徑比較詭異,可能會引起一些異常狀況,所以有些用戶可能會消耗比較多的流量。

1)、精準獲取流量的能力

咱們在客戶端能夠精確q地獲取到流量的消耗,這樣就給咱們排查用戶的流量消耗提供了依據,咱們就知道用戶的流量消耗是否是不少。

2)、全部請求大小及次數的監控

此外,經過網絡請求質量的監控,咱們知道了用戶全部網絡請求的次數與大小,經過大小和次數排查,咱們就能知道用戶在使用過程當中遇到了哪些 bug 或者是執行了一些異常的邏輯致使重複下載,處於不斷重試的過程之中。

3)、主動預警的能力

在客戶端,咱們發現了相似的問題以後,咱們還須要配備主動預警的能力,及時地通知開發同窗進行排除驗證,經過以上手段,咱們對待用戶的反饋就能更加高效的解決,由於咱們有了用戶全部的網絡請求數據。

四、系統如何知道當前 WiFi 有問題?

若是一個 WiFi 發送過數據包,可是沒有收到任何的 ACK 回包,這個時候就能夠初步判斷當前的 WiFi 是有問題的。

9、總結

網絡優化能夠說是移動端性能優化領域中水最深的領域之一,要想作好網絡優化必須具有很是紮實的技術功底與全鏈路思惟。總所周知,對於一個工程師的技術評級每每是以他最深刻的那一兩個領域爲基準,而不是計算其技術棧的平均值。所以,建議你們能找準一兩個點,例如 網絡、內存、NDK、Flutter,對其進行深刻挖掘,以打造自身的技術壁壘。而筆者後續也會利用晚上的時間繼續深刻 網絡協議與安全 的領域,開始持續不斷地深刻挖掘。

公衆號

個人公衆號 JsonChao 開通啦,歡迎關注~

參考連接:


Contanct Me

● 微信:

歡迎關注個人微信:bcce5360

● 微信羣:

因爲微信羣人數過多,麻煩你們想進微信羣的朋友們,加我微信拉你進羣。

● QQ羣:

2千人QQ羣,Awesome-Android學習交流羣,QQ羣號:959936182, 歡迎你們加入~

About me

很感謝您閱讀這篇文章,但願您能將它分享給您的朋友或技術羣,這對我意義重大。

但願咱們能成爲朋友,在 Github掘金上一塊兒分享知識。

本文使用 mdnice 排版

相關文章
相關標籤/搜索