九、Android效能優化之網路優化

kpioneer123發表於2018-01-18

####前言 網際網路時代, App作為於使用者互動的端, 可以說實際上是一個介面, 產品的業務, 服務都是由Server提供的. 而App與Server的互動依賴於網路, 故而網路優化, 也是我們的App優化中不可缺少的一個優化項. 典型的HTTP請求流程說明:

典型HTTP請求流程

####1、網路連線對使用者的影響 App的網路連線對於使用者來說, 影響很多, 且多數情況下都很直觀, 直接影響使用者對這個App的使用體驗. 其中較為重要的幾點: 流量App的流量消耗對使用者來說是比較敏感的, 畢竟流量是花錢的嘛. 現在大部分人的手機上都有安裝流量監控的工具App, 用來監控App的流量使用. 如果我們的App這方面沒有控制好, 會給使用者不好的使用體驗.

電量電量相對於使用者來說, 沒有那麼明顯. 一般使用者可能不會太注意. 但是如前文電量優化中說的那樣, 網路連線(radio)是對電量影響很大的一個因素. 所以我們也要加以注意.

使用者等待也就是使用者體驗, 良好的使用者體驗, 才是我們留住使用者的第一步. 如果App請求等待時間長, 會給使用者網路卡, 應用反應慢的感覺, 如果有對比, 有替代品, 我們的App很可能就會被使用者無情拋棄.

####2、分析網路連線的工具 2.1 Network Monitor Android Studio內建的Monitor工具中就有一個Network Monitor:

Network Monitor

其中: Rx --- R(ecive) 表示下行流量, 即下載接收. Tx --- T(ransmit) 表示上行流量, 即上傳傳送.

怎麼使用Network Monitor? Network monitor實時跟蹤選定應用的資料請求情況. 我們可以連上手機, 選定除錯應用程式, 然後在App上操作我們需要分析的頁面請求. 例如, 上圖就是以CoderPub為例, 針對從repo列表介面進入repo詳情介面的監控資料. 可以看到從10s到30s之間, 20s時間內發生了多次資料請求, 且22s到27s之間的請求資料量還很大. 分析程式碼可以看到, 在請求repo詳情的時候是打包了很多請求的:

@Override
public Observable<RepoDetail> getRepoDetail(String owner, String name) {
    return Observable.zip(mRepoService.get(owner, name),
            mRepoService.contributors(owner, name),
            mRepoService.listForks(owner, name, "newest"),
            mRepoService.readme(owner, name),
            isStarred(owner, name),
            new Func5<Repo, ArrayList<User>, ArrayList<Repo>, Content, Boolean, RepoDetail>() {
                @Override
                public RepoDetail call(Repo repo, ArrayList<User> users, ArrayList<Repo> forks, Content readme, Boolean isStarred) {
                    RepoDetail detail = new RepoDetail();

                    repo.setStarred(isStarred);
                    detail.setBaseRepo(repo);
                    detail.setForks(forks);

                    // because the readme content is encode with Base64 by github.
                    readme.content = StringUtil.base64Decode(readme.content);
                    detail.setReadme(readme);

                    detail.setContributors(users);
                    return detail;
                }
            });
}

複製程式碼

這也驗證了14s到20s間的四次資料請求, 另外由於repo詳情介面會顯示作者以及貢獻者的圖片, 而圖片的資料量相對大, 故而23s到27s間有多次資料量很大的請求發生. 這個實際是有很多優化空間的, 我們稍後再說.

2.2 WiresharkFiddlerCharlesr等抓包工具

使用Charles、Fiddler等抓包工具同樣可以實現Network Monitor的功能,而且更加強大。

Charles使用
2.3Stetho

Stetho是Facebook出品的一個Android應用的除錯工具。無需Root即可通過Chrome,在Chrome Developer Tools中視覺化檢視應用佈局,網路請求,sqlite,preference等。同樣整合了Stetho之後也可以很方便的檢視網路請求的各種情況。

Paste_Image.png

####3、網路優化

重點來了,網路優化主要從三個方面進行:1. 速度;2. 成功率;3. 流量。 #####3.1 介面設計 API設計 App與Server之間的API設計要考慮網路請求的頻次, 資源的狀態等. 以便App可以以較少的請求來完成業務需求和介面的展示. 例如, 註冊登入. 正常會有兩個API, 註冊和登入, 但是設計API時我們應該給註冊介面包含一個隱式的登入. 來避免App在註冊後還得請求一次登入介面(有可能失敗, 從而導致業務流程失敗). 再例如, 上文提到的獲取repo詳情, 實際上請求了4個介面, 請求了repo的資訊, forks列表, contributors列表, readme, 這是因為github提供的介面是儘量單一職責的. 然而在我們的實際開發中, 我們的Server除了提供這些單一職責的小介面外, 最好還能組合一個滿足客戶端業務需求的repo詳情介面出來. Gzip壓縮 使用Gzip來壓縮request和response, 減少傳輸資料量, 從而減少流量消耗. 考慮使用Protocol Buffer代替JSON 從前我們傳輸資料使用XML, 後來使用JSON代替了XML, 很大程度上也是為了可讀性和減少資料量(當然還有對映成POJO的方便程度).

下圖是對比Json資料使用 Gzip 壓縮前後對比圖 返回內容開啟 Gzip壓縮前

Paste_Image.png
開啟Gzip壓縮後

Paste_Image.png
對比發現,開啟Gzip後可以減少57.3%的資料傳輸量

Paste_Image.png

Protocol Buffer是Google推出的一種資料交換格式. 如果我們的介面每次傳輸的資料量很大的話, 可以考慮下protobuf, 會比JSON資料量小很多. 當然相比來說, JSON也有其優勢, 可讀性更高. 本文以網路流量優化的角度推薦protobuf作為一個選擇, 具體還需更具實際情況考慮.

圖片的Size 上面Network Monitor中看到的22s到27s之間的有多次請求, 且資料量還很大. 就是在獲取圖片資源. 圖片相對於介面請求來說, 資料量要大得多. 故而也是我們需要優化的一個點. 我們可以在獲取圖片時告知伺服器需要的圖片的寬高, 以便伺服器給出合適的圖片, 避免浪費. 我們現在很多公司的圖片資源都是使用第三方的雲端儲存服務的(七牛, 阿里雲端儲存之類的). 以七牛為例, 可以在請求圖片的url中新增諸如質量, 格式, width, height等path來獲取合適的圖片資源:

imageView2/<mode>/w/<LongEdge>
                 /h/<ShortEdge>
                 /format/<Format>
                 /interlace/<Interlace>
                 /q/<Quality>
                 /ignore-error/<ignoreError>
複製程式碼

參考七牛官方文件.

#####3.2 圖片處理

#####3.2.1 圖片下載

#####使用縮圖 App中需要載入的圖片按需載入,列表中的圖片根據需要的尺寸載入合適的縮圖即可,只有使用者檢視大圖的時候才去載入原圖。不僅節省流量,同時也能節省記憶體!之前使用某公司的圖片儲存服務在原圖連結之後拼接寬高引數,根據引數的不同返回相應的圖片。

有許多方式來使得圖片更加容易下載,比如使用 WebP圖片,動態地調整大小的圖片,以及使用圖片載入框架。 #####使用WebP圖片 通過網路提供WebP檔案來減少圖片載入的時間和節省網路頻寬,WebP檔案通常會比它的PNG或者JPG檔案小,但會擁有同樣的圖片質量。甚至是使用有損的設定,WebP也可以輸出一個和原圖幾乎完全一樣的圖片。安卓系統從Android4.0(API 14)新增了有損耗的WebP support並且在Android4.2(API 17)對無損的,清晰的WebP提供了支援。 使用WebP格式;同樣的照片,採用WebP格式可大幅節省流量,相對於JPG格式的圖片,流量能節省將近 25% 到 35 %;相對於 PNG 格式的圖片,流量可以節省將近80%。最重要的是使用WebP之後圖片質量也沒有改變。

#####動態修改圖片 應用要求按指定的渲染大小來從網路上請求圖片,這個渲染大小和裝置規格有關,並且伺服器提供的是合適大小的圖片。這樣能夠最小化網路上的資料傳輸,減少持有圖片對記憶體的大量損耗,直接影響到效能的提高和使用者滿意度。

當使用者不得不等待圖片下載的時候使用者體驗就會有所下降,使用合適的圖片尺寸有助於解決這些問題,可以考慮讓圖片的請求都基於網路的型別或者網路連線的質量,這個尺寸可能會比目標值小。

#####使用圖片載入框架 你的APP不應該重複獲取圖片,圖片載入框架比如Glide或者Picasso獲取圖片,然後快取,然後hook到你的View來顯示佔位符圖片,直到真正的圖片準備好了顯示真正的圖片。因為圖片被快取下來了,當圖片下次被請求的時候,這些圖片載入框架會直接從本地快取中獲取。

圖片載入框架會管理他們的快取大小,保留最近使用過的圖片,這樣你的APP的大小不會無限制地增長。

#####3.2.2 圖片上傳

圖片(檔案)的上傳失敗率比較高,不僅僅因為大檔案,同時頻寬、時延、穩定性等因素在此場景下的影響也更加明顯;

避免整檔案傳輸,採用分片傳輸; 根據網路型別以及傳輸過程中的變化動態的修改分片大小; 每個分片失敗重傳的機會。 備註:圖片上傳是一項看似簡單、共性很多但實際上覆雜、需要細分的工作。移動網際網路的場景和有線的場景是有很多區別的,例如行動網路的質量/頻寬經常會發生“跳變”,但有線網路卻是“漸變”。

#####3.3 網路快取 適當的快取, 既可以讓我們的應用看起來更快, 也能避免一些不必要的流量消耗. 關於Android App的網路快取, 請參考MVP架構實現的Github客戶端(4-加入網路快取)一文. #####3.4 打包網路請求 當介面設計不能滿足我們的業務需求時. 例如可能一個介面需要請求多個介面, 或是網路良好, 處於Wifi狀態下時我們想獲取更多的資料等. 這時就可以打包一些網路請求, 例如請求列表的同時, 獲取Header點選率較高的的item項的詳情資料. 可以通過一些統計資料來幫助我們定位使用者接下來的操作是高概率的, 提前獲取這部分的資料.

#####3.5 監聽相關狀態 通過監聽裝置的狀態: 休眠狀態 充電狀態 網路狀態

在狀態弱網下優化:

壓縮/減少資料傳輸量 利用快取減少網路傳輸 針對弱網(行動網路), 不自動載入圖片 介面先反饋, 請求延遲提交例如, 使用者點贊操作, 可以直接給出介面的點贊成功的反饋, 使用JobScheduler在網路情況較好的時候打包請求.

結合JobScheduler來根據實際情況做網路請求. 比方說Splash閃屏廣告圖片, 我們可以在連線到Wifi時下載快取到本地; 新聞類的App可以在充電, Wifi狀態下做離線快取.

#####3.6 調整資料傳輸

有幾種方式讓你的APP適應網路條件,提供一個較好的使用者體驗的,比如,對請求劃分優先等級來最小化使用者等待資訊的時間。也可以檢測並適應較慢網路速度和發生網路連線的時候的發生的改變。

優先考慮頻寬 不應該假設裝置連線的網路是一個長時間持續並且穩定可靠的網路,app應該對網路請求劃分優先順序儘可能快地展示最有用的資訊給使用者。

立刻呈現給使用者一些實質的資訊是一個比較好的使用者體驗,相對於讓使用者等待那些不那麼必要的資訊來說。這可以減少使用者不得不等待的時間,增加APP在慢速網路時的實用性。

為了達到這個目的,對網路請求進行排序比如文字的獲取應該在富媒體之前,文字請求一般都比較小,壓縮更好,並且傳輸速度快,這意味著你的APP可以快速地先顯示內容。

在慢速網路的時候使用更少的頻寬 你的APP傳輸資料的能力是否及時取決於網路連線,檢測這些網路的質量並且調整你的APP使用網路的行為可以提供一個更好的使用者體驗。

使用下列的方法來檢測外部不容易觀察的網路質量,使用從這些方法返回的資料,你的APP應該調整對網路的使用來為使用者的操作提供一個及時的響應

ConnectivityManager> isActiveNetworkMetered() ConnectivityManager> getActiveNetworkInfo() ConnectivityManager> getNetworkCapabilities(Network) TelephonyManager> getNetworkType() 在一個慢速的網路連線中,考慮只下載低解析度的媒體或者直接不下載。確保你的使用者可以在慢速網路中繼續使用你的APP,對於沒有圖片或者圖片仍然在載入的情況,應該先顯示一個佔位符,使用 Palette library建立一個動態的佔位符,生成一個符合目標圖片顏色的佔位符

在Android 7.0或更高版本的裝置上,使用者可以開啟Data Saver設定,可以幫助最小化資料的使用,Android 7.0擴充套件ConnectivityManager來檢測Data Saver設定。

檢測網路改變,然後修改APP的行為 網路質量不是固定不變的,它會隨著地理位置,網路流量和當地人口密度發生改變。APP 應該檢測網路中的改變並且相應地調整頻寬,讓APP可以更好地適應網路質量,可以實現下面的這些方法檢測網路狀態:

ConnectivityManager> getActiveNetworkInfo()
ConnectivityManager> getNetworkCapabilities(Network)
TelephonyManager> getDataState()
複製程式碼

隨著網路質量的下降,減少請求的數量,隨著網路質量的提升,你可以提高你的請求量到最優級別。 在更高的網路質量下,不計費使用流量的網路,可以考慮預取資料讓資料提前可用。從使用者體驗的立場,這可能意味著一個新聞閱讀應用在2G網路下一次只能獲取3篇文章,而在WIFI狀態下一次可以獲取20篇文章。

當網路連線狀態發生改變時會發出CONNECTIVITY_CHANGE廣播,APP在前臺執行的情況下,可以通過註冊廣播接收器來接受這個廣播,在接收廣播以後,你應該再評估當前的網路狀態並且調整你的UI,處理網路請求,不能夠在manifest檔案中宣告這個廣播Action,因為在Android7.0版本之後這個Action就被刪除了。

#####3.6 IP直連與HttpDns;

DNS解析的失敗率佔聯網失敗中很大一種,而且首次域名解析一般需要幾百毫秒。針對此,我們可以不用域名,才用IP直連省去 DNS 解析過程,節省這部分時間。

另外熟悉阿里雲的小夥伴肯定知道HttpDns:HttpDNS基於Http協議的域名解析,替代了基於DNS協議向運營商Local DNS發起解析請求的傳統方式,可以避免Local DNS造成的域名劫持和跨網訪問問題,解決域名解析異常帶來的困擾。

#####3.7請求打包

合併網路請求,減少請求次數。對於一些介面類如統計,無需實時上報,將統計資訊儲存在本地,然後根據策略統一上傳。這樣頭資訊僅需上傳一次,減少了流量也節省了資源。

#####3.8請求頻率優化

可以通過提供一個最佳的網路體驗來增強使用者體驗,比如,你可以讓你的APP在離線狀態下依然可以使用,使用GcmNetworkManager和ContentProvider,刪除重複的網路請求。

讓你的APP在離線狀態下依然可用 在一些比較偏僻的地方,通常網路訊號都不會很好,APP失去網路連線是很常見的事情。建立一個能夠在離線狀態依然正常使用的APP意味著使用者可以在任何時候和你的APP進行互動。可以通過把網路資料儲存在本地來實現這個需求,快取資料,並且把發出的請求新增到佇列中,當網路恢復的時候再及時發出。 在可能的情況下,app不應該通知使用者網路已經失去連線了,只有當使用者進行操作,並且這個操作一定需要網路連線的支援,才通知使用者當前處於沒有網路的狀態。

當一個裝置處於沒有網路連線狀態時,應用應該允許使用者發出的網路請求,在網路連線恢復以後再執行這些網路請求。一個例子就是一個郵件客戶端允許使用者在裝置處於離線的狀態下依然能夠建立,傳送,檢視,移動和刪除已經存在的郵件,就是因為這些操作被儲存下來並且在網路恢復以後再執行。這樣的話,APP就能夠在裝置有網路和沒有網路的時候都提供一個相似的使用者體驗。

使用GcmNetworkManager和contentProvider 確保你的APP使用資料庫或者相似的結構來儲存所有的資料到磁碟上,這樣它就能夠在不管網路條件如何的情況下表現出極佳的體驗,比如使用(SQLite和ContentProvider)快取資料, GCM Network Manager ( GcmNetworkManager)提供一個健全的機制和伺服器同步資料當 content providers (ContentProvider)快取這些資料,結合來提供一個允許在離線狀態繼續使用的結構。

App應該快取從網路上獲取的內容,在發起持續的請求之前,app應該先顯示本地的快取資料。這確保了app不管裝置有沒有網路連線或者是很慢或者是不可靠的網路,都能夠為使用者提供服務。

除去網路請求 一個離線優先的結構初始化時會嘗試從本地獲取資料,失敗以後,從網路請求資料。從網路檢索以後,資料會快取到本地。這就確保對於同一塊資料發起網路請求只會發生一次,對隨後的請求都會使用本地資料來完成請求。為了達到這個,使用本地資料庫來對資料持久化(通常使用android.database.sqlite 或者 SharedPreferences)

這個結構同樣簡化一個APP的流暢性,在離線和線上狀態之間一方從網路獲取資料儲存到本地,另一方從快取獲取資料展示給使用者。

對於短暫不持續的資料,也就是內容更新快的資料,使用一個有大小,或者時間限制的磁碟快取,比如DiskLruCache,資料通常不會發生改變的就只從網路請求一次然後快取下來以後使用,這樣的資料一般是圖片或者非臨時的文件比如新聞文章或者推送訊息。

####4.結語

網路優化, 是App優化中相當重要的一項優化. 除了客戶端, 介面的優化外, 很多一部分優化還依賴於伺服器端, 包括伺服器端的程式碼開發, 部署方式等. 跟你的伺服器開發/運維工程師一起聊聊這個話題吧:)

特別感謝 黑白咖 夜之魔 anly_jun 石先

相關文章