####前言 互聯網時代, App做爲於用戶交互的端, 能夠說其實是一個界面, 產品的業務, 服務都是由Server提供的. 而App與Server的交互依賴於網絡, 故而網絡優化, 也是咱們的App優化中不可缺乏的一個優化項. 典型的HTTP請求流程說明:html
####一、網絡鏈接對用戶的影響 App的網絡鏈接對於用戶來講, 影響不少, 且多數狀況下都很直觀, 直接影響用戶對這個App的使用體驗. 其中較爲重要的幾點: 流量App的流量消耗對用戶來講是比較敏感的, 畢竟流量是花錢的嘛. 如今大部分人的手機上都有安裝流量監控的工具App, 用來監控App的流量使用. 若是咱們的App這方面沒有控制好, 會給用戶很差的使用體驗.android
電量電量相對於用戶來講, 沒有那麼明顯. 通常用戶可能不會太注意. 可是如前文電量優化中說的那樣, 網絡鏈接(radio)是對電量影響很大的一個因素. 因此咱們也要加以注意.git
用戶等待也就是用戶體驗, 良好的用戶體驗, 纔是咱們留住用戶的第一步. 若是App請求等待時間長, 會給用戶網絡卡, 應用反應慢的感受, 若是有對比, 有替代品, 咱們的App極可能就會被用戶無情拋棄.github
####二、分析網絡鏈接的工具 2.1 Network Monitor Android Studio內置的Monitor工具中就有一個Network Monitor:sql
其中: Rx --- R(ecive) 表示下行流量, 即下載接收. Tx --- T(ransmit) 表示上行流量, 即上傳發送.數據庫
怎麼使用Network Monitor? Network monitor實時跟蹤選定應用的數據請求狀況. 咱們能夠連上手機, 選定調試應用進程, 而後在App上操做咱們須要分析的頁面請求. 例如, 上圖就是以CoderPub爲例, 針對從repo列表界面進入repo詳情界面的監控數據. 能夠看到從10s到30s之間, 20s時間內發生了屢次數據請求, 且22s到27s之間的請求數據量還很大. 分析代碼能夠看到, 在請求repo詳情的時候是打包了不少請求的:api
@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 Wireshark, Fiddler, Charlesr等抓包工具bash
使用Charles、Fiddler等抓包工具一樣能夠實現Network Monitor的功能,並且更增強大。服務器
2.3StethoStetho是Facebook出品的一個Android應用的調試工具。無需Root便可經過Chrome,在Chrome Developer Tools中可視化查看應用佈局,網絡請求,sqlite,preference等。一樣集成了Stetho以後也能夠很方便的查看網絡請求的各類狀況。
####三、網絡優化
重點來了,網絡優化主要從三個方面進行: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壓縮前
開啓Gzip壓縮後 對比發現,開啓Gzip後能夠減小57.3%的數據傳輸量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優化中至關重要的一項優化. 除了客戶端, 接口的優化外, 不少一部分優化還依賴於服務器端, 包括服務器端的代碼開發, 部署方式等. 跟你的服務器開發/運維工程師一塊兒聊聊這個話題吧:)