Android 網(wǎng)絡(luò)優(yōu)化(下)

一、網(wǎng)絡(luò)優(yōu)化維度

1、網(wǎng)絡(luò)優(yōu)化分析

基礎(chǔ)網(wǎng)絡(luò)的效率就像一輛列車,時(shí)延是火車的速度 (啟動(dòng)時(shí)間),而帶寬就像火車的車廂裝載量,整個(gè)傳輸?shù)奈锢礞溌肪拖窕疖嚨蔫F軌。從網(wǎng)絡(luò)的通信過程來看,共涉及到 三個(gè)模塊

  • 1)、網(wǎng)絡(luò)庫(kù) SDK 內(nèi)部的設(shè)計(jì)與策略:I/O 并發(fā)模型,針對(duì)網(wǎng)絡(luò)問題的優(yōu)化。
  • 2)、服務(wù)器性能:并發(fā)、帶寬能力。
  • 3)、網(wǎng)絡(luò)相關(guān):用戶網(wǎng)絡(luò)(弱網(wǎng)/強(qiáng)網(wǎng))、運(yùn)營(yíng)商、網(wǎng)絡(luò)鏈路等。

而對(duì)于網(wǎng)絡(luò)的優(yōu)化,我們可以從以下五個(gè)維度來進(jìn)行。
1)、流量?jī)?yōu)化
精確獲取網(wǎng)絡(luò)流量的消耗量,解決整體均值掩蓋單點(diǎn)異常流量的問題。
2)、網(wǎng)絡(luò)監(jiān)控
建設(shè)全面的網(wǎng)絡(luò)監(jiān)控,因?yàn)榇至6鹊谋O(jiān)控不能夠幫助我們發(fā)現(xiàn)和解決問題。
3)、流量消耗

  • 1、精準(zhǔn)獲取一段時(shí)間的流量消耗、網(wǎng)絡(luò)類型、前后臺(tái)。

  • 2、用戶流量消耗均值、異常率(消耗多、次數(shù)多)。

  • 3、完整鏈路全監(jiān)控(Request、Response)、主動(dòng)上報(bào)。
    4)、網(wǎng)絡(luò)請(qǐng)求質(zhì)量

  • 1、請(qǐng)求時(shí)長(zhǎng)、業(yè)務(wù)成功率、失敗率、TOP 失敗接口,導(dǎo)致請(qǐng)求失敗的原因通常有兩種情況:
    1)、弱信號(hào):可以簡(jiǎn)單看成手機(jī)信號(hào)只有一兩格的時(shí)候,這是不僅僅是信令(無線網(wǎng)絡(luò)通信的都是一個(gè)個(gè)的信令)發(fā)出去困難,還可能導(dǎo)致不斷切換網(wǎng)絡(luò)、基站。App 只能在應(yīng)用層做重試,因?yàn)槿跣盘?hào)一般都是一時(shí)的。
    2)、擁塞網(wǎng)絡(luò):可以類比為堵車、排隊(duì)的場(chǎng)景,數(shù)據(jù)包排隊(duì),信令也在排隊(duì)。這時(shí) App 不斷重試,只會(huì)使得擁塞網(wǎng)絡(luò)更為嚴(yán)重。我們只能讓自己的非核心業(yè)務(wù)不要去排隊(duì),并讓核心業(yè)務(wù)的數(shù)據(jù)量更少,協(xié)議來回更少。

  • 2、用戶體驗(yàn)

  • 3、請(qǐng)求速度、成功率:網(wǎng)絡(luò)正常時(shí)如何更好地利用帶寬提升網(wǎng)絡(luò)請(qǐng)求速度?

  • 4、弱網(wǎng):網(wǎng)絡(luò)不穩(wěn)定是如何最大程度上保證網(wǎng)絡(luò)的連通性?

  • 5、安全:如何防止被第三方劫持、竊聽甚至篡改?
    5)、其它

  • 1、公司成本

  • 2、帶寬、服務(wù)器數(shù)量、CDN

  • 3、耗電

2、網(wǎng)絡(luò)優(yōu)化誤區(qū)

  • 1)、僅僅關(guān)注流量消耗,忽視其它維度。
  • 2)、僅僅關(guān)注均值、整體、忽視個(gè)體。

二、網(wǎng)絡(luò)優(yōu)化工具

1、Network Profiler

特點(diǎn)

  • 1)、顯示實(shí)時(shí)網(wǎng)絡(luò)活動(dòng):發(fā)送、接收數(shù)據(jù)及連接數(shù)。
  • 2)、需啟動(dòng)高級(jí)分析。
  • 3)、僅支持 HttpURLConnection 與 OkHttp
    打開高級(jí)分析
    Run => Edit Cofigurations => 界面最右邊 Profiling => 打開 Enable advanced profiling (required for API level < 26 only)

使用 Network Profiler 調(diào)試 WanAndroid 網(wǎng)絡(luò)請(qǐng)求
Connection View
選中目標(biāo)網(wǎng)絡(luò)請(qǐng)求,可以看到在下方的 Connection View 一欄看到對(duì)應(yīng)的網(wǎng)絡(luò)數(shù)據(jù),如下所示:

Size
Type
Status
Time
Timeline

選中 Connection View 特定的一條數(shù)據(jù)即可在右邊看到該請(qǐng)求對(duì)應(yīng)的網(wǎng)絡(luò)數(shù)據(jù)。

Overview
該網(wǎng)絡(luò)請(qǐng)求的預(yù)覽信息

普通 Json 數(shù)據(jù)請(qǐng)求.png

圖片加載請(qǐng)求.png

Response
Response Header 與 Body 信息
Request
Request Header 與 Body 信息
CallStack
網(wǎng)絡(luò)請(qǐng)求的調(diào)用堆棧信息, 下圖就是 Awesome-WanAndroid 發(fā)起一個(gè)網(wǎng)絡(luò)請(qǐng)求所經(jīng)歷的調(diào)用堆棧:

2、Charles

特點(diǎn)

  • 1)、斷點(diǎn)功能
  • 2)、Map Local
  • 3)、弱網(wǎng)環(huán)境模擬

Charles。

使用斷點(diǎn)功能
1)、右鍵點(diǎn)擊要斷點(diǎn)的 URL,選中 BreakPoints 開啟斷點(diǎn)功能。
2)、點(diǎn)擊頂部 Proxy => Breadkpoint Settings。
3)、雙擊 Breakpoints Settings 面板中的目標(biāo)
URL,在彈出的 Edit Breakpoint 面板中進(jìn)行編輯。
4)、這里默認(rèn)選擇斷點(diǎn) Request 與 Response,我們可以選擇僅斷點(diǎn) Response 或 Request。點(diǎn)擊確認(rèn)即斷點(diǎn)設(shè)置完成。
5)、然后,我們就可以點(diǎn)擊主面板右側(cè)的 Edit Response 編輯 Response,修改完成后點(diǎn)擊最下方的 Execute 即可。

使用 Map Local
1)、自由模擬服務(wù)端的返回?cái)?shù)據(jù),以提前進(jìn)行接口測(cè)試。
1)、右鍵點(diǎn)擊要使用 Map Local 的 URL,選中Map Local 開啟斷點(diǎn)功能。
1)、然后,我們?cè)?Edit Mapping 面板中選擇 Map To 的 Local path,選擇本地設(shè)定的 maplocal 本地?cái)?shù)據(jù)(例如 JsonString)

弱網(wǎng)模擬功能

  • 1)、注意開啟前需將 Map Local 關(guān)閉。
  • 2)、點(diǎn)擊 Proxy => Throttle Setting => 選中 Enable Throttling
  • 3)、這里預(yù)設(shè)了很多模擬設(shè)置,我們只需將 網(wǎng)絡(luò)包傳輸?shù)乃俾?Throttle preset 設(shè)置為較低的速率(一般設(shè)為 256/512)。

3、Wireshark

geektime-webprotocol

WireShark 主要可以用來對(duì)四種流進(jìn)行跟蹤,如下所示:

TCP
UDP
SSL
HTTP

4、TcpDump(網(wǎng)絡(luò)數(shù)據(jù)包嗅探器)

tcpdump

5、Stetho

  • 1)、在 build.gradle 中,除了 Stetho 依賴外,還需添加 'com.facebook.stetho:stetho-okhttp3:1.5.0'。
  • 2)、在 Application 的 onCreate 方法中初始化 'Stetho.initializeWithDefaults(this)'。
  • 3)、調(diào)用 OkHttp 的 'addNeworkInterceptor' 方法添加 Stetho 用于收集網(wǎng)絡(luò)信息而提供的網(wǎng)絡(luò)攔截器。
  • 4)、訪問 Chrome 調(diào)試頁(yè)面 'chrome://inspect'。

6、其它的性能檢測(cè)工具

  • strace:跟蹤 Socket 相關(guān)的系統(tǒng)調(diào)用。
  • netstat:記錄多種網(wǎng)絡(luò)棧和接口統(tǒng)計(jì)信息。
  • ifconfig:記錄接口配置。
  • ip:記錄網(wǎng)絡(luò)接口統(tǒng)計(jì)信息。
  • ping:測(cè)試網(wǎng)絡(luò)連通性。
  • traceroute:測(cè)試網(wǎng)絡(luò)路由。
  • /proc/net 命令:查看網(wǎng)絡(luò)統(tǒng)計(jì)信息,Android TrafficState 使用了 /proc/net/xt_qtaguid/stats 和 /proc/net/xt_qtaguid/iface_stat_fmt 文件來統(tǒng)計(jì) App 的流量信息。

三、精準(zhǔn)獲取流量消耗

1、如何判斷 App 流量消耗偏高?

  • 1)、絕對(duì)值看不出高低。
  • 2)、對(duì)比競(jìng)品,相同 Case 對(duì)比流浪消耗。
  • 3)、異常監(jiān)控超過正常指標(biāo)。

2、測(cè)試方案

  • 1)、打開手機(jī)設(shè)置 => 流量管理 => 僅允許目標(biāo) App 聯(lián)網(wǎng)
  • 2)、可以查找出大多數(shù)的問題,但是線上場(chǎng)景線下可能遇不到。

3、線上流量獲取方案

1)、TrafficStats

特點(diǎn)

  • API 18 以上。
  • 記錄手機(jī)重啟以來的數(shù)據(jù)流量。
    API
  • getMobileRxBytes():通過蜂窩流量接收到的信息。
  • getUidRxBytes(int uid):獲取指定 uid 的接收流量。
  • getTotalRxBytes():總發(fā)送流量。
    缺點(diǎn)
    無法獲取某個(gè)時(shí)間段內(nèi)的流量消耗。

2)、NetworkStatsManager

API 23 之后。
特點(diǎn)

  • 1)、獲取指定時(shí)間間隔內(nèi)的流量信息。
  • 2)、獲取不同網(wǎng)絡(luò)類型下的消耗。
    NetUtils.getStats
    獲取指定時(shí)間間隔的 蜂窩 + WIFI 流量總信息

4、前后臺(tái)流量獲取方案

問題:線上反饋 App 后天流量消耗大?
只獲取一個(gè)時(shí)間段的流量不夠全面。
實(shí)現(xiàn)原理
后臺(tái)定時(shí)任務(wù) => 獲取時(shí)間間隔內(nèi)流量 => 記錄前后臺(tái) => 分別計(jì)算 => 上報(bào) APM 后臺(tái) => 流量治理依據(jù)
小結(jié)

  • 1)、該方案無法獲取應(yīng)用在前后臺(tái)切換時(shí)的流量,因此有一定的誤差,但這個(gè)誤差是可以接受的。
  • 2)、結(jié)合精細(xì)化的流量異常報(bào)警針對(duì)性的解決后臺(tái)跑流量的問題。

四、網(wǎng)絡(luò)請(qǐng)求流量?jī)?yōu)化

1、常見使用網(wǎng)絡(luò)的場(chǎng)景

1)、數(shù)據(jù)壓縮
POST 請(qǐng)求 Body 使用 GZip 壓縮,同時(shí)服務(wù)端返回 Body 也使用 GZip 壓縮。
2)、圖片

  • 圖片上傳前壓縮。
  • 圖片使用策略細(xì)化:讓 服務(wù)端/CDN 云服務(wù)器 優(yōu)先使用縮略圖/WebP格式圖片。
    3)、性能日志上報(bào):批量 + 特定場(chǎng)景上報(bào)
    APM 相關(guān)、單點(diǎn)問題相關(guān)。例如埋點(diǎn)數(shù)據(jù)可以等到某一時(shí)機(jī)點(diǎn)(例如 開啟了 WIFI、數(shù)據(jù)量過大必須上傳一部分時(shí))再上傳。
    4)、數(shù)據(jù)緩存
    服務(wù)端返回加上過期時(shí)間,避免每次重新獲取。 節(jié)約流量且大幅提高數(shù)據(jù)訪問速度,更好的用戶體驗(yàn)。
    Request 緩存設(shè)置
  • 1、Pragma:no-cache:去服務(wù)器拉取最新的資源,不使用緩存。
  • 2、If-Modified-Since:datetime:如果資源在客戶端提供的時(shí)間后發(fā)生改變,服務(wù)器會(huì)返回新的資源,否則使用緩存。
  • 3、If-None-Match:etagvalue:如果資源的標(biāo)識(shí)和服務(wù)器的不同,返回新的資源。
    當(dāng) Request 的頭部是 2 和 3 時(shí),如果服務(wù)器的資源沒有修改,則服務(wù)器會(huì)返回 HTTP/304 Not Modified,客戶端會(huì)使用緩存的 Response。
    Response 緩存設(shè)置
    HTTP Response 是否可以緩存是由 Response 的頭部控制的,服務(wù)器可以通過 Expires 和 Cache-Control 控制 Response 如何在客戶端緩存。
    Expires
    Expires 頭部會(huì)包含一個(gè)日期,即該資源緩存的有效期,客戶端有新的相同請(qǐng)求時(shí),如果資源緩存沒有過期,則使用緩存資源,服務(wù)器不會(huì)返回任何東西。
    Cache-Control
    Cache-Control 可以標(biāo)明 Response 如何存儲(chǔ)及其如何使用,其選項(xiàng)如下所示:
  • 1)、public:Response 可以存儲(chǔ)在任何 Cache 中,包括共享的 Cache。
  • 2)、private:Response 存儲(chǔ)在私有 Cache 中,只能被一個(gè)用戶使用。
  • 3)、no-cache:Response 將來不會(huì)被使用。
  • 4)、no-store:Response 將來不會(huì)被使用,也不會(huì)寫到磁盤上。
  • 5)、max-age=#seconds:Response 在設(shè)定的時(shí)間內(nèi)可以被重復(fù)使用。
  • 6)、must-revalidate:和原始服務(wù)器確認(rèn) Response 是最新后,可以使用緩存。

OKHttp 無網(wǎng)數(shù)據(jù)緩存實(shí)現(xiàn)
POST 在 OKHttp 中默認(rèn)不會(huì)緩存,因?yàn)?POST 一般是用來修改數(shù)據(jù)的。在 Awesome-WanAndroid 中的 HttpModule—cacheInterceptor 中就已經(jīng)實(shí)現(xiàn)了 OKHttp 的無網(wǎng)數(shù)據(jù)緩存,代碼如下所示:

File cacheFile = new File(Constants.PATH_CACHE);
Cache cache = new Cache(cacheFile, 1024 * 1024 * 50);
Interceptor cacheInterceptor = chain -> {
    Request request = chain.request();
    if (!CommonUtils.isNetworkConnected()) {
        // 無網(wǎng)時(shí)強(qiáng)制使用數(shù)據(jù)緩存,以提升用戶體驗(yàn)。
        request = request.newBuilder()
                .cacheControl(CacheControl.FORCE_CACHE)
                .build();
    }
    Response response = chain.proceed(request);
    if (CommonUtils.isNetworkConnected()) {
        int maxAge = 0;
        // 有網(wǎng)絡(luò)時(shí), 不緩存, 最大保存時(shí)長(zhǎng)為0
        response.newBuilder()
                .header("Cache-Control", "public, max-age=" + maxAge)
                .removeHeader("Pragma")
                .build();
    } else {
        // 無網(wǎng)絡(luò)時(shí),設(shè)置超時(shí)為4周
        int maxStale = 60 * 60 * 24 * 28;
        response.newBuilder()
                .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
                .removeHeader("Pragma")
                .build();
    }
    return response;
};
// 緩存優(yōu)化
builder.addNetworkInterceptor(cacheInterceptor);
builder.addInterceptor(cacheInterceptor);
builder.cache(cache);

5)、離線包、增量數(shù)據(jù)更新
加上版本的概念,僅傳輸有變化的數(shù)據(jù)。
6)、請(qǐng)求頭壓縮
如果請(qǐng)求頭不變,服務(wù)端可以使用映射緩存 請(qǐng)求頭 MD5 : 請(qǐng)求頭,之后請(qǐng)求頭都使用 MD5 即可。
7)、優(yōu)化發(fā)送頻率和時(shí)機(jī)
8)、合并網(wǎng)絡(luò)請(qǐng)求、減少請(qǐng)求次數(shù)。
9)、流量兜底能力
如果發(fā)現(xiàn)流量異常,我們可以通過后臺(tái)服務(wù)器終止協(xié)議交互,以避免問題惡化。

2、流量統(tǒng)計(jì)

我們可以利用 network-connection-class 進(jìn)行流量統(tǒng)計(jì),它內(nèi)部使用的是 API 8 的 TrafficStats 類,用于獲取整個(gè)手機(jī)或者某個(gè) UID 從開機(jī)算起的網(wǎng)絡(luò)流量。
1)、四個(gè)核心 API

// 從開機(jī)開始Mobile網(wǎng)絡(luò)接收的字節(jié)總數(shù),不包括Wifi
getMobileRxBytes()        
// 從開機(jī)開始所有網(wǎng)絡(luò)接收的字節(jié)總數(shù),包括Wifi
getTotalRxBytes()     
// 從開機(jī)開始Mobile網(wǎng)絡(luò)發(fā)送的字節(jié)總數(shù),不包括Wifi
getMobileTxBytes()        
// 從開機(jī)開始所有網(wǎng)絡(luò)發(fā)送的字節(jié)總數(shù),包括Wifi
getTotalTxBytes()         

2)、對(duì)應(yīng)的Linux 內(nèi)核 proc 統(tǒng)計(jì)接口

// stats接口提供各個(gè)uid在各個(gè)網(wǎng)絡(luò)接口(wlan0, ppp0等)的流量信息
/proc/net/xt_qtaguid/stats
// iface_stat_fmt接口提供各個(gè)接口的匯總流量信息
proc/net/xt_qtaguid/iface_stat_fmt

3)、工作原理

  • 1)、讀取 proc,并將目標(biāo) UID 下面所有網(wǎng)絡(luò)接口的流量相加。
  • 2)、Android 7.0 之后只能通過 TrafficStats 拿到自己應(yīng)用的流量信息。

參考

深入探索 Android 網(wǎng)絡(luò)優(yōu)化(三、網(wǎng)絡(luò)優(yōu)化篇)上

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容