當前位置:
首頁 > 知識 > 移動端體驗優化經驗總結與實踐

移動端體驗優化經驗總結與實踐

(給JavaScript加星標,提升前端技能.)

作者:鳳蕭 螞蟻金服·數據體驗技術團隊

https://github.com/ProtoTeam/blog/blob/master/201911/1.md

前言

很多企業都會特別注重自己產品的體驗,尤其是移動端,那移動端的體驗為什麼這麼重要?首先體驗本身就很重要,好的體驗帶給用戶的感受是截然不同的,用戶選擇使用一個產品除了產品本身功能滿足需求之外,還有一個更重要的原因就是產品用起來「爽」,產品整個使用流程必然是舒適自然,才能受到大眾喜愛;此外,產品體驗已成為市場競爭力之一,借用人人都是產品經理上面對體驗的論述:

當技術已不再是產品核心競爭力時,產品競爭的實質就是用戶體驗之爭。

如果產品不能讓用戶身心感到愉悅和舒適,他們很可能會迅速使用其他替代品,對於 toC 的產品尤為明顯,產品體驗糟糕必然會被市場淘汰。但是體驗是一個很龐大的話題,有很多方面會影響產品的體驗,如性能、UI、交互以及人性化的功能等等,本文拋磚引玉,只從技術層面的某幾個方面聊聊移動端的體驗優化,主要以 Android 為切入點,IOS 大部分優化方向與 Android 類似。考慮到市面上絕大多數 APP 都是 Native H5 相結合的應用,且本人項目中也大量使用 H5 頁面,因此將從 Native 端和 H5 端分別總結如何優化體驗。

Native 端體驗優化

一直在思考從技術層面上,Native 端什麼樣才稱得上是體驗佳的產品,有什麼評判標準,從過往經驗來看,個人覺得應該具備以下基本特質:

啟動速度要快

交互流暢不卡頓

有離線緩存

支持弱網環境

友好的用戶提示

作為技術人需要重點把控的是前 4 點,第 5 點可能更多需要設計同學介入,根據以往的經驗,可以從以下幾個方面著手:啟動優化, 內存優化、 UI渲染優化、 網路優化等,內存和 UI 渲染的優化主要針對卡頓問題,網路優化中一個重點涉及的對象是緩存和弱網支持,每一個方面都可以獨立成文進行專門的探索,本文只提供一些主流的優化思路供參考,不詳細展開。

內存優化

雖然現在手機內存配置越來越好,但是內存依然是很吃緊的資源,因為系統對 APP 內存佔用有限制(具體大小依不同手機廠商而異)。內存的優化首先要避免大量的內存泄露,可以使用leakcanary進行自動檢測,若要深入分析,可以使用 AndroidStudio 手動 dump 內存下來用MAT工具進行分析,發現其中潛在的內存泄露對象。其次是盡量使用成熟的圖片開源框架,如Glide或者Picasso等展示圖片或者 Gif。

內存優化除了注意 內存泄露,還要關注 內存的抖動,出現的原因一般是大量頻繁的創建對象,導致頻繁觸發 GC,以致於 APP 使用卡頓,比如常見的場景是在自定義控制項的 onDraw 方法創建對象,因為 onDraw 方法會頻繁調用,在 onDraw 方法中創建大對象會導致內存急劇增長,觸發 GC 導致卡頓。因此要盡量避免在循環體中創建對象,可以考慮使用對象池一次創建多處復用來規避內存抖動。

UI 渲染優化

UI 渲染性能關係到 APP 的流暢度,16ms 內未能完成一次繪製就會出現掉幀,給人感覺就是頁面卡頓,響應不及時。移動端上導致渲染性能下降的原因和解決的一般套路:

布局不合理

布局要避免不必要嵌套,以使用 Hierarchy View 進行輔助查看布局層級關係,來識別嵌套是否合理;同時要根據具體場景合理使用哪一種布局,如 RelativeLayout 不能濫用,對於複雜布局可以用 ConstaintLayout 代替;此外還可以使用 viewstub 進行延遲載入布局,用 merge 和 include 進行布局復用。

過度繪製(overdraw)

過度繪製的出現是因為在重疊的層級結構中,一些不可見的部分因為某些原因,如設置了背景色,也會出現在繪製操作中,導致這塊重疊區域的像素被多次繪製,那明顯是浪費計算資源。可以使用簡單方法識別過度繪製是否嚴重,在 Android 系統中開發主菜單裡面打開「調試 GPU 過度繪製」開關就能看到界面 UI 元素被不同的顏色塊標註(如下圖),

顏色從原色——藍色——綠色——粉色——紅色依次代表過度繪製嚴重程度從低到高,一般而言需要關心紅色的色塊 UI 元素,因為它有嚴重的過度繪製,是有優化空間的。我的一般解法是去掉布局背後不必要的背景色,當然還有其他因素會導致過度繪製,如包裝的自定義控制項,本身因為不注意避免過度繪製的影響,在使用的時候就自帶嚴重的過度繪製問題。

主線程有複雜耗時任務

主線程(UI 線程)不能有複雜耗時的計算任務,否則會導致 UI 無響應,卡頓,最終導致 ANR 的發生。

網路優化

保證介面設計的合理性,必要時合併網路請求,減少請求次數;

網路緩存,針對服務端返回的數據設置有效時間,在有效時間內不走網路請求,減少流量消耗,可以按照自己業務的特性自定義緩存的實現。在弱網或者是無網路的情況下,因為有緩存的支持,不至於 APP 打開一片空白,這給用戶更好的體驗。

數據壓縮,如 Gzip 壓縮 request 和 response,減少網路流量傳輸。

啟動優化

最主要的思路避免把全部的初始化任務放在 Application 中,可以使用子線程或者懶載入的方式來處理初始化任務;另外常規套路是會給第一個 Activity 設置 theme,這樣打開 APP 瞬間看到不是白屏,給用戶的感覺就是啟動速度得到改善。

H5 頁面加速優化

移動互聯網時代,H5 頁面無處不在,幾乎 80%以上的 APP 都有 H5 頁面的影子,一份代碼多端運行且能快速部署的優勢,讓 Hybrid 開發成為很多 APP 的標配。雖然 Hybrid 在體驗上總是趕不上 Native 的體驗,甚至在處理不當的情況下,糟糕的體驗會讓很多企業選擇使用其他技術棧,但是 Hybrid 依然是很多公司使用的主流技術。個人認為,在對頁面體驗沒有太高要求的情況下,Hybrid 依然是當下最佳的開發方式。要實現較好的體驗,需要花費心思對 H5 頁面進行優化,我覺得有三個方向可以進行優化:

頁面啟動白屏時間

H5 頁面的交互體驗,如響應流暢度

頁面渲染性能

本文只從影響體驗最重要的指標——白屏時間來聊聊如何進行優化,響應流暢度和頁面渲染性能因為缺乏實踐經驗,這裡就不班門弄斧。

耗時拆解

先分析下在移動端從用戶點 H5 鏈接到頁面渲染完成展示給用戶,需要經歷的粗略過程,示意如下圖:

Webview 初始化

下載靜態資源(html、js 和 css 等)

數據請求

渲染(解析、組裝、繪製)

這裡的渲染包含了 html、js、css 的解析,組裝成 Render Tree 以及最後的繪製。粗略的估算,可以將耗時拆解為:

總耗時(t) = Webview 初始化耗時(t1) 下載靜態資源耗時(t2) 數據請求耗時(t3) 渲染耗時(t4)

其中 Webview 初始化、靜態資源載入以及數據請求佔用的耗時是比較多的,且這個耗時頁面一定處於白屏階段,以下對這三塊給出一些常規的優化方案,渲染的耗時優化本文不論述。

靜態資源的優化

靜態資源主要指 html,js 和 css 資源,對於單頁應用而言主要是 js 和 css,下圖是我參與的項目中頁面第一次打開時的靜態資源請求情況(無瀏覽器緩存):?

從頁面請求可以看到,其中 1.js 的下載是比較耗時的,是應用比較核心的 js 文件,必須等待此文件下載完成,才有可能繼續後面的頁面渲染。在幾乎零優化的情況下可以看到耗時接近 800ms,還是有很大的優化空間的。下面從前端視角和客戶端視角來講解下靜態資源優化的思路。

前端視角

從前端的角度入手,可以有以下幾個優化手段:

資源壓縮,前端有成熟的工具可以對生成的 js、css 等產物進行壓縮,若有必要可以還考慮 gzip 壓縮,獲得更大的壓縮比。

資源請求合併,過多分散的資源包會產生過多的網路請求,但也不能隨意合併,最佳的方式是按照頁面或者模塊進行劃分,並配置 async 屬性來非同步載入 script 腳本。

配置瀏覽器緩存,主要指強緩存和協商緩存,可以大大減少網路時延,減少伺服器壓力。

按需載入,對於單頁應用,如果在首頁就把整個站點的資源全部下載,其實是不合理的,使用按需載入(懶載入)的方式可以有效提高首頁性能。

骨架屏也是在移動端頁面首屏優化的一個重要手段,在頁面數據未準備好的情況,相比與枯燥的白屏頁面而言,展示骨架屏能給用戶一個好的感官體驗。但是如何生成質量高的骨架屏也是一個難點,需要綜合考慮 ROI 來選擇是否使用骨架屏。

客戶端視角

從客戶端角度入手,其實是客戶端預載入靜態資源或者提前內置到手機本地,因此客戶端需要維護要載入到本地的靜態資源列表,當頁面打開時,攔截 webview 資源請求,根據資源 URL 路由到本地對應資源,這樣的速度是極快的。自己去實現該過程會比較繁瑣,上述過程的實現其實就是離線包方案,離線包機制能幫助做好靜態資源更新、管理、攔截、重定向以及異常鏈路,如支付寶的 nebula 容器自帶離線包解決方案。但是單個離線包不宜過大,一般 0-4M,對於較大應用有時候會突破這個限制,實際項目中將一些共用通用的框架資源(如 React、lodash、moment)提取出來,提前預置 APP 中來解決單個離線包大小限制,除此之外成熟的離線包方案自帶公共包機制,也可以解決離線包過大的問題。下圖是經過優化後資源請求情況,

可以看到使用離線包外加預置公共資源方案之後,靜態資源的請求耗時直接降到 200ms 以下,幾乎所有的靜態資源在首次打開頁面就全部走本地存儲,優化效果還是很明顯的。?

數據請求優化

一些在瀏覽器中打開的 web 頁面可能不太注重數據請求的優化,在移動端,由於追求極致體驗,往往數據請求也是有很大優化空間的。以下總結幾點數據請求的優化思路。

請求合併

單頁面數據請求介面壓縮到 1—2 個,過多的網路介面請求,一是會有過多建鏈和斷鏈的網路耗時,二是會提高介面請求失敗率。尤其是相互依賴的介面,可以考慮將請求進行合併。

請求提前

數據請求提前。首屏的數據如果在打開 webview 的瞬間已經準備完畢,那基本很快可以將頁面展示出來。因此在對首屏性能要求較高的場景下,可以考慮將介面請求提前在頁面打開前,如 APP 打開後就提前開始緩存用戶可能要打開的頁面數據,在用戶打開頁面時從本地緩存獲取數據。在實際項目中請求提前涉及兩個現實的問題,請求具體時機以及緩存問題。

請求時機。我參與的項目中,用戶可能要打開的頁面很多,無法提前預知要緩存哪個頁面的數據,初期使用粗暴的方法是在 APP 首頁列表打開時把所有頁面數據全部提前緩存,列表數據太多時性能很差,最終優化方案是使用部分緩存的方式,只對列表可見項進行提前緩存,用戶在滑動頁面時,只緩存可見項的頁面數據,性能有明顯提升。

緩存問題,客戶端提前緩存頁面數據,會遇到緩存一致性問題,如何更新緩存在體驗和正確性之間需要做權衡。我參與的項目沒有健全的推送機制,服務端無法主動通知緩存更新,在這種情況下,何時更新客戶端緩存是一個難題,一般客戶端不會選擇短時間輪詢方式進行緩存更新,因為輪詢會大量消耗手機電量,也會造成服務端壓力。最後使用一個折中方式,犧牲極少概率的正確性換取更好的體驗,客戶端會根據用戶的一些行為來更新緩存,如殺進程、下拉刷新等,同時給緩存設定一個固定的有效期,有效期根據 APP 單次使用平均時長(如 15 分鐘)來設定,保證下次打開 APP 絕大多數用戶緩存能更新。

webview 初始化

webview 是移動端瀏覽器實例,幾乎具備 PC 端瀏覽器的絕大多數能力,客戶端在使用 webview 打開 H5 頁面前,需要實例化 webview 對象,其初始化的過程在 android 系統中需要大約 500ms 以上的時間。有一種手段是使用對象復用機制,提前創建 webview 對象池,需要使用 webview 時直接從池中獲取初始化完畢的對象,這種類似於線程池的方式可以避免每次打開 H5 頁面都要初始化 webview 實例,從而提升頁面打開速度。

其他

還有另外一個完全不同思路來優化移動端 H5 頁面打開速度,那就是服務端渲染,也稱之為 SSR,簡單來講就是服務端將頁面的 html 和數據提前組裝好再傳遞給瀏覽器,瀏覽器只負責解析 html 和展示,因此首屏渲染較快。但是會給服務端增加壓力和複雜度,現實中需要綜合考慮利弊以及 ROI 來選擇是否使用 SSR 方案。

實踐效果

本人參與的項目在 H5 頁面只針對靜態資源和數據請求進行了優化,完成後獲得效果還是較為理想的,見下圖綠色線是優化之後頁面打開的平均白屏時間,藍色是優化前的平均白屏時間,能看到優化成效還是相當可觀的,如果能將 webview 的初始化時間也優化掉,基本上能達到頁面秒開。

總結

以上是結合自己項目以及以往的經驗總結的較為常規的針對移動端體驗優化的思路,比較淺顯,其實每一個優化思路雖然說起來簡單,但是在實踐中會因為各種因素(如投入產出比,前後端資源協調等)導致夭折,而且優化思路也需要分場景,需要因地制宜選用不同的方案。每一個優化思路都可以寫長文進行深入探討,體驗優化是一個馬拉松,需要長期持續的投入,有興趣的歡迎一起交流。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!


請您繼續閱讀更多來自 JavaScript 的精彩文章:

axios 是如何封裝 HTTP 請求的