百度小程序框架性能優化實踐
發表時(shí)間:2021-1-5
發布人(rén):融晨科技
浏覽次數:67
今天給大(dà)家講的(de)題目是(shì)《百度開源小程序框架架構演進和(hé / huò)性能優化實踐》。本次分享包含兩部分,第一部分是(shì)百度智能小程序整體的(de)框架及演進,主要(yào / yāo)講百度小程序開發全流程概況、百度智能小程序框架,以(yǐ)及百度小程序多宿主運行保障;第二部分是(shì)百度小程序框架的(de)性能優化,主要(yào / yāo)講整個(gè)小程序的(de)啓動過程,以(yǐ)及從開發者角度,有哪些重要(yào / yāo)的(de)優化點。
百度智能小程序整體框架及演進
整個(gè)移動互聯網一直是(shì)在(zài) NA 和(hé / huò) H5 之(zhī)間尋找權衡,NA 的(de)性能好、能力強;H5 靈活性更高。我認爲(wéi / wèi)渲染分爲(wéi / wèi)兩派,一派就(jiù)是(shì) NA 渲染派,一派叫做 H5 渲染派。
NA 渲染派,比較有代表性的(de)如 RN、Flutter;
Web 渲染派,比如百度的(de)輕應用,以(yǐ)及之(zhī)後做的(de)小程序。
1. 開發全流程概覽

百度曾經做過的(de) Web 渲染派的(de)三個(gè)代表産品,分别是(shì)輕應用、直達号和(hé / huò)小程序。
輕應用,是(shì) H5 + 端能力。它是(shì)一個(gè)标準的(de) H5,增加了(le/liǎo)一些 NA 的(de) API,比如定位等。
直達号,在(zài)技術層面跟輕應用是(shì)一樣的(de)。
小程序,本質上(shàng)是(shì)一個(gè)受限的(de) H5 + 大(dà)量豐富的(de) API + UI 組件。現在(zài)我們給小程序提供的(de) API 有 300 多個(gè),組件有 30 多個(gè),組件是(shì)有界面的(de)。比如,視頻、地(dì / de)圖 。
爲(wéi / wèi)什麽小程序要(yào / yāo)受限,主要(yào / yāo)有兩個(gè)原因:
保持體驗的(de)一緻性。H5 太過靈活,JS 随時(shí)可以(yǐ)去改變界面。
安全考慮。因爲(wéi / wèi)我們提供了(le/liǎo)大(dà)量 API 和(hé / huò)組件,且這(zhè)些都是(shì)很底層的(de)一些能力,比如電話号碼、賬号,肯定不(bù)能輕易開放給大(dà)家。
怎麽受限,主要(yào / yāo)有兩點:
編寫語言,不(bù)再是(shì)直接寫 HTML ,而(ér)是(shì)用自定義語言 swan 來(lái)編寫 。
runtime 層有兩個(gè)棧,一個(gè)是(shì)渲染棧,一個(gè)是(shì) JS 執行棧,這(zhè)兩個(gè)棧從物理上(shàng)隔離,以(yǐ)保障安全性。
2. 智能小程序框架
(1)開發運行全流程

先簡單介紹一下整個(gè)百度智能小程序的(de)開發流程。
首先開發者用 swan 寫布局;
接着通過開發者工具打包,上(shàng)傳到(dào)我們的(de)小程序 B 端服務器;
然後是(shì)小程序的(de)審核流程,有機審、人(rén)審;
最後是(shì)用戶點擊小程序時(shí),客戶端請求小程序 C 端服務器,C 端服務器再從 B 端服務器獲取小程序包。整個(gè)過程都是(shì)加密傳輸,可以(yǐ)保證代碼的(de)安全。
(2)百度智能小程序框架 -SWAN

上(shàng)圖是(shì)一個(gè)百度智能小程序的(de)框架,我們内部命名爲(wéi / wèi) SWAN。
分層結構如下:
最上(shàng)層是(shì)開發者基礎庫,命名爲(wéi / wèi) swan-js ,開發者直接和(hé / huò)這(zhè)層打交道(dào)。swan-js 負責兩件事情:(1)swan 代碼轉爲(wéi / wèi) HTML,變成 WebView 可運行程序;(2)客戶端端能力的(de)封裝暴露。
再下一層是(shì) swan-native。這(zhè)裏面最核心的(de)是(shì) API 和(hé / huò)組件的(de) NA 實現。其中雙棧管理也(yě)在(zài)這(zhè)一層,另外标紅的(de) Extension 用于(yú)開發者宿主自身能力擴展使用,比如,貼吧宿主期望增加個(gè)發帖能力,就(jiù)可以(yǐ)通過此機制。
再下面這(zhè)層叫 Porting Layer。這(zhè)層是(shì)百度小程序爲(wéi / wèi)了(le/liǎo)實現開源,增加的(de)一層與宿主的(de)接口層。
最下面這(zhè)一層是(shì)宿主基礎能力層。如果宿主沒有這(zhè)些能力,可以(yǐ)參考百度開源的(de)參考實現,可直接集成到(dào)宿主使用。
3. 核心結構
(1)前端角度

我們從前端的(de)視角來(lái)看看雙棧結構。一個(gè)宿主客戶端可以(yǐ)運行多個(gè)小程序,并且在(zài)一段時(shí)間内保持存活狀态,每個(gè)小程序都有一個(gè) master 執行框架 JS 和(hé / huò)小程序開發者的(de) JS,一個(gè) master 對應多個(gè) slave(slave 代表一個(gè)用戶可見的(de)界面)。
(2)客戶端角度

從客戶端角度來(lái)看雙棧結構,如上(shàng)圖所示,master 負責執行 JS,可以(yǐ)有兩種實現方式,WebView 或 JS 引擎(V8/jscore),JS 引擎的(de)效率更高;slave 的(de)展現有 WebView,爲(wéi / wèi)了(le/liǎo)加快 WebView 的(de)創建速度,設置 cache;master 和(hé / huò) slave 的(de)通信通過消息總線。
master 不(bù)支持 BOM、DOM 和(hé / huò) WEB-API,小程序隻能調用對外開放的(de)能力。
(3)小程序 NA 組件與界面關系

從體驗上(shàng)看,小程序的(de)體驗要(yào / yāo)好于(yú) H5,其中有一點就(jiù)是(shì)小程序會把一些 NA 的(de)能力和(hé / huò) UI 融合到(dào)小程序裏面去。小程序的(de)主體渲染還是(shì)基于(yú) H5 技術,接下來(lái)我們講一下 NA 元素如何融入 UI 界面。
NA 元素與 H5 的(de)關系有兩種,貼片關系、同層關系。
貼片關系:NA 元素在(zài) H5 不(bù)在(zài)同一層,NA 浮在(zài) H5 之(zhī)上(shàng),H5 所有元素都不(bù)可以(yǐ)放到(dào) NA 元素之(zhī)上(shàng)。因爲(wéi / wèi)不(bù)在(zài)一層,就(jiù)需要(yào / yāo)處理滾動聯動,當檢測到(dào) WebView 滾動 n 個(gè)像素, NA 元素也(yě)需要(yào / yāo)滾動 n 個(gè)像素。
同層關系:NA 元素在(zài) H5 這(zhè)一層,H5 的(de)原生可以(yǐ)壓在(zài) NA 元素之(zhī)上(shàng)。

貼片、同層的(de)界面層級樹如上(shàng)。

我們舉一個(gè)視頻組件同層的(de)例子(zǐ)。視頻組件比較複雜,有 4 層。第 1 層是(shì)視頻層,即原始視頻的(de)圖像,第 2 層是(shì)彈幕層,第 3 層是(shì)用于(yú)視頻控制的(de)控件層(比如,開始、暫停按鍵),第 4 層 Slot 層,視頻上(shàng)面漂的(de) H5 元素将被放到(dào)這(zhè)層。
同層處理機制,先在(zài) H5(開發者寫的(de) swan 會被轉爲(wéi / wèi) H5) 上(shàng)打一個(gè)特殊的(de)标記先占位,屬性 inline;浏覽内核把這(zhè)個(gè)區域的(de) surface 拿出(chū)來(lái)給到(dào) NA 層,然後,小程序框架會把這(zhè)個(gè)區域 surface 塞給播放器,讓播放器直接在(zài) surface 上(shàng)面繪制,達到(dào)同層。上(shàng)面的(de)彈幕、控件、Slot,都是(shì) swanjs 層 H5 實現 ,Slot 層可以(yǐ)認爲(wéi / wèi)是(shì)一個(gè)容器,例如寫一個(gè) video,其所有的(de)子(zǐ)元素都會放在(zài) Slot。
NA 的(de)組件同層的(de)技術方案還不(bù)太一樣,安卓和(hé / huò) iOS 也(yě)是(shì)有些區别的(de)。像在(zài) iOS 上(shàng),如果有些組件設置 over-flow ,它會天然支持這(zhè)一套東西,但是(shì)安卓就(jiù)需要(yào / yāo)浏覽器内核來(lái)支持。
4. 小程序多宿主運行保障

百度智能小程序是(shì)開放系統,可以(yǐ)運行在(zài)多宿主之(zhī)上(shàng),那如何在(zài)多宿主上(shàng)保證小程序運行體驗的(de)一緻性呢?
各個(gè)宿主集成了(le/liǎo)我們的(de)小程序框架後,首先要(yào / yāo)跑 CTS 測試,通過之(zhī)後才可以(yǐ)拿到(dào)小程序列表進行分發。
對于(yú)可選能力部分,各個(gè)宿主不(bù)是(shì)所有的(de)能力都需要(yào / yāo)實現。比如,一些 AI 能力、push 能力。
如果一個(gè)小程序用到(dào)了(le/liǎo)可選能力怎麽辦?
兩個(gè)辦法,一是(shì)小程序和(hé / huò)宿主之(zhī)間的(de)雙向選擇機制,小程序可以(yǐ)選擇我要(yào / yāo)分發到(dào)哪些平台,宿主也(yě)有權利選擇要(yào / yāo)分發到(dào)哪些宿主。二是(shì),小程序做兼容。

上(shàng)圖所示,标紅的(de)是(shì) Extension 機制,當宿主有一些定制化的(de)要(yào / yāo)求時(shí),可以(yǐ)使用此機制。作爲(wéi / wèi)宿主,需要(yào / yāo)做兩件事情,一是(shì)在(zài) JS 層需要(yào / yāo)寫一套接口。二是(shì)在(zài) Porting Layer 接口實現層實現一套能力。如果宿主覺得這(zhè)個(gè)能力是(shì)通用的(de),可以(yǐ)反饋提案,審核通過後,百度小程序團隊會将提案合并到(dào)開源框架裏。
5. 章節小結

百度智能小程序框架性能優化實踐
首先從用戶視角看看一個(gè)小程序的(de)加載過程。
1. 百度智能小程序加載分階段過程

拿微博舉例,如上(shàng)圖所示。
首先小程序被啓動後,先是(shì)一個(gè) Loading 的(de)過程,上(shàng)面的(de) title 和(hé / huò)下面的(de) tab(框架 NA 實現)顯示出(chū)來(lái)。
第二張圖片我們定義爲(wéi / wèi) FP(First Paint )階段。
第三張圖下面有搜索框了(le/liǎo),這(zhè)其實是(shì)小程序包裏面的(de)内容。它是(shì)通過 initdate 接口初始化渲染出(chū)來(lái)的(de),此階段我們定義爲(wéi / wèi) FCP( First Contentfull Paint )階段。
第四張圖,是(shì)小程序從網上(shàng)拉到(dào)了(le/liǎo)實時(shí)的(de)内容,然後更新到(dào)界面,我們将其定義爲(wéi / wèi) FMP(First Meaningful Paint) 階段。
最後一張圖,所有的(de)元素都已經拉下來(lái)并展示了(le/liǎo),用戶可以(yǐ)操作任何一個(gè)位置,我們将其定義爲(wéi / wèi) TTI (Time to Interative) 階段。
2. 百度智能小程序
(1)性能基線
百度小程序在(zài) 2019 年底建立了(le/liǎo) FMP 指标,它在(zài)開發者平台展示的(de)名字爲(wéi / wèi)“上(shàng)屏時(shí)間”。
我們統計了(le/liǎo)線上(shàng)的(de)一個(gè) 80 分位點,耗時(shí)是(shì) 1.9s。(什麽是(shì) 80 分位點?比如,有 100 個(gè)請求過來(lái)了(le/liǎo),然後我們把請求的(de)耗時(shí)排序,第 80 個(gè)請求的(de)耗時(shí),我們就(jiù)認爲(wéi / wèi)是(shì) 80 分位點。)
(2)性能曆史曲線

如上(shàng)圖所示, 2019 年百度小程序性能優化的(de)曆史曲線。FP 框架層從接近 3s 一直優化了(le/liǎo)現在(zài)的(de) 1.1s 左右。百度小程序的(de)目标是(shì)讓小程序無線接近 NA 體驗。
3. 啓動流程

接下來(lái),我們從開發者角度看,還能優化什麽?我們先看一下啓動流程,所有的(de)啓動邏輯簡單串行羅列(實際是(shì)有一些步奏是(shì)并行的(de))。
4. 性能優化
開發者能夠做的(de)性能優化主要(yào / yāo)有兩部分。一是(shì)小程序包的(de)體積,二是(shì)業務數據。接下來(lái),我用三點來(lái)說(shuō)明開發者可以(yǐ)做什麽。
(1)包體積優化

建議包體積保持在(zài) 1M 以(yǐ)内,爲(wéi / wèi)什麽呢?
因爲(wéi / wèi)我們統計了(le/liǎo)一下,如果打開當次需要(yào / yāo)下載包,則這(zhè)次的(de)啓動時(shí)長會占到(dào)我們整個(gè)時(shí)長的(de) 60%。1M 的(de)包,80 分位速度需要(yào / yāo) 1s+ 才能下載完成。所以(yǐ)要(yào / yāo)控制自己的(de)包的(de)體積。而(ér)且我們現在(zài)還隻是(shì)看 80 分位,當我們拉到(dào) 90 分位,99 分位,這(zhè)個(gè)是(shì)一個(gè)非常陡的(de)曲線,惡化很嚴重。
包體優化機制有兩個(gè)技術:一是(shì)分包技術和(hé / huò)獨立分包技術,二是(shì)資源壓縮。
分包技術 & 獨立分包技術
分包技術

一個(gè)小程序有很多頁面,但不(bù)是(shì)所有的(de)頁面都是(shì)高 PV 頁面。很多頁面是(shì)用戶很少點到(dào)的(de),可以(yǐ)把這(zhè)些頁面放到(dào)我們的(de)分包去,主包放我們高 PV 的(de)頁面。
分包不(bù)能夠獨立運行,比如,從搜索 feed 分發過去,它運行時(shí)需要(yào / yāo)把我們主包下載下來(lái),但是(shì)因爲(wéi / wèi)它的(de)概率低,不(bù)會影響絕大(dà)多數情況。簡而(ér)言之(zhī),就(jiù)是(shì)用分包技術把非關鍵的(de)頁面剝離出(chū)去。
用分包技術把非關鍵的(de)頁面剝離出(chū)去之(zhī)後小程序包的(de)體積還是(shì)大(dà)的(de)話怎麽辦?
獨立分包技術
所謂獨立,就(jiù)是(shì)說(shuō)下載完這(zhè)個(gè)包之(zhī)後就(jiù)可以(yǐ)運行,無需下載主包。此時(shí)的(de)主包和(hé / huò)獨立分包的(de)區别就(jiù)是(shì),小程序總要(yào / yāo)有一個(gè)入口,這(zhè)個(gè)入口的(de)獨立分包,我們就(jiù)命名爲(wéi / wèi)主包。
通過這(zhè)兩項技術來(lái)減小我們的(de)包體大(dà)小,将其保持在(zài)在(zài) 1M 以(yǐ)内。
資源壓縮

我們分析過一些小程序,發現有的(de)包體裏包含 PC 圖片,這(zhè)無疑增加了(le/liǎo)包的(de)體積。建議如下:
把圖片放到(dào)服務器,不(bù)要(yào / yāo)放在(zài)包裏面。
壓縮圖片體積,比如,把 png 改爲(wéi / wèi) jpeg 格式這(zhè)樣體積可以(yǐ)減少 90%(不(bù)考慮透明度情況)。
剔除無用資源。
App-js 需要(yào / yāo)通過分包來(lái)解決,最終我們要(yào / yāo)達到(dào)什麽目标?
單個(gè)包控制在(zài) 1M 以(yǐ)内。
文件數控制在(zài) 200 個(gè)以(yǐ)内。
(2) 數據拉取
數據拉取的(de)目的(de)是(shì)快速讓界面有内容,減少用戶的(de)白屏時(shí)間。即使用戶是(shì)斷網的(de),也(yě)給他(tā)離線緩存一些數據。

如上(shàng)圖所示,這(zhè)裏面提到(dào)了(le/liǎo)業務骨架屏和(hé / huò)框架骨架屏。現在(zài)很多小程序都會參考 H5 的(de)實現,把 H5 的(de)漸進式加載骨架屏用到(dào)我們的(de)小程序裏面,用了(le/liǎo)這(zhè)種技術之(zhī)後,反而(ér)會讓真實的(de)内容展示的(de)速度變慢,我們統計大(dà)概有 300ms 延遲。
爲(wéi / wèi)了(le/liǎo)解決骨架屏導緻的(de)内容展示延遲,我們做了(le/liǎo)一套框架層的(de)骨架屏機制。按照我們這(zhè)個(gè)機制來(lái)實現骨架屏,對性能的(de)影響就(jiù)會大(dà)大(dà)減少。策略上(shàng)就(jiù)是(shì)在(zài) master 做 appjs 執行時(shí),就(jiù)讓 slave 加載框架骨架屏,并行執行。
自己寫的(de)業務骨架屏,它什麽時(shí)候才展示?如上(shàng)圖所示,當你把 App 、page、waitNotify 通知到(dào)渲染線程,到(dào)了(le/liǎo) Ready firstRender 的(de)時(shí)候才會渲染自己做的(de)業務骨架屏,這(zhè)個(gè)過程當然很慢。雖然你用了(le/liǎo)骨架屏,但是(shì)骨架屏和(hé / huò)用戶點擊的(de)這(zhè)段時(shí)間還有大(dà)量的(de)白屏時(shí)間。用框架骨架屏,白屏時(shí)間問題就(jiù)會解決。用框架骨架屏,或多或少都會耗一點時(shí)間,雖然是(shì)并行的(de),但是(shì)依然在(zài)搶占手機的(de)資源。
所以(yǐ)整體來(lái)看,站在(zài)客戶端或者站在(zài)框架的(de)角度,我們是(shì)不(bù)建議用,但是(shì)也(yě)不(bù)反對用。如果要(yào / yāo)用就(jiù)用框架骨架屏,影響最小。

request 的(de)優化,我總結主要(yào / yāo)是(shì)兩點,第一要(yào / yāo)早,第二要(yào / yāo)少。
“早”又可以(yǐ)分兩部分來(lái)說(shuō),一是(shì)提早發,二是(shì)不(bù)阻塞。
第一是(shì)提早發,請求得太晚,展示當然比較慢了(le/liǎo)。建議把網絡請求放在(zài) onlaunch 裏面,這(zhè)是(shì)我們給小程序開放的(de)第一個(gè)事件,很多小程序會放到(dào) page unload 裏面,這(zhè)個(gè)就(jiù)比較慢了(le/liǎo)。這(zhè)兩個(gè)時(shí)間在(zài)線上(shàng) 80 分位,大(dà)概差 200ms~300ms。
第二個(gè)是(shì)不(bù)阻塞,經常看到(dào)一些小程序,一起來(lái)以(yǐ)後,它要(yào / yāo)等用戶的(de)授權、定位。通常定位涉及 XY 坐标,但是(shì)定位一旦涉及高度,就(jiù)需要(yào / yāo)打開 GPS,這(zhè)樣性能又會慢 2s~3s。如果不(bù)需要(yào / yāo)高度就(jiù)不(bù)要(yào / yāo)去設置,否則非常慢。還有的(de)小程序在(zài)使用的(de)時(shí)候會讓用戶授權,如果不(bù)授權下面什麽也(yě)不(bù)展示,阻塞了(le/liǎo)。如果可以(yǐ)的(de)話,建議在(zài)需要(yào / yāo)授權的(de)時(shí)候再提示用戶,這(zhè)樣用戶也(yě)不(bù)反感,也(yě)能加快啓動的(de)速度。
“少”,主要(yào / yāo)分爲(wéi / wèi)兩點,一是(shì)非關鍵請求延後,二是(shì)隻拉取一屏數據。
一個(gè)小程序運行後,可能有幾十甚至上(shàng)百個(gè)網絡請求,小程序除了(le/liǎo)自己的(de)業務還要(yào / yāo)打點,這(zhè)會很大(dà)程度上(shàng)影響我們的(de)網絡速度。因爲(wéi / wèi)一般的(de)宿主在(zài)底層的(de)網絡庫都會設置線程池,請求多了(le/liǎo)就(jiù)要(yào / yāo)排隊。小程序框架根本不(bù)知道(dào)某個(gè)請求是(shì)核心請求還是(shì)非核心請求,隻能排隊。要(yào / yāo)是(shì)一上(shàng)來(lái)全是(shì)一些打點的(de),業務就(jiù)阻塞了(le/liǎo)。總之(zhī),整個(gè)頁面需要(yào / yāo)顯示的(de)數據先請求,非關鍵請求延後。
二是(shì)隻拉一屏數據,分段加載。
(3)渲染
setData 操作是(shì)較爲(wéi / wèi)昂貴的(de),盡量減少數據量和(hé / huò)次數。

如上(shàng)圖所示,setData 是(shì)一個(gè)非常核心的(de) API, 當網絡數據回來(lái),隻有經過 setData 驅動渲染,内容才能顯示到(dào)界面上(shàng)。
上(shàng)圖是(shì)一個(gè)優化前和(hé / huò)優化後的(de)對比。我們可以(yǐ)看到(dào),即使是(shì) 1K 的(de)數據,也(yě)需要(yào / yāo) 20ms 左右的(de)時(shí)間。如果 js 是(shì)用 WebView 來(lái)執行,首先一個(gè) JS string,到(dào)了(le/liǎo)浏覽器有 Renderer 線程、Browser 線程,變爲(wéi / wèi) C 層的(de) string,然後再到(dào)我們 NA ,通過 Java interface,變成一個(gè) Java string。然後到(dào)了(le/liǎo) slave 以(yǐ)後還要(yào / yāo)再反過來(lái),所以(yǐ)快不(bù)了(le/liǎo)。雖然我們做了(le/liǎo)一些優化,通過内核讓它變成一個(gè)内存指針優化切換,但是(shì)還是(shì)很昂貴。

發現有些小程序在(zài)使用的(de)過程中, setData 使用有很多不(bù)當之(zhī)處,以(yǐ)下是(shì)使用 setdata 要(yào / yāo)注意的(de)幾點 。
減少調用 setData 次數。goodcase:将多次 setData 合并成一次 setData 調用。
減少 setData 數據量。badcase:新一頁數據添加上(shàng)之(zhī)前頁面數據後再調用 setData。
變量變化隻更新變量不(bù)更新對象。
5. 性能自查

性能自查主要(yào / yāo)有三個(gè)階段,即開發階段、測試階段和(hé / huò)上(shàng)線後。
在(zài)開發階段這(zhè)部分,我們有三個(gè)手段去性能自查,分别是(shì)工具體驗評分、性能面闆(在(zài)客戶端上(shàng)性能面闆可以(yǐ)提示整個(gè)性能啓動的(de)耗時(shí))以(yǐ)及打點系統。
在(zài)測試階段我們有兩個(gè)手段,一是(shì)錄屏,二是(shì)高速攝像頭,這(zhè)兩個(gè)手段可以(yǐ)真實地(dì / de)反應用戶的(de)體驗。
上(shàng)線之(zhī)後,有開發者平台。
如何獲取技術的(de)官方支持途徑?建議去開發者文檔和(hé / huò)社群去獲取技術支持。

6. 章節小結
開發者可從包體積、數據請求、渲染三方面去優化性能。
包體:1M 内。分包技術、壓縮圖片、無用資源剔除。
骨架屏:如果要(yào / yāo)使用,建議采用框架骨架屏。
setData:減少頻度、減少數據量。