作者:孫然(煮蝦)
對於小程式技術來說,容器載入和前端非同步渲染的過程中固然不可避免的會有白屏或 loading 頁的展示,短則一瞬間,長則需要數秒才能展示首屏。如果白屏時間長,將非常影響使用者的體驗。根據 Google 的統計,如果頁面載入耗時超過了 3s,那麼有 53% 的使用者會選擇直接退出該頁面了。
為加速小程式首頁的展示,支付寶和手淘運用了基於 HTML 的快照技術,主要思路都是快取首頁 HTML 供下次啟動時與資料一起優先渲染以提前首屏展示的時間,適用於傳統 WebView 渲染的小程式場景。這種基於 HTML 的快照技術能夠極大縮短啟動時的白屏時間,但首屏展示的速度還是不夠快,期間使用者仍然會有可見的白屏感受。並且快照展示的仍然是無法點選操作的頁面,需要等待 JS 部分 ready 後才可點選互動。
為了追求極致的體驗效果,我們提出了一種全新的小程式快照技術,目標是既做到徹底消除白屏現象,同時也要能夠響應使用者互動。
核心思路
不同於現有的基於 HTML 的快照技術,我們提出了一種 native 的影像級別的快照技術,主要由以下三個步驟:
- 步驟1:在小程式啟動後合適的時機將小程式首頁儲存為圖片,我們稱之為快照
- 步驟2:下次開啟小程式時先展示上次儲存的快照,再啟動小程式
- 步驟3:當小程式啟動完後的合適時機,隱藏快照,展示出真實的小程式首頁,並儲存當前介面檢視作為下次的快照(同步驟1)
效果
現在釘釘中的新建 DING 日程頁面就運用了快照技術,前後效果對比如下:
before | after |
---|---|
可以看出,通過快照技術,該頁面實現了首屏秒開的效果,啟動白屏的現象徹底消失,頁面的首屏渲染耗時從 1700ms 左右降低到了 300ms 以下。
下面我會對快照技術的幾個關鍵考慮點進行詳細介紹。
場景和時機
理想中的快照,應當是能夠和首屏頁面完全重合,並且在快照隱藏時不會產生任何視覺變化的。那麼生成快照的時機和運用快照的場景就直接決定了快照技術能夠達到的優化效果。
什麼頁面適合用快照?
並不是所有的小程式都適合使用快照技術來提升首屏體驗。如果使用不當,快照可能還會成為體驗的減分項。為達到最佳效果,一般首屏頁面滿足下面幾個條件是比較適合使用快照的:
- 首屏頁面較為固定。如果首屏不固定,很難找到合適的快照時機來保證快照與下次的首屏重合
- 首屏頁面不含使用者隱私資料。使用者的隱私資料不應當被快照下來
什麼時候生成快照?
如果快照的時機過早,快照可能展示的也是白屏或者未渲染完整的首頁框架。
如果快照的時機過晚,可能使用者已經對首屏有了互動(滾動、點選等),容易生成無法和首屏重合的快照。
所以需要根據不同的首屏場景來確定最佳的快照時機。一般我們考慮快照的時機有:
- 小程式首屏 Page 的 onReady 生命週期回撥裡。但此時頁面很可能仍然沒有渲染完成,可以考慮適當延時後進行快照
- 小程式首屏的資料如果需要遠端拉取,可以在遠端獲取到首屏資料後進行
- 使用者發生滾動時、點選等互動後不再進行快照
什麼時候隱藏快照?
我們一般考慮在生成快照時去隱藏當前已經展示的快照。二者的順序一般是在隱藏快照後立即生成快照,才能實現快照和真實頁面的無縫銜接。
當然,也需要考慮小程式啟動可能失敗的場景。這裡需要對展示快照的時間設定一個展示的上限,如果展示時間達到上限時小程式首屏仍然沒有啟動成功,那麼快照將直接隱藏,以防造成首屏對使用者可見但一直不響應的尷尬局面。
這裡我們在還隱藏快照時做了一個小小的視覺優化。考慮到在隱藏快照時,如果直接隱藏快照,一旦快照和真實頁面稍有差別,在視覺上可能就會有閃爍的體感發生。
所以在隱藏快照時,我們會做一個 200ms 的淡出動畫,來緩解這種快照和真實頁面差異導致的閃爍感。因為有時快照的時機可能會稍提前於首頁網路資料載入成功、圖片載入成功等這些非同步事件成功的時機,導致快照比真實頁面元素缺少或者資料不準確,而淡出動畫能夠有效淡化這些差錯造成的視覺異常感。下面的 demo 對比了這兩種情況:
直出 | 淡出 |
---|---|
可互動
由於快照和真實的首屏頁面基本是相同的,從使用者體感上使用者會以為首屏已經成功展現,也應該是個可互動的頁面。所以單純展示死的快照頁面是遠遠不夠的,做到可互動是我們快照的重要能力。
我們的快照支援響應使用者的點選行為,具體方法就是在使用者點選快照時先暫存使用者的點選事件,待快照隱藏時將此次事件分發到真實頁面上去。
此過程中如果使用者有多次點選事件,我們只會響應最後一次點選事件。
從使用者體感上來說,可能使用者會感覺到此次點選的響應會比較慢,但不會讓使用者感知到它點選的是快照還是真實首頁。
如果是小程式啟動較慢的場景,還可以考慮在使用者點選後展示 loading:
為進一步提高快照層的可互動性,我們甚至還可以允許開發者設定一些快照層的點選區域和簡單操作,使使用者在點選快照層的時候快速響應點選事件。例如釘釘的工作臺就非常適合這種場景:工作臺中的各個應用一般不會頻繁變化,並且有很明確的分塊區域:
可以配置好不同的點選區域以及對應的 action (例如:跳轉到其它頁面/應用),形如:
[{
area: {
left: 100,
top: 100,
width: 100,
height: 100
},
action: {
type: 'openLink',
params: { url: 'http://xxx' }
}
}, ...]
這樣使用者在點選快照指定區域時就能直接實現跳轉,而無需等待到小程式啟動完成。
儲存與安全
快照屬於敏感資料,並且只儲存在客戶端本地不能進行上傳,管理其儲存必須格外小心,否則很容易釀成一起公關事件。
對於快照的儲存,我們考慮了以下幾點:
加密儲存
快照資料必須進行加密儲存,這裡加密方式用的是集團無線保鏢裡的加密方法。
隱私保護
快照裡不能含有使用者的隱私資料。也就是說,快照應當只含有一些 UI 元素或者無意義的預設資料,而不應該含有使用者隱私資料。
不含使用者隱私資料 | 含使用者隱私資料 |
---|---|
那麼如何做到獲取到不含使用者隱私資料的首頁快照呢?可以考慮在前端從網路、快取中獲取資料之前進行快照。但這樣的快照必定是不完整的,會損失一定的體驗,這也是我們不推薦在有使用者資料的首屏場景使用快照的原因。
快照清理
快照被儲存在客戶端中,需要有儲存上限。當快照資料達到一定量後,需要淘汰一些老的快照資料。\
其次,在小程式版本更新、使用者登出切換使用者時都應該考慮將現存的對應快照資料清理掉。
準確性
當快照上線後,我們需要對快照的使用者體驗進行感知。最佳的體驗是使用者根本就沒有感知到快照的存在,也就是快照和真實頁面完全重合;而如果快照和真實頁面相差較大,則會讓使用者體驗大大下降,這是我們需要感知到的。
這裡我們主要關注快照的準確性指標,也就是快照和真實頁面的相似(重合)程度。準確性越高,則說明快照與真實頁面的過渡約自然,體驗越好;反之不但不會提升體驗,可能還會對使用者帶來困惑。
如何判斷快照準確性
在每次生成快照時,我們會將本次快照與上一次快照進行對比,得出量化的指標進而反映快照的準確性。那麼下一步的問題就變成了如何判斷兩張圖片的相似度了。
這裡可能首先會想到直接使用畫素逐個對比的方式來計算出兩張快照中不同畫素點的比例,比例越高則快照越準確,但實際上這種方法無法體現出真正的相似度和使用者的體感。例如,當兩張快照位置只要稍有偏移,得出來的相似度值可能很低;或者是兩張色差很小的快照,也可能得到很差的結果。並且,快照的畫素數可能達到上百萬量級,測試發現逐個的畫素對比工作一次可能就會耗時數秒鐘。
我們現在使用了 Google 以圖搜圖中用到的“感知雜湊演算法”來量化快照的準確性。演算法本身流程大致是將圖片壓縮後得出一些“指紋”資訊,然後通過對比不同圖片的指紋資訊計算出“差異指數”。差異指數越高,則說明二者相似度越低。此演算法能夠體現出兩次快照的相似程度,並且其效率比畫素逐個比對的方法有了極大提升,線上資料統計到整個演算法的耗時不超過 3ms。
我們對一些場景進行了實驗並得出差異指數。可以看出,對於微小字元改變的場景,差異指數非常低;而有明顯視覺差距的場景中,差異指數會變高。這樣得出的量化值能夠體現快照對使用者帶來的真實體感的影響。
場景差異指數視覺效果 | ||
---|---|---|
少量字元變化 | 1 | |
整體偏移 | 6 |
如何復原錯誤快照場景
能夠感知快照的準確性後,對於準確性較差的快照,我們還需要知道快照和真實頁面相差在什麼地方,進而改進快照的時機。
這裡,我們是通過獲取快照時前端頁面 DOM 樹的方式來追查當前的真實頁面情況。具體操作是在生成快照時獲取當前小程式 HTML 頁面脫敏後的 DOM 樹資訊,然後再依賴小程式框架的 CSS 檔案,最後直接用瀏覽器就可以恢復出快照時的介面了
其它能力
區域性快照
快照的一個比較大的侷限性就是無法適應多變的首屏場景,這種場景使用快照很容易導致每次快照都無法跟真實首頁重合,反而降低了使用者體驗。所以我們考慮提供一種能力,只對首頁中每次都基本不變的部分進行快照,而其它多變的部分不進行快照,這樣也能夠每次使首屏部分內容實現秒出。
例如釘釘裡的人脈首頁,上半部分是相對固定的展示,而下半部分 feed 流可能每次開啟都會展示不同的資訊。那麼在這種場景下我們就沒必要每次都對整個首頁第一屏進行快照,可以指定一定高度的部分進行快照,讓首頁的一部分實現秒出。
超一屏快照
當首頁可滾動時,我們甚至可以考慮超過一屏長度的快照,並且下次小程式啟動時展示快照時讓快照可滾動。此方案需要注意兩個問題:
- 快照大小\
線上統計顯示,一屏的快照檔案平均大小在 100K 左右。如果是超一屏的快照,大小可能會達到幾百 K。需要在生成快照時預估一個長度上限或快照大小上限,以防快照使用時在低端機中出現 OOM 等異常情況。 - 快照滾動\
如果快照在展示時使用者進行了滾動操作,那麼在隱藏快照時需要記錄當前滾動的偏移量,以便將真實首頁也滾動到指定位置,才能讓快照和真實頁面重合。
效能
對於快照的效能表現,我們進行了實驗室測試和線上的資料統計。
實驗室測試中我們構造了一個超大的快照(5.2M)的極限場景,並在低端機上與正常快照進行了對比:
普通場景極端場景 | ||
---|---|---|
快照大小 | 262K | 5.2M |
記憶體佔用 | 1840K | 3245K |
載入視覺體驗 | 直接出現 | 有極短暫延時 |
快照載入過程並沒有影響正常的頁面切換,只是在過大快照的載入可能有短暫的延時。
線上資料顯示,帶快照的頁面載入耗時在 280ms 左右,快照的平均大小約 110K。
快照的生成和準確性檢測等工作都是在非同步執行緒中進行的,此時使用者互動並未開始,並且在使用者滾動、互動後不會進行快照,不會對效能造成太多影響。
這裡還有個比較有趣的資料:使用者對快照的平均點選次數是 0.6 次,首次點選時間約為 1500ms。也就是說,當快照展示 1.5s 後,有一半多的人會開始首次的互動。這也足矣說明讓快照具備可互動能力的重要性。
展望
快照技術雖然緣起是為解決小程式啟動效能問題,但實際運用場景可以擴充套件到更多地方。
理論上來說,任何形式的非同步渲染場景,不論是現在 WebView 還是 weex 渲染的小程式,或者就是普通的 H5 網頁,甚至是一些 native 的場景(需要 loading 的場景),只要是一塊能夠在客戶端中展示的檢視,都能夠運用快照技術解決其過程中的白屏或 loading 問題,並且都能做到秒出、可互動。因為快照是一個純 native 的技術,它的實現本來就不依賴於真實頁面的渲染方式,它更需要關心的是更合適的快照時機和應用場景從而獲取更佳的體驗。
總結
我們提出了一種全新的小程式快照技術,實現了小程式首頁的秒開和可互動。它能夠徹底消除小程式開啟過程中的 loading 或白屏現象,讓小程式開啟達到了 native 的體驗,還可以響應使用者點選互動。
它是一種純 native 的技術,不依賴於小程式容器和前端的渲染,只要有檢視就能快照,只要有快照資料就能立即展示,甚至可擴充套件運用於其它非小程式場景。
而其侷限性主要是依賴首屏樣式和快照時機選擇,多變、含使用者隱私資料的首屏不適合快照,而且優質快照的生成的時機要求比較苛刻。在快照準確性保障方面,快照的相似度對比方法上也仍然有很大的優化空間,這些都還需要在今後不斷打磨。